WebUI: add vault management

Add vault management into WebUI, there are some constraints:
- There is no crypto library so Symmetric and Assymetric vaults
  are not supported in WebUI. Also retrieving or archiving data
  is not supported.
- There aren't any container support right now

Supported is:
- Browsing vaults
- Adding Standard vaults (users, service, shared)
- Removing vaults
- Adding and removing owners
- Adding and removing members

https://fedorahosted.org/freeipa/ticket/5426

Reviewed-By: Martin Basti <mbasti@redhat.com>
Reviewed-By: Petr Vobornik <pvoborni@redhat.com>
This commit is contained in:
Pavel Vomacka 2016-10-05 12:06:27 +02:00 committed by Martin Basti
parent 587b7324fb
commit 39d7ef3de4
6 changed files with 948 additions and 2 deletions

View File

@ -51,11 +51,12 @@ define([
'./selinux',
'./serverconfig',
'./service',
'./stageuser',
'./sudo',
'./trust',
'./topology',
'./user',
'./stageuser',
'./vault',
'dojo/domReady!'
],function(app_container) {
return app_container;

View File

@ -240,6 +240,18 @@ var IPA = function () {
}
}));
batch.add_command(rpc.command({
entity: 'vaultconfig',
method: 'show',
retry: false,
on_success: function(data, text_status, xhr) {
that.vault_enabled = true;
},
on_error: function(xhr, text_status, error_thrown) {
that.vault_enabled = false;
}
}));
batch.execute();
};

View File

@ -215,6 +215,32 @@ var nav = {};
{ entity: 'dnsserver' },
{ entity: 'dnsconfig' }
]
},
{
name: 'vault',
entity: 'vault',
facet: 'search',
children: [
{
entity: 'vault',
facet: 'user_search',
hidden: true
},
{
entity: 'vault',
facet: 'service_search',
hidden: true
},
{
entity: 'vault',
facet: 'shared_search',
hidden: true
},
{
entity: 'vaultconfig',
hidden: true
}
]
}
]
},
@ -298,7 +324,32 @@ nav.self_service = {
name: 'self-service',
items: [
{ entity: 'user' },
{ entity: 'otptoken' }
{ entity: 'otptoken' },
{
entity: 'vault',
facet: 'search',
children: [
{
entity: 'vault',
facet: 'user_search',
hidden: true
},
{
entity: 'vault',
facet: 'service_search',
hidden: true
},
{
entity: 'vault',
facet: 'shared_search',
hidden: true
},
{
entity: 'vaultconfig',
hidden: true
}
]
}
]
};

View File

@ -0,0 +1,819 @@
//
// Copyright (C) 2017 FreeIPA Contributors see COPYING for license
//
define([
'dojo/on',
'./ipa',
'./jquery',
'./phases',
'./menu',
'./rpc',
'./reg',
'./text',
'./widget'],
function(on, IPA, $, phases, menu, rpc, reg, text, widget) {
/**
* Vault module
* @class vault
* @alternateClassName IPA.vault
* @singleton
*/
var vault = IPA.vault = {
search_facet_group: {
name: 'vaults',
facets: {
vault_search: 'vault_search',
user_search: 'vault_user_search',
service_search: 'vault_service_search',
shared_search: 'vault_shared_search',
vaultconfig_details: 'vaultconfig_details'
}
}
};
/**
* Create general specification of "* Vaults" details facets.
*/
var make_vaults_details_page_spec = function() {
return {
$type: 'details',
$factory: vault.custom_details_facet,
update_command_name: 'mod_internal',
disable_facet_tabs: true,
sections: [
{
name: 'global_info',
layout_ccs_class: 'col-sm-12',
fields: [
'cn',
'description',
{
name: 'ipavaulttype',
read_only: true
},
{
$type: 'text',
name: 'ipavaultsalt',
visible: false
},
{
$type: 'pub_key',
name: 'ipavaultpublickey',
visible: false
}
]
},
{
$factory: IPA.section,
name: 'divider',
layout_css_class: 'col-sm-12',
fields: []
},
{
$factory: IPA.section,
name: 'members',
label: '@i18n:objects.vault.members',
fields: [
{
$type: 'association_table',
id: 'member_user_cn',
name: 'member_user',
acl_param: 'member',
columns: [
{
name: 'member_user',
label: '@i18n:objects.vault.user'
}
]
},
{
$type: 'association_table',
id: 'member_group_cn',
name: 'member_group',
other_entity: 'group',
acl_param: 'member',
columns: [
{
name: 'member_group',
label: '@i18n:objects.vault.group'
}
]
},
{
$type: 'association_table',
id: 'member_service_cn',
name: 'member_service',
other_entity: 'service',
other_option_name: 'services',
acl_param: 'member',
columns: [
{
name: 'member_service',
label: '@i18n:objects.vault.service'
}
]
}
]
},
{
$factory: IPA.section,
name: 'owners',
label: '@i18n:objects.vault.owners',
fields: [
{
$type: 'association_table',
id: 'owner_user_cn',
name: 'owner_user',
add_method: 'add_owner',
remove_method: 'remove_owner',
other_entity: 'user',
acl_param: 'owner',
columns: [
{
name: 'owner_user',
label: '@i18n:objects.vault.user'
}
]
},
{
$type: 'association_table',
id: 'owner_group_cn',
name: 'owner_group',
add_method: 'add_owner',
remove_method: 'remove_owner',
other_entity: 'group',
acl_param: 'owner',
columns: [
{
name: 'owner_group',
label: '@i18n:objects.vault.group'
}
]
},
{
$type: 'association_table',
id: 'owner_service_cn',
name: 'owner_service',
add_method: 'add_owner',
remove_method: 'remove_owner',
other_entity: 'service',
other_option_name: 'services',
acl_param: 'owner',
columns: [
{
name: 'owner_service',
label: '@i18n:objects.vault.service'
}
]
}
]
}
]
};
};
/**
* Create entity spec for whole vaults and also spec for search facet, adder
* and deleter dialog.
*/
var make_my_vault_spec = function() {
var entity = {
name: 'vault',
facets: [
{
$type: 'search',
tab_label: '@i18n:objects.vault.my_vaults_title',
facet_groups: [vault.search_facet_group],
facet_group: 'vaults',
disable_facet_tabs: false,
search_all_entries: true,
tabs_in_sidebar: true,
custom_actions: [
{
$type: 'add',
hide_cond: []
},
{
$type: 'batch_remove',
hide_cond: []
}
],
columns: [
'cn',
'ipavaulttype'
],
policies: [
vault.config_sidebar_policy
]
}
],
adder_dialog: {
$factory: vault.custom_adder_dialog,
name: 'add',
method: 'add_internal',
policies: [
{ $factory: vault.adder_policy }
]
},
deleter_dialog: {
// Each parametr is present only in one facet. It could cause errors
// in case that table on each facet gather more columns with these names.
// I.e. facet with user vaults get column with name 'service', then
// the value of 'service' column will be also added to command options.
additional_table_attrs: ['username', 'service', 'shared']
}
};
/**
* This function extends general details facet - so the same declaration
* of facet (which would differ only in several lines)
* should not be present six times.
*/
var update_facet_spec = function(facet, facet_type) {
facet.sections[0].fields.push(facet_type);
facet.refresh_attribute = facet_type;
facet.update_attribute = facet_type;
var user_members = facet.sections[2].fields[0];
var group_members = facet.sections[2].fields[1];
var service_members = facet.sections[2].fields[2];
var user_owners = facet.sections[3].fields[0];
var group_owners = facet.sections[3].fields[1];
var service_owners = facet.sections[3].fields[2];
var attributes = {
refresh_attribute: facet_type,
additional_add_del_field: facet_type
};
$.extend(user_members, attributes);
$.extend(user_owners, attributes);
$.extend(group_members, attributes);
$.extend(group_owners, attributes);
$.extend(service_members, attributes);
$.extend(service_owners, attributes);
};
// Create details page for my vauls:
var details_spec = make_vaults_details_page_spec();
entity.facets.push(details_spec);
// Create details page for user vaults and modify it
details_spec = make_vaults_details_page_spec();
details_spec.name = 'vault_user';
update_facet_spec(details_spec, 'username');
details_spec.redirect_info = {
facet: 'user_search'
};
entity.facets.push(details_spec);
// Create details page for service vaults and modify it
details_spec = make_vaults_details_page_spec();
details_spec.name = 'vault_service';
update_facet_spec(details_spec, 'service');
details_spec.redirect_info = {
facet: 'service_search'
};
entity.facets.push(details_spec);
// Create details page for shared vaults and modify it
details_spec = make_vaults_details_page_spec();
details_spec.name = 'vault_shared';
update_facet_spec(details_spec, 'shared');
details_spec.redirect_info = {
facet: 'shared_search'
};
entity.facets.push(details_spec);
return entity;
};
vault.custom_details_facet = function(spec) {
spec = spec || {};
var that = IPA.details_facet(spec);
that.load = function(data) {
that.details_facet_load(data);
// show fields according to the type of vault
var type_f = that.fields.get_field('ipavaulttype');
var type = type_f.value[0];
var salt_w = that.fields.get_field('ipavaultsalt').widget;
var pub_key_w = that.fields.get_field('ipavaultpublickey').widget;
if (type === 'symmetric') {
pub_key_w.set_visible(false);
salt_w.set_visible(true);
} else if (type === 'asymmetric') {
pub_key_w.set_visible(true);
salt_w.set_visible(false);
} else {
pub_key_w.set_visible(false);
salt_w.set_visible(false);
}
};
return that;
};
vault.public_key_widget = function(spec) {
spec = spec || {};
var that = IPA.sshkey_widget(spec);
that.set_user_value = function(value) {
var previous = that.key;
that.key = value;
that.update_link();
if (value !== previous) {
that.value_changed.notify([], that);
that.emit('value-change', { source: that });
}
};
that.update = function(value) {
var key = value[0];
if (key) that.key = key;
if (that.key && that.key !== '') {
that.originally_set = true;
that.original_key = that.key;
}
that.update_link();
that.on_value_changed(value);
};
that.get_status = function() {
var status = '';
var value = that.key;
if (that.original_key) {
if (value !== that.original_key) {
if (value === '') {
status = text.get('@i18n:objects.publickey.status_mod_ns');
} else {
status = text.get('@i18n:objects.publickey.status_mod_s');
}
} else {
// f00c is code of check icon
var decimal_check_i = parseInt('f00c', 16);
status = String.fromCharCode(decimal_check_i);
}
} else {
if (!value || value === '') {
status = text.get('@i18n:objects.publickey.status_new_ns');
} else {
status = text.get('@i18n:objects.publickey.status_new_ns');
}
}
return status;
};
that.create_edit_dialog = function() {
var writable = that.is_writable();
var dialog = IPA.dialog({
name: 'pubkey-edit-dialog',
title: '@i18n:objects.publickey.set_dialog_title',
width: 500,
height: 380
});
dialog.message = text.get('@i18n:objects.publickey.set_dialog_help');
dialog.create_button({
name: 'update',
label: '@i18n:buttons.set',
click: function() {
var value = dialog.textarea.val();
that.set_user_value(value);
dialog.close();
}
});
dialog.create_button({
name: 'cancel',
label: '@i18n:buttons.cancel',
click: function() {
dialog.close();
}
});
dialog.create_content = function() {
dialog.container.append(dialog.message);
dialog.textarea = $('<textarea/>', {
'class': 'certificate',
disabled: !that.enabled
}).appendTo(dialog.container);
var key = that.key || '';
dialog.textarea.val(key);
};
return dialog;
};
return that;
};
/**
* Adder policy handles realtime showing and hiding fields when user switch
* between User/Service/Shared vault in adder dialog.
*
* @extends IPA.facet_policy
*/
vault.adder_policy = function(spec) {
var that = IPA.facet_policy(spec);
that.init = function() {
var type_f = that.container.fields.get_field('type');
on(type_f, 'value-change', that.on_type_change);
};
that.on_type_change = function() {
var type_f = that.container.fields.get_field('type');
var user_f = that.container.fields.get_field('username');
var service_f = that.container.fields.get_field('service');
var mode = type_f.get_value()[0];
var user = true;
var service = true;
if (mode === 'user') service = false;
else if (mode === 'service') user = false;
else if (mode === 'shared') user = service = false;
user_f.set_enabled(user);
user_f.widget.set_visible(user);
service_f.set_enabled(service);
service_f.widget.set_visible(service);
};
return that;
};
/**
* Custom adder dialog.
*
* @extends IPA.entity_adder_dialog
*/
vault.custom_adder_dialog = function(spec) {
spec = spec || {};
spec.sections = spec.sections || [];
var section_warn_arch_ret= {
show_header: false,
name: 'warning_ar',
fields: [
{
field: false,
$type: 'html',
name: 'warn_arch_ret'
}
],
layout: {
$factory: widget.fluid_layout,
widget_cls: "col-sm-12 controls",
label_cls: "hide"
}
};
var section_f = {
show_header: false,
fields: [
{
$type: 'radio',
name: 'type',
flags: ['no_command'],
label: '@i18n:objects.vault.type',
options: [
{
value: 'user',
label: '@i18n:objects.vault.user'
},
{
value: 'service',
label: '@i18n:objects.vault.service'
},
{
value: 'shared',
label: '@i18n:objects.vault.shared'
}
]
},
{
$type: 'entity_select',
name: 'username',
other_entity: 'user',
other_field: 'uid'
},
{
$type: 'entity_select',
name: 'service',
other_entity: 'service',
other_field: 'krbprincipalname'
},
'cn',
'description',
{
$type: 'radio',
name: 'ipavaulttype',
default_value: 'standard',
read_only: true,
options: [
{
value: 'standard',
label: '@i18n:objects.vault.standard_type'
},
{
label: '@i18n:objects.vault.symmetric_type'
},
{
label: '@i18n:objects.vault.asymmetric_type'
}
],
tooltip: "@i18n:objects.vault.type_tooltip"
}
]
};
var section_warn_standard = {
name: 'warning_st',
fields: [
{
field: false,
$type: 'html',
name: 'warn_standard'
}
],
layout: {
$factory: widget.fluid_layout,
widget_cls: "col-sm-12 controls",
label_cls: "hide"
}
};
spec.sections.push(section_warn_arch_ret);
spec.sections.push(section_f);
spec.sections.push(section_warn_standard);
var that = IPA.entity_adder_dialog(spec);
that.create_add_command = function(record) {
var command = that.entity_adder_dialog_create_add_command(record);
var type_f = that.fields.get_field('type');
var type = type_f.save()[0];
if (type === 'shared') command.set_option(type, true);
return command;
};
that.create_content = function() {
var warn_arch_ret_w = that.widgets.get_widget('warning_ar.warn_arch_ret');
var warn_st_w = that.widgets.get_widget('warning_st.warn_standard');
var warn_arch_text = text.get('@i18n:objects.vault.add_warn_arch_ret');
var warn_st_text = text.get('@i18n:objects.vault.add_warn_standard');
var warn_arch_ret = IPA.alert_helper.create_alert('arch', warn_arch_text);
var warn_standard = IPA.alert_helper.create_alert('standard',
warn_st_text);
warn_st_w.html = IPA.alert_helper.render_alert(warn_standard);
warn_arch_ret_w.html = IPA.alert_helper.render_alert(warn_arch_ret);
that.entity_adder_dialog_create_content();
var facet_name = that.entity.facet.name;
facet_name = facet_name.substr(0, facet_name.indexOf('_'));
var type_f = that.fields.get_field('type');
type_f.set_pristine_value([facet_name]);
if (IPA.is_selfservice) type_f.set_writable(false);
};
return that;
};
/**
* Creates specification of search facet for User Vaults
*/
var make_user_vault_search_spec = function() {
return {
$type: 'search',
entity: 'vault',
managed_entity: 'vault',
name: 'user_search',
tab_label: '@i18n:objects.vault.user_vaults_title',
label: '@i18n:objects.vault.user_vaults_title',
facet_groups: [vault.search_facet_group],
facet_group: 'vaults',
custom_actions: [
{
$type: 'add',
hide_cond: []
},
{
$type: 'batch_remove',
hide_cond: []
}
],
additional_navigation_arguments: ['username'],
show_values_with_dup_key: true,
details_facet: 'vault_user',
show_command_additional_attr: 'username',
disable_facet_tabs: false,
tabs_in_sidebar: true,
command_options: {
'users': true
},
columns: [
'cn',
'username',
'ipavaulttype'
],
policies: [
vault.config_sidebar_policy
]
};
};
var make_service_vault_spec = function() {
return {
$type: 'search',
entity: 'vault',
managed_entity: 'vault',
name: 'service_search',
tab_label: '@i18n:objects.vault.service_vaults_title',
label: '@i18n:objects.vault.service_vaults_title',
facet_groups: [vault.search_facet_group],
facet_group: 'vaults',
additional_navigation_arguments: ['service'],
show_values_with_dup_key: true,
details_facet: 'vault_service',
show_command_additional_attr: 'service',
disable_facet_tabs: false,
tabs_in_sidebar: true,
command_options: {
'services': true
},
columns: [
'cn',
'service',
'ipavaulttype'
],
policies: [
vault.config_sidebar_policy
]
};
};
var make_shared_vault_spec = function() {
return {
$type: 'search',
entity: 'vault',
managed_entity: 'vault',
tab_label: '@i18n:objects.vault.shared_vaults_title',
name: 'shared_search',
label: '@i18n:objects.vault.shared_vaults_title',
facet_groups: [vault.search_facet_group],
facet_group: 'vaults',
additional_navigation_arguments: ['shared'],
show_values_with_dup_key: true,
show_command_additional_attr: 'shared',
details_facet: 'vault_shared',
disable_facet_tabs: false,
tabs_in_sidebar: true,
command_options: {
'shared': true
},
columns: [
'cn',
'shared',
'ipavaulttype'
],
policies: [
vault.config_sidebar_policy
]
};
};
var make_vaultconfig_spec = function() {
return {
name: 'vaultconfig',
facets: [
{
$type: 'details',
label: '@i18n:objects.vault.config_title',
tab_label: '@i18n:objects.vault.config_title',
facet_groups: [vault.search_facet_group],
facet_group: 'vaults',
disable_facet_tabs: false,
tabs_in_sidebar: true,
check_rights: false,
no_update: true,
fields: [
'kra_server_server',
{
$type: 'textarea',
name: 'transport_cert',
read_only: true,
style: {
width: '550px',
height: '350px'
}
}
],
policies: [
vault.config_sidebar_policy
]
}
]
};
};
vault.config_sidebar_policy = function(spec) {
var that = IPA.facet_policy(spec);
that.post_create = function(data) {
if (IPA.is_selfservice && that.container &&
that.container.tabs_in_sidebar) {
var header = that.container.header;
if (header) header.tabs_widget.hide_tab('vaultconfig_details');
}
};
return that;
};
vault.remove_vault_menu_item = function() {
if (!IPA.vault_enabled) {
menu.remove_item('network_services/vault');
}
};
vault.my_vault_spec = make_my_vault_spec();
vault.user_vault_search_spec = make_user_vault_search_spec();
vault.service_vault_spec = make_service_vault_spec();
vault.shared_vault_spec = make_shared_vault_spec();
vault.vaultconfig_spec = make_vaultconfig_spec();
vault.register = function() {
var e = reg.entity;
var fa = reg.facet;
var w = reg.widget;
w.register('pub_key', vault.public_key_widget);
e.register({type: 'vault', spec: vault.my_vault_spec});
e.register({type: 'vaultconfig', spec: vault.vaultconfig_spec});
fa.register_from_spec('vault_user_search', vault.user_vault_search_spec);
fa.register_from_spec('vault_service_search', vault.service_vault_spec);
fa.register_from_spec('vault_shared_search', vault.shared_vault_spec);
};
phases.on('registration', vault.register);
phases.on('profile', vault.remove_vault_menu_item, 20);
return vault;
});

View File

@ -669,6 +669,31 @@
"status_link": "Click to ${action}",
"unlock": "Unlock",
"unlock_confirm": "Are you sure you want to unlock user ${object}?"
},
"vault": {
"add_warn_arch_ret": "Secrets can be added/retrieved
to vault only by using vault-archive and
vault-retrieve from CLI.",
"add_warn_standard": "Content of 'standard' vaults
can be seen by users with higher privileges
(admins).",
"asymmetric_type": "Asymmetric",
"config_title": "Vaults Config",
"group": "Group",
"members": "Members",
"my_vaults_title": "My User Vaults",
"owners": "Owners",
"service": "Service",
"service_vaults_title": "Service Vaults",
"shared": "Shared",
"shared_vaults_title": "Shared Vaults",
"standard_type": "Standard",
"symmetric_type": "Symmetric",
"type": "Vault Type",
"type_tooltip": "Only standard vaults can be created
in WebUI, use CLI for other types of vaults.",
"user": "User",
"user_vaults_title": "User Vaults"
}
},
"password": {

View File

@ -692,6 +692,15 @@ class i18n_messages(Command):
"privilege": {
"identity": _("Privilege Settings"),
},
"publickey": {
"set_dialog_help": _("Public key:"),
"set_dialog_title": _("Set public key"),
"show_set_key": _("Show/Set key"),
"status_mod_ns": _("Modified: key not set"),
"status_mod_s": _("Modified"),
"status_new_ns": _("New: key not set"),
"status_new_s": _("New: key set"),
},
"pwpolicy": {
"identity": _("Password Policy"),
},
@ -852,6 +861,35 @@ class i18n_messages(Command):
"unlock": _("Unlock"),
"unlock_confirm": _("Are you sure you want to unlock user ${object}?"),
},
"vault": {
"add_warn_arch_ret": _(
"Secrets can be added/retrieved to vault only by using "
"vault-archive and vault-retrieve from CLI."
),
"add_warn_standard": _(
"Content of 'standard' vaults can be seen by users with "
"higher privileges (admins)."
),
"asymmetric_type": _("Asymmetric"),
"config_title": _("Vaults Config"),
"group": _("Group"),
"members": _("Members"),
"my_vaults_title": _("My User Vaults"),
"owners": _("Owners"),
"service": _("Service"),
"service_vaults_title": _("Service Vaults"),
"shared": _("Shared"),
"shared_vaults_title": _("Shared Vaults"),
"standard_type": _("Standard"),
"symmetric_type": _("Symmetric"),
"type": _("Vault Type"),
"type_tooltip": _(
"Only standard vaults can be created in WebUI, use CLI "
"for other types of vaults."
),
"user": _("User"),
"user_vaults_title": _("User Vaults"),
},
},
"password": {
"current_password": _("Current Password"),