URL: https://github.com/freeipa/freeipa/pull/139
Author: pvomacka
 Title: #139: WebUI: Vault Management
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/139/head:pr139
git checkout pr139
From 5ae278199c0ae562647b7fba63b24de359a606a5 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Wed, 5 Oct 2016 09:54:24 +0200
Subject: [PATCH 01/15] Additional option to add and del operations can be set

By setting the property 'additional_add_del_field' to the name of one of
the fields which are on current details page, we choose field which value
will be added to  *_add_* and *_del_* commands in this format:

{field_name: field_value}
--field_name: field_value

Part of: https://fedorahosted.org/freeipa/ticket/5426
---
 install/ui/src/freeipa/association.js | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/install/ui/src/freeipa/association.js b/install/ui/src/freeipa/association.js
index 7579bb0..d44f8c8 100644
--- a/install/ui/src/freeipa/association.js
+++ b/install/ui/src/freeipa/association.js
@@ -421,6 +421,14 @@ IPA.association_table_widget = function (spec) {
 
     var that = IPA.table_widget(spec);
 
+    /**
+     * The value should be name of the field, which will be added to *_add_*,
+     * *_del_* commands as option: {fieldname: fieldvalue}.
+     *
+     * @property {String} fieldname
+     */
+    that.additional_add_del_field = spec.additional_add_del_field;
+
     that.other_entity = IPA.get_entity(spec.other_entity);
     that.attribute_member = spec.attribute_member;
 
@@ -677,9 +685,22 @@ IPA.association_table_widget = function (spec) {
         });
         command.set_option(that.other_entity.name, values);
 
+        that.join_additional_option(command);
+
         command.execute();
     };
 
+    that.join_additional_option = function(command) {
+        var add_opt = that.additional_add_del_field;
+        if (add_opt && typeof add_opt === 'string') {
+            var opt_field = that.entity.facet.get_field(add_opt);
+            var value;
+            if (opt_field) value = opt_field.get_value()[0];
+
+            command.set_option(add_opt, value);
+        }
+    };
+
     that.show_remove_dialog = function() {
 
         var selected_values = that.get_selected_values();
@@ -741,6 +762,7 @@ IPA.association_table_widget = function (spec) {
         });
 
         command.set_option(that.other_entity.name, values);
+        that.join_additional_option(command);
 
         command.execute();
     };

From 0322f2e82f024a8f3da0ad33401caba8f8ea68bb Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Wed, 5 Oct 2016 10:09:20 +0200
Subject: [PATCH 02/15] Allow to set another other_entity name

Association table's add, del commands needs as option list of cn of
other_entity, which is added or deleted. There is a case (currently in vaults)
that the name of option is different than the name of other_entity.
In this situation we can set 'other_option_name' and put there the option name.
This option name will be used instead of 'other_entity' name.

Part of: https://fedorahosted.org/freeipa/ticket/5426
---
 install/ui/src/freeipa/association.js | 29 ++++++++++++++++++++++++++---
 1 file changed, 26 insertions(+), 3 deletions(-)

diff --git a/install/ui/src/freeipa/association.js b/install/ui/src/freeipa/association.js
index d44f8c8..02f990a 100644
--- a/install/ui/src/freeipa/association.js
+++ b/install/ui/src/freeipa/association.js
@@ -429,6 +429,22 @@ IPA.association_table_widget = function (spec) {
      */
     that.additional_add_del_field = spec.additional_add_del_field;
 
+    /**
+     * Can be used in situations when the *_add_member command needs entity
+     * as a parameter, but parameter has different name than entity.
+     * i.e. vault_add_member --services=[values] ... this needs values from service
+     * entity, but option is called services, that we can set by setting
+     * this option in spec to other_option_name: 'services'
+     *
+     * @property {String} other_option_name
+     */
+    that.other_option_name = spec.other_option_name;
+
+    /**
+     * Entity which is added into member table.
+     *
+     * @property {String} other_entity
+     */
     that.other_entity = IPA.get_entity(spec.other_entity);
     that.attribute_member = spec.attribute_member;
 
@@ -683,9 +699,9 @@ IPA.association_table_widget = function (spec) {
             on_success: on_success,
             on_error: on_error
         });
-        command.set_option(that.other_entity.name, values);
 
         that.join_additional_option(command);
+        that.handle_entity_option(command, values);
 
         command.execute();
     };
@@ -701,6 +717,14 @@ IPA.association_table_widget = function (spec) {
         }
     };
 
+    that.handle_entity_option = function(command, values) {
+        var option_name = that.other_option_name;
+        if (!option_name) {
+            option_name = that.other_entity.name;
+        }
+        command.set_option(option_name, values);
+    };
+
     that.show_remove_dialog = function() {
 
         var selected_values = that.get_selected_values();
@@ -745,7 +769,6 @@ IPA.association_table_widget = function (spec) {
             );
         };
 
-
         dialog.open();
     };
 
@@ -761,8 +784,8 @@ IPA.association_table_widget = function (spec) {
             on_error: on_error
         });
 
-        command.set_option(that.other_entity.name, values);
         that.join_additional_option(command);
+        that.handle_entity_option(command, values);
 
         command.execute();
     };

From 0ee98b2b9d67dd852a91a1580d226e8f2a2682a4 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Wed, 5 Oct 2016 10:20:25 +0200
Subject: [PATCH 03/15] Possibility to skip checking writable according to
 metadata

Useful in association tables which need to ignore object's metadata flags.
Association tables don't check right at all. They check them only when
'acl_param' is set in association table field spec. In case that checking metadata
needs to be turned on even for Association table, then set 'check_writable_from_metadata'
true value in spec.

Part of: https://fedorahosted.org/freeipa/ticket/5426
---
 install/ui/src/freeipa/association.js | 19 +++++++++++
 install/ui/src/freeipa/field.js       | 64 +++++++++++++++++++++++++----------
 2 files changed, 65 insertions(+), 18 deletions(-)

diff --git a/install/ui/src/freeipa/association.js b/install/ui/src/freeipa/association.js
index 02f990a..7954ddc 100644
--- a/install/ui/src/freeipa/association.js
+++ b/install/ui/src/freeipa/association.js
@@ -827,12 +827,31 @@ IPA.association_table_field = function (spec) {
 
     spec = spec || {};
 
+    /**
+     * Turn off decision whether the field is writable according to metadata.
+     * The source of rights will be only ACLs.
+     *
+     * @property {Boolean}
+     */
+    spec.check_writable_from_metadata = spec.check_writable_from_metadata === undefined ?
+                        false : spec.check_writable_from_metadata;
+
     var that = IPA.field(spec);
 
     that.load = function(data) {
         that.values = that.adapter.load(data);
         that.widget.update(that.values);
         that.widget.unselect_all();
+
+        if (!!that.acl_param) {
+            var record = that.adapter.get_record(data);
+            that.load_writable(record);
+            that.handle_acl();
+        }
+    };
+
+    that.handle_acl = function() {
+        if (!that.writable) that.widget.set_enabled(false);
     };
 
     that.refresh = function() {
diff --git a/install/ui/src/freeipa/field.js b/install/ui/src/freeipa/field.js
index 5df2f6c..76ce253 100644
--- a/install/ui/src/freeipa/field.js
+++ b/install/ui/src/freeipa/field.js
@@ -96,6 +96,16 @@ field.field = IPA.field = function(spec) {
     that.param = spec.param || spec.name;
 
     /**
+     * Some fields needs to skip checking whether they are writable or not
+     * in metadata. It is possible by setting this option to true.
+     * Field example: association_table_field
+     *
+     * @property {string}
+     */
+    that.check_writable_from_metadata = spec.check_writable_from_metadata !== undefined ?
+                spec.check_writable_from_metadata : true;
+
+    /**
      * Entity param which provides access control rights
      *
      * - defaults to `param`
@@ -459,10 +469,43 @@ field.field = IPA.field = function(spec) {
     };
 
     /**
+    * Evaluate if field is writable according to ACL in record and field
+    * configuration. Updates `writable` property.
+    *
+    * Not writable:
+    *
+    * - primary keys
+    * - with 'no_update' metadata flag
+    */
+    that.load_writable_from_metadata = function(writable) {
+        if (that.metadata) {
+            if (that.metadata.primary_key) {
+                writable = false;
+            }
+
+            // In case that field has set always_writable attribute, then
+            // 'no_update' flag is ignored in WebUI. It is done because of
+            // commands like user-{add,remove}-certmap. They operate with user's
+            // attribute, which cannot be changed using user-mod, but only
+            // using command user-{add,remove}-certmap. Therefore it has set
+            // 'no_update' flag, but we need to show 'Add', 'Remove' buttons in
+            // WebUI.
+            if (that.metadata.flags &&
+                array.indexOf(that.metadata.flags, 'no_update') > -1 &&
+                !that.always_writable) {
+                writable = false;
+            }
+        }
+
+        return writable;
+    };
+
+
+    /**
      * Evaluate if field is writable according to ACL in record and field
      * configuration. Updates `writable` property.
      *
-     * Not writable:
+     * Not writable (checked in method that.load_writable_from_metadata()):
      *
      * - primary keys
      * - with 'no_update' metadata flag
@@ -487,23 +530,8 @@ field.field = IPA.field = function(spec) {
             return has;
         }
 
-        if (that.metadata) {
-            if (that.metadata.primary_key) {
-                writable = false;
-            }
-
-            // In case that field has set always_writable attribute, then
-            // 'no_update' flag is ignored in WebUI. It is done because of
-            // commands like user-{add,remove}-certmap. They operate with user's
-            // attribute, which cannot be changed using user-mod, but only
-            // using command user-{add,remove}-certmap. Therefore it has set
-            // 'no_update' flag, but we need to show 'Add', 'Remove' buttons in
-            // WebUI.
-            if (that.metadata.flags &&
-                array.indexOf(that.metadata.flags, 'no_update') > -1 &&
-                !that.always_writable) {
-                writable = false;
-            }
+        if (that.check_writable_from_metadata) {
+            writable = that.load_writable_from_metadata(writable);
         }
 
         if (record && record.attributelevelrights) {

From 4943d29068b23658ae78ad412f728eccf8f282ee Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Wed, 5 Oct 2016 10:27:19 +0200
Subject: [PATCH 04/15] Added optional option in refreshing after modifying
 association table

The 'refresh_option' of association field takes string. This string has to
correspond with field name on details page. In case that the field is present
the value of the field is passed to command as option in following format:

{fieldname: field_value}

Part of: https://fedorahosted.org/freeipa/ticket/5426
---
 install/ui/src/freeipa/association.js | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/install/ui/src/freeipa/association.js b/install/ui/src/freeipa/association.js
index 7954ddc..04b375a 100644
--- a/install/ui/src/freeipa/association.js
+++ b/install/ui/src/freeipa/association.js
@@ -838,6 +838,15 @@ IPA.association_table_field = function (spec) {
 
     var that = IPA.field(spec);
 
+    /**
+     * In case that facet has a state attribute set this is the way how to user
+     * that attribute in refresh command as option in format:
+     * {attributename: attributevalue}.
+     *
+     * @property {String}
+     */
+    that.refresh_attribute = spec.refresh_attribute || '';
+
     that.load = function(data) {
         that.values = that.adapter.load(data);
         that.widget.update(that.values);
@@ -865,14 +874,19 @@ IPA.association_table_field = function (spec) {
         }
 
         var pkey = that.facet.get_pkey();
-        rpc.command({
+        var command = rpc.command({
             entity: that.entity.name,
             method: 'show',
             args: [pkey],
             options: { all: true, rights: true },
             on_success: on_success,
             on_error: on_error
-        }).execute();
+        });
+
+        var additional_option = that.facet.state[that.refresh_attribute];
+        if (additional_option) command.set_option(that.refresh_attribute, additional_option);
+
+        command.execute();
     };
 
     that.widgets_created = function() {

From e77d89b75c0903bbbb08a4bb4b8c4c9901340c84 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Wed, 5 Oct 2016 10:50:08 +0200
Subject: [PATCH 05/15] Add property which allows refresh command to use url
 value

'refresh_attribute' can be set to the name of url parameter name. This parameter with
its value is then passed to refresh command of the details facet.

Part of: https://fedorahosted.org/freeipa/ticket/5426
---
 install/ui/src/freeipa/details.js | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/install/ui/src/freeipa/details.js b/install/ui/src/freeipa/details.js
index 87b355a..d1f2305 100644
--- a/install/ui/src/freeipa/details.js
+++ b/install/ui/src/freeipa/details.js
@@ -539,6 +539,13 @@ exp.details_facet = IPA.details_facet = function(spec, no_init) {
     that.refresh_command_name = spec.refresh_command_name || 'show';
 
     /**
+     * Name of url argument which will be added to refresh RPC command as option.
+     *
+     * @property {string}
+     */
+    that.refresh_attribute = spec.refresh_attribute || null;
+
+    /**
      * Name of update command
      *
      * - defaults to 'mod'
@@ -885,6 +892,23 @@ exp.details_facet = IPA.details_facet = function(spec, no_init) {
     };
 
     /**
+     * Takes url argument which is named arg_name and its value. Creates object
+     * from this infromation {arg_name: arg_name_value} and attach it as option
+     * to command.
+     *
+     * @protected
+     * @param {rpc.command} command
+     * @param {string} argumnent name
+     */
+    that.add_url_arg_to_command = function(command, arg_name) {
+        var additional_opt = {};
+        command.options = command.options || {};
+
+        additional_opt[arg_name] = that.state[arg_name];
+        $.extend(command.options, additional_opt);
+    };
+
+    /**
      * Create update command based on field part of update info
      * @protected
      * @param {details.update_info} update_info
@@ -1034,6 +1058,10 @@ exp.details_facet = IPA.details_facet = function(spec, no_init) {
             command.args = that.get_pkeys();
         }
 
+        if (that.refresh_attribute) {
+            that.add_url_arg_to_command(command, that.refresh_attribute);
+        }
+
         return command;
     };
 

From 77658c8770e7776749465df99d768de0e2256014 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Wed, 5 Oct 2016 11:44:13 +0200
Subject: [PATCH 06/15] Add possibility to pass url parameter to update command
 of details page

'update_attribute' can contain a name of field in details page. In that case the value
of the field with field name will be appended to the update command options.

Part of: https://fedorahosted.org/freeipa/ticket/5426
---
 install/ui/src/freeipa/details.js | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/install/ui/src/freeipa/details.js b/install/ui/src/freeipa/details.js
index d1f2305..a38b3a3 100644
--- a/install/ui/src/freeipa/details.js
+++ b/install/ui/src/freeipa/details.js
@@ -554,6 +554,13 @@ exp.details_facet = IPA.details_facet = function(spec, no_init) {
     that.update_command_name = spec.update_command_name || 'mod';
 
     /**
+     * Name of url argument which will be added to update RPC command as option.
+     *
+     * @property {string}
+     */
+    that.update_attribute = spec.update_attribute || null;
+
+    /**
      * Command mode
      * Command mode determines how update information on update is collected.
      * There are two modes:
@@ -929,6 +936,10 @@ exp.details_facet = IPA.details_facet = function(spec, no_init) {
             options: options
         });
 
+        if (that.update_attribute) {
+            that.add_url_arg_to_command(command, that.update_attribute);
+        }
+
         //set command options
         that.add_fields_to_command(update_info, command);
 

From 2e4f6c1ffbb3461728b0b3202edf99ce7c786242 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Wed, 5 Oct 2016 11:52:10 +0200
Subject: [PATCH 07/15] Extend _show command after _find command in table
 facets

Allow pagination to table facets which needs to call _show on all rows
with additional parameter. 'show_command_additional_attr' can be set to any
attribute from result of _find command. This attribute is taken with its value
and added to options of _each command for each row.

Part of: https://fedorahosted.org/freeipa/ticket/5426
---
 install/ui/src/freeipa/facet.js | 42 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 40 insertions(+), 2 deletions(-)

diff --git a/install/ui/src/freeipa/facet.js b/install/ui/src/freeipa/facet.js
index ab5af1f..bbd47b6 100644
--- a/install/ui/src/freeipa/facet.js
+++ b/install/ui/src/freeipa/facet.js
@@ -1847,6 +1847,14 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
     that.search_all_entries = spec.search_all_entries;
 
     /**
+     * Attribute from *_find command which will be used in batch *_show command
+     * which is called for each row.
+     *
+     * @property {String}
+     */
+    that.show_command_additional_attr = spec.show_command_additional_attr || null;
+
+    /**
      * Member resolution(no_member: true ) in rpc request is skipped by default
      * to improve performance of getting data.
      *
@@ -2144,7 +2152,7 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
 
         // get the complete records
         that.get_records(
-            records_map.keys,
+            records_map,
             function(data, text_status, xhr) {
                 var results = data.result.results;
                 for (var i=0; i<records_map.length; i++) {
@@ -2227,7 +2235,9 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
      * @param {Function} on_success command success handler
      * @param {Function} on_failure command error handler
      */
-    that.create_get_records_command = function(pkeys, on_success, on_error) {
+    that.create_get_records_command = function(records, on_success, on_error) {
+
+        var pkeys = records.keys;
 
         var batch = rpc.batch_command({
             name: that.get_records_command_name(),
@@ -2244,6 +2254,10 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
                 args: [pkey]
             });
 
+            if (that.show_command_additional_attr) {
+                that.extend_get_records_command(command, records, pkey);
+            }
+
             if (!that.always_request_members && that.table.entity.has_members()) {
                 command.set_options({no_members: true});
             }
@@ -2254,6 +2268,24 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
         return batch;
     };
 
+
+    /**
+     * This allows to use pagination in situations when for loading whole search
+     * page you need *_show command with
+     *
+     */
+    that.extend_get_records_command = function(command, records, pkey) {
+        var record = records.get(pkey);
+        var item = record[that.show_command_additional_attr];
+        if (item) {
+            var temp_option = {};
+            temp_option[that.show_command_additional_attr] = item;
+            command.set_options(temp_option);
+        }
+    };
+
+
+
     /**
      * Execute command for obtaining complete records
      *
@@ -2431,6 +2463,12 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
         }
     };
 
+    that.fetch_records = function() {
+        if (!that.table) return null;
+
+        return that.table.records;
+    };
+
     if (!no_init) that.init_table_columns();
 
     that.table_facet_create_get_records_command = that.create_get_records_command;

From 0234f88a08362a593cbefe6dcb0613032ee25a91 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Wed, 5 Oct 2016 12:00:33 +0200
Subject: [PATCH 08/15] Possibility to set list of table attributes which will
 be added to _del command

'additional_table_attrs' can contain array of names of columns. Value from each
column with its name will be added to the batch _del command. in case that
the column with set name does not exists - the name is skipped.

Part of: https://fedorahosted.org/freeipa/ticket/5426
---
 install/ui/src/freeipa/search.js | 36 +++++++++++++++++++++++++++++++++++-
 1 file changed, 35 insertions(+), 1 deletion(-)

diff --git a/install/ui/src/freeipa/search.js b/install/ui/src/freeipa/search.js
index 6820f54..ee00522 100644
--- a/install/ui/src/freeipa/search.js
+++ b/install/ui/src/freeipa/search.js
@@ -350,6 +350,15 @@ IPA.search_deleter_dialog = function(spec) {
     var that = IPA.deleter_dialog(spec);
     that.pkey_prefix = spec.pkey_prefix || [];
 
+    /**
+     * List of attributes from table from search facet, which
+     * are added to remove command as options. In case that there is not column
+     * with this name, then the option is skipped
+     *
+     * @property {String}
+     */
+    that.additional_table_attrs = spec.additional_table_attrs || [];
+
     that.create_command = function() {
         var batch = rpc.batch_command({
             error_message: '@i18n:search.partial_delete',
@@ -369,7 +378,8 @@ IPA.search_deleter_dialog = function(spec) {
                 for (var key in value) {
                     if (value.hasOwnProperty(key)) {
                         if (key === 'pkey'){
-                            command.add_arg(value[key]);
+                            value = value[key];
+                            command.add_arg(value);
                         } else {
                             command.set_option(key, value[key]);
                         }
@@ -379,6 +389,11 @@ IPA.search_deleter_dialog = function(spec) {
                 command.add_arg(value);
             }
 
+            var add_attrs = that.additional_table_attrs;
+            if (add_attrs && add_attrs.length && add_attrs.length > 0) {
+                command = that.extend_command(command, add_attrs, value);
+            }
+
             batch.add_command(command);
         }
 
@@ -405,6 +420,25 @@ IPA.search_deleter_dialog = function(spec) {
         batch.execute();
     };
 
+    that.extend_command = function(command, add_attrs, pkey) {
+        var records = that.facet.fetch_records();
+        var pkey_name = that.entity.metadata.primary_key;
+
+        for (var i=0,l=records.length; i<l; i++) {
+            var record = records[i];
+            var curr_pkey = record[pkey_name][0];
+            if (curr_pkey && curr_pkey === pkey) {
+                for (var j=0,k=add_attrs.length; j<k; j++) {
+                    var attr = add_attrs[j];
+                    var val = record[attr];
+                    if (val) command.set_option(attr, val);
+                }
+            }
+        }
+
+        return command;
+    };
+
     that.search_deleter_dialog_create_command = that.create_command;
 
     return that;

From b928b51169257a58b68ff82550db60bbe1ddcbb8 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Wed, 5 Oct 2016 17:37:47 +0200
Subject: [PATCH 09/15] Add possibility to hide only one tab in sidebar

Removes item selected by name attribute from sidebar

Part of: https://fedorahosted.org/freeipa/ticket/5426
---
 install/ui/src/freeipa/facet.js | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/install/ui/src/freeipa/facet.js b/install/ui/src/freeipa/facet.js
index bbd47b6..c3d0b49 100644
--- a/install/ui/src/freeipa/facet.js
+++ b/install/ui/src/freeipa/facet.js
@@ -1644,6 +1644,20 @@ exp.FacetGroupsWidget = declare([], {
         return el;
     },
 
+    hide_tab: function(tab_name) {
+        var tab = this.get_tab_el(tab_name);
+        if (tab) tab.css('display', 'none');
+    },
+
+    show_tab: function(tab_name) {
+        var tab = this.get_tab_el(tab_name);
+        if (tab) tab.css('display', '');
+    },
+
+    get_tab_el: function(tab_name) {
+        return this.tab_els[tab_name];
+    },
+
     on_click: function(facet) {
         if (this.facet.get_pkeys) {
             var pkeys = this.facet.get_pkeys();

From 44c18bf30a7a5b0c0f7a711327035063bed7f220 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Sun, 12 Mar 2017 22:46:06 +0100
Subject: [PATCH 10/15] WebUI: search facet's default actions might be
 overriden

While defining search facet and adding custom actions with the same name
as default actions in search facet. Custom actions will be used and their
definition will override default actions.

Part of:https://fedorahosted.org/freeipa/ticket/5426
---
 install/ui/src/freeipa/search.js | 31 ++++++++++++++++++++++++++-----
 1 file changed, 26 insertions(+), 5 deletions(-)

diff --git a/install/ui/src/freeipa/search.js b/install/ui/src/freeipa/search.js
index ee00522..e2fbf44 100644
--- a/install/ui/src/freeipa/search.js
+++ b/install/ui/src/freeipa/search.js
@@ -37,12 +37,33 @@ var exp = {};
 
 exp.search_facet_control_buttons_pre_op = function(spec, context) {
 
-    spec.actions = spec.actions || [];
-    spec.actions.unshift(
-        'refresh',
-        'batch_remove',
-        'add');
+    var override_actions = function(cust_acts, def_acts) {
+        if (!cust_acts) return def_acts;
 
+        var new_default_actions = [];
+        for (var i=0, l=def_acts.length; i<l; i++) {
+            var d_action = def_acts[i];
+
+            var chosen_action = d_action;
+
+            for (var k=0, j=cust_acts.length; k<j; k++) {
+                var custom_act = cust_acts[k];
+                if (custom_act === d_action || (custom_act.$type && custom_act.$type === d_action)) {
+                    chosen_action = custom_act;
+                    break;
+                }
+            }
+
+            new_default_actions.push(chosen_action);
+        }
+
+        return new_default_actions;
+    };
+
+    var default_actions = ['refresh', 'batch_remove', 'add'];
+    var merged_actions = override_actions(spec.custom_actions, default_actions);
+
+    spec.actions = merged_actions.concat(spec.actions);
     spec.control_buttons = spec.control_buttons || [];
 
     if (!spec.no_update) {

From cd1e47d24dff8a14c20eb8d50fb4f28293567a8e Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 9 Dec 2016 13:08:05 +0100
Subject: [PATCH 11/15] WebUI: allow to show rows with same pkey in tables

Allows to show rows which have the same primary key. Used in Vault.

https://fedorahosted.org/freeipa/ticket/5426
---
 install/ui/src/freeipa/association.js | 24 ++++++++++++---
 install/ui/src/freeipa/automember.js  |  5 ++--
 install/ui/src/freeipa/dns.js         |  5 +++-
 install/ui/src/freeipa/facet.js       | 56 +++++++++++++++++++++++++----------
 install/ui/src/freeipa/hbactest.js    | 16 +++++++---
 install/ui/src/freeipa/service.js     | 10 +++++--
 install/ui/src/freeipa/topology.js    |  4 +--
 7 files changed, 90 insertions(+), 30 deletions(-)

diff --git a/install/ui/src/freeipa/association.js b/install/ui/src/freeipa/association.js
index 04b375a..27a76a5 100644
--- a/install/ui/src/freeipa/association.js
+++ b/install/ui/src/freeipa/association.js
@@ -1042,6 +1042,7 @@ exp.association_facet = IPA.association_facet = function (spec, no_init) {
     that.facet_group = spec.facet_group;
 
     that.read_only = spec.read_only;
+    that.show_values_with_dup_key = spec.show_values_with_dup_key || false;
 
     that.associator = spec.associator || IPA.bulk_associator;
     that.add_method = spec.add_method || 'add_member';
@@ -1318,6 +1319,7 @@ exp.association_facet = IPA.association_facet = function (spec, no_init) {
     that.get_records_map = function(data) {
 
         var records_map = $.ordered_map();
+        var pkeys_map = $.ordered_map();
         var association_name = that.get_attribute_name();
         var pkey_name = that.managed_entity.metadata.primary_key;
 
@@ -1326,10 +1328,18 @@ exp.association_facet = IPA.association_facet = function (spec, no_init) {
             var pkey = pkeys[i];
             var record = {};
             record[pkey_name] = pkey;
-            records_map.put(pkey, record);
+            var compound_pkey = pkey;
+            if (that.show_values_with_dup_key) {
+                compound_pkey = pkey + i;
+            }
+            records_map.put(compound_pkey, record);
+            pkeys_map.put(compound_pkey, pkey);
         }
 
-        return records_map;
+        return {
+            records_map: records_map,
+            pkeys_map: pkeys_map
+        };
     };
 
     that.refresh = function() {
@@ -1488,16 +1498,22 @@ exp.attribute_facet = IPA.attribute_facet = function(spec, no_init) {
     that.get_records_map = function(data) {
 
         var records_map = $.ordered_map();
+        var pkeys_map = $.ordered_map();
         var pkeys = data.result.result[that.attribute];
 
         for (var i=0; pkeys && i<pkeys.length; i++) {
             var pkey = pkeys[i];
             var record = {};
             record[that.attribute] = pkey;
-            records_map.put(pkey, record);
+            var compound_pkey = pkey + i;
+            records_map.put(compound_pkey, record);
+            pkeys_map.put(compound_pkey, pkey);
         }
 
-        return records_map;
+        return {
+            records_map: records_map,
+            pkeys_map: pkeys_map
+        };
     };
 
     that.refresh = function() {
diff --git a/install/ui/src/freeipa/automember.js b/install/ui/src/freeipa/automember.js
index 0a73967..a1d56a1 100644
--- a/install/ui/src/freeipa/automember.js
+++ b/install/ui/src/freeipa/automember.js
@@ -167,9 +167,10 @@ IPA.automember.rule_search_facet = function(spec) {
         return name;
     };
 
-    that.create_get_records_command = function(pkeys, on_success, on_error) {
+    that.create_get_records_command = function(records, pkeys, on_success, on_error) {
 
-        var batch = that.table_facet_create_get_records_command(pkeys, on_success, on_error);
+        var batch = that.table_facet_create_get_records_command(records, pkeys,
+                                                        on_success, on_error);
 
         for (var i=0; i<batch.commands.length; i++) {
             var command = batch.commands[i];
diff --git a/install/ui/src/freeipa/dns.js b/install/ui/src/freeipa/dns.js
index df6dbbd..1ea3aaa 100644
--- a/install/ui/src/freeipa/dns.js
+++ b/install/ui/src/freeipa/dns.js
@@ -875,7 +875,9 @@ IPA.dns.record_search_facet = function(spec) {
 
     var that = IPA.nested_search_facet(spec);
 
-    that.get_records = function(pkeys, on_success, on_error) {
+    that.get_records = function(records, pkeys_list, on_success, on_error) {
+
+        var pkeys = pkeys_list.keys;
 
         var batch = rpc.batch_command({
             name: that.get_records_command_name(),
@@ -887,6 +889,7 @@ IPA.dns.record_search_facet = function(spec) {
 
         for (var i=0; i<pkeys.length; i++) {
             var pkey = pkeys[i];
+            var call_pkey = pkeys_list.get(pkey);
 
             var command = rpc.command({
                 entity: that.table.entity.name,
diff --git a/install/ui/src/freeipa/facet.js b/install/ui/src/freeipa/facet.js
index c3d0b49..2bf5b96 100644
--- a/install/ui/src/freeipa/facet.js
+++ b/install/ui/src/freeipa/facet.js
@@ -1835,6 +1835,14 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
     var that = IPA.facet(spec, no_init);
 
     /**
+     * Sets whether table on the facet will or will not show items with the
+     * same key.
+     *
+     * @property {boolean}
+     */
+    that.show_values_with_dup_key = spec.show_values_with_dup_key || false;
+
+    /**
      * Names of additional row attributes which will be send to another facet
      * during navigation as URL parameters.
      *
@@ -2074,11 +2082,13 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
      *
      * @protected
      * @param {Object} data RPC command data
-     * @return {ordered_map} record map
+     * @return {Object} ordered_maps with records and with pkeys. keys
+     *                              are composed from pkey and index.
      */
     that.get_records_map = function(data) {
 
         var records_map = $.ordered_map();
+        var pkeys_map = $.ordered_map();
 
         var result = data.result.result;
         var pkey_name = that.managed_entity.metadata.primary_key ||
@@ -2089,11 +2099,21 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
             var record = result[i];
             var pkey = adapter.load(record, pkey_name)[0];
             if (that.filter_records(records_map, pkey, record)) {
-                records_map.put(pkey, record);
+                // This solution allows to show tables where are the same
+                // primary keys. (i.e. {User|Service} Vaults)
+                var compound_pkey = pkey;
+                if (that.show_values_with_dup_key) {
+                    compound_pkey = pkey + i;
+                }
+                records_map.put(compound_pkey, record);
+                pkeys_map.put(compound_pkey, pkey);
             }
         }
 
-        return records_map;
+        return {
+            records_map: records_map,
+            pkeys_map: pkeys_map
+        };
     };
 
     /**
@@ -2110,7 +2130,9 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
     that.load_page = function(data) {
 
         // get primary keys (and the complete records if search_all_entries is true)
-        var records_map = that.get_records_map(data);
+        var records = that.get_records_map(data);
+        var records_map = records.records_map;
+        var pkeys_map = records.pkeys_map;
 
         var total = records_map.length;
         that.table.total_pages = total ? Math.ceil(total / that.table.page_length) : 1;
@@ -2146,11 +2168,11 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
 
         // sort map based on primary keys
         if (that.sort_enabled) {
-            records_map = records_map.sort();
+            pkeys_map = pkeys_map.sort();
         }
 
         // trim map leaving the entries visible in the current page only
-        records_map = records_map.slice(start-1, end);
+        pkeys_map = pkeys_map.slice(start-1, end);
 
         var columns = that.table.columns.values;
         if (columns.length == 1) { // show primary keys only
@@ -2167,16 +2189,19 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
         // get the complete records
         that.get_records(
             records_map,
+            pkeys_map,
             function(data, text_status, xhr) {
                 var results = data.result.results;
-                for (var i=0; i<records_map.length; i++) {
-                    var pkey = records_map.keys[i];
+                var show_records_map = $.ordered_map();
+                for (var i=0; i<pkeys_map.length; i++) {
+                    var pkey = pkeys_map.keys[i];
                     var record = records_map.get(pkey);
                     // merge the record obtained from the refresh()
                     // with the record obtained from get_records()
                     $.extend(record, results[i].result);
+                    show_records_map.put(pkey, record);
                 }
-                that.load_records(records_map.values);
+                that.load_records(show_records_map.values);
             },
             function(xhr, text_status, error_thrown) {
                 that.load_records([]);
@@ -2249,9 +2274,9 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
      * @param {Function} on_success command success handler
      * @param {Function} on_failure command error handler
      */
-    that.create_get_records_command = function(records, on_success, on_error) {
+    that.create_get_records_command = function(records, pkeys_list, on_success, on_error) {
 
-        var pkeys = records.keys;
+        var pkeys = pkeys_list.keys;
 
         var batch = rpc.batch_command({
             name: that.get_records_command_name(),
@@ -2261,11 +2286,12 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
 
         for (var i=0; i<pkeys.length; i++) {
             var pkey = pkeys[i];
+            var call_pkey = pkeys_list.get(pkey);
 
             var command = rpc.command({
                 entity: that.table.entity.name,
                 method: 'show',
-                args: [pkey]
+                args: [call_pkey]
             });
 
             if (that.show_command_additional_attr) {
@@ -2304,13 +2330,13 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
      * Execute command for obtaining complete records
      *
      * @protected
-     * @param {Array.<string>} pkeys primary keys
+     * @param records_map of all records
      * @param {Function} on_success command success handler
      * @param {Function} on_failure command error handler
      */
-    that.get_records = function(pkeys, on_success, on_error) {
+    that.get_records = function(records, pkeys, on_success, on_error) {
 
-        var batch = that.create_get_records_command(pkeys, on_success, on_error);
+        var batch = that.create_get_records_command(records, pkeys, on_success, on_error);
 
         batch.execute();
     };
diff --git a/install/ui/src/freeipa/hbactest.js b/install/ui/src/freeipa/hbactest.js
index 9ac4e82..83e6093 100644
--- a/install/ui/src/freeipa/hbactest.js
+++ b/install/ui/src/freeipa/hbactest.js
@@ -706,12 +706,15 @@ IPA.hbac.test_run_facet = function(spec) {
     that.get_records_map = function(data) {
 
         var records_map = $.ordered_map();
+        var pkeys_map = $.ordered_map();
 
         var matched = data.result.matched;
         if (that.show_matched && matched) {
             for (var i=0; i<matched.length; i++) {
                 var pkey = matched[i];
-                records_map.put(pkey, { matched: true });
+                var compound_pkey = pkey + i;
+                records_map.put(compound_pkey, { matched: true });
+                pkeys_map.put(compound_pkey, pkey);
             }
         }
 
@@ -719,11 +722,16 @@ IPA.hbac.test_run_facet = function(spec) {
         if (that.show_unmatched && notmatched) {
             for (i=0; i<notmatched.length; i++) {
                 pkey = notmatched[i];
-                records_map.put(pkey, { matched: false });
+                compound_pkey = pkey + i;
+                records_map.put(compound_pkey, { matched: false });
+                pkeys_map.put(compound_pkey, pkey);
             }
         }
 
-        return records_map;
+        return {
+            records_map: records_map,
+            pkeys_map: pkeys_map
+        };
     };
 
     that.get_records_command_name = function() {
@@ -811,4 +819,4 @@ exp.register = function() {
 phases.on('registration', exp.register);
 
 return exp;
-});
\ No newline at end of file
+});
diff --git a/install/ui/src/freeipa/service.js b/install/ui/src/freeipa/service.js
index 279f842..752ff98 100644
--- a/install/ui/src/freeipa/service.js
+++ b/install/ui/src/freeipa/service.js
@@ -430,6 +430,7 @@ IPA.service.search_facet = function(spec) {
     that.get_records_map = function(data) {
 
         var records_map = $.ordered_map();
+        var pkeys_map = $.ordered_map();
 
         var result = data.result.result;
         var pkey_name = that.managed_entity.metadata.primary_key ||
@@ -443,11 +444,16 @@ IPA.service.search_facet = function(spec) {
                 pkey = adapter.load(record, that.alternative_pkey)[0];
             }
             if (that.filter_records(records_map, pkey, record)) {
-                records_map.put(pkey, record);
+                var compound_pkey = pkey + i;
+                records_map.put(compound_pkey, record);
+                pkeys_map.put(compound_pkey, pkey);
             }
         }
 
-        return records_map;
+        return {
+            records_map: records_map,
+            pkeys_map: pkeys_map
+        };
     };
 
     return that;
diff --git a/install/ui/src/freeipa/topology.js b/install/ui/src/freeipa/topology.js
index 7559d78..e98cb1e 100644
--- a/install/ui/src/freeipa/topology.js
+++ b/install/ui/src/freeipa/topology.js
@@ -490,7 +490,7 @@ topology.servers_search_facet = function(spec, no_init) {
 
     var that = IPA.search_facet(spec);
 
-    that.create_get_records_command = function(pkeys, on_success, on_error) {
+    that.create_get_records_command = function(records, pkeys, on_success, on_error) {
 
         var on_success_extended = function(data, text_status, xhr) {
             // Call original on_success handler
@@ -560,7 +560,7 @@ topology.servers_search_facet = function(spec, no_init) {
             dialog.open();
         };
 
-        var batch = that.table_facet_create_get_records_command(pkeys,
+        var batch = that.table_facet_create_get_records_command(records, pkeys,
                                                 on_success_extended, on_error);
 
         return batch;

From a08e7f7222e8348fcf485d2126bdbd9cef0ff79e Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Wed, 5 Oct 2016 12:06:27 +0200
Subject: [PATCH 12/15] 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
---
 install/ui/src/freeipa/app.js                  |   3 +-
 install/ui/src/freeipa/ipa.js                  |  12 +
 install/ui/src/freeipa/navigation/menu_spec.js |  53 +-
 install/ui/src/freeipa/vault.js                | 819 +++++++++++++++++++++++++
 install/ui/test/data/ipa_init.json             |  25 +
 ipaserver/plugins/internal.py                  |  38 ++
 6 files changed, 948 insertions(+), 2 deletions(-)
 create mode 100644 install/ui/src/freeipa/vault.js

diff --git a/install/ui/src/freeipa/app.js b/install/ui/src/freeipa/app.js
index 5e70dba..093737b 100644
--- a/install/ui/src/freeipa/app.js
+++ b/install/ui/src/freeipa/app.js
@@ -51,11 +51,12 @@ define([
     './selinux',
     './serverconfig',
     './service',
+    './stageuser',
     './sudo',
     './trust',
     './topology',
     './user',
-    './stageuser',
+    './vault',
     'dojo/domReady!'
 ],function(app_container) {
     return app_container;
diff --git a/install/ui/src/freeipa/ipa.js b/install/ui/src/freeipa/ipa.js
index 46b033a..0ddbd07 100644
--- a/install/ui/src/freeipa/ipa.js
+++ b/install/ui/src/freeipa/ipa.js
@@ -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();
     };
 
diff --git a/install/ui/src/freeipa/navigation/menu_spec.js b/install/ui/src/freeipa/navigation/menu_spec.js
index 8d13da1..4f78e4b 100644
--- a/install/ui/src/freeipa/navigation/menu_spec.js
+++ b/install/ui/src/freeipa/navigation/menu_spec.js
@@ -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
+                }
+            ]
+        }
     ]
 };
 
diff --git a/install/ui/src/freeipa/vault.js b/install/ui/src/freeipa/vault.js
new file mode 100644
index 0000000..b764bfb
--- /dev/null
+++ b/install/ui/src/freeipa/vault.js
@@ -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;
+});
diff --git a/install/ui/test/data/ipa_init.json b/install/ui/test/data/ipa_init.json
index 820f7aa..6f223e1 100644
--- a/install/ui/test/data/ipa_init.json
+++ b/install/ui/test/data/ipa_init.json
@@ -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": {
diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py
index 5e70a79..9fa1b6d 100644
--- a/ipaserver/plugins/internal.py
+++ b/ipaserver/plugins/internal.py
@@ -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"),

From ef60acae5464384d9a79cb67da6c45e414187c6f Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 25 Oct 2016 16:53:14 +0200
Subject: [PATCH 13/15] TESTS: Add support for KRA in ui_driver

https://fedorahosted.org/freeipa/ticket/5426
---
 ipatests/test_webui/ui_driver.py | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/ipatests/test_webui/ui_driver.py b/ipatests/test_webui/ui_driver.py
index 4ce3668..62dcced 100644
--- a/ipatests/test_webui/ui_driver.py
+++ b/ipatests/test_webui/ui_driver.py
@@ -69,6 +69,7 @@
     'IPA_NO_CA': 'no_ca',
     'IPA_NO_DNS': 'no_dns',
     'IPA_HAS_TRUSTS': 'has_trusts',
+    'IPA_HAS_KRA': 'has_kra',
     'IPA_HOST_CSR_PATH': 'host_csr_path',
     'IPA_SERVICE_CSR_PATH': 'service_csr_path',
     'AD_DOMAIN': 'ad_domain',
@@ -283,6 +284,12 @@ def has_trusts(self):
         """
         return self.config.get('has_trusts')
 
+    def has_kra(self):
+        """
+        FreeIPA server was installed with Kra.
+        """
+        return self.config.get('has_kra')
+
     def has_active_request(self):
         """
         Check if there is running AJAX request

From 66e897c6fe8e3491e8bf13dbaf6d13fb5fa089a5 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 25 Oct 2016 16:54:28 +0200
Subject: [PATCH 14/15] TESTS: Add support for sidebar with facets

Part of: https://fedorahosted.org/freeipa/ticket/5426
---
 ipatests/test_webui/ui_driver.py | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/ipatests/test_webui/ui_driver.py b/ipatests/test_webui/ui_driver.py
index 62dcced..23d8127 100644
--- a/ipatests/test_webui/ui_driver.py
+++ b/ipatests/test_webui/ui_driver.py
@@ -451,8 +451,18 @@ def switch_to_facet(self, name):
         Click on tab with given name
         """
         facet = self.get_facet()
-        s = "div.facet-tabs li[name='%s'] a" % name
-        link = self.find(s, By.CSS_SELECTOR, facet, strict=True)
+        tabs = "div.facet-tabs"
+        sidebar = "div.sidebar-pf"
+
+        facets_container = self.find(tabs, By.CSS_SELECTOR, facet)
+
+        # handle sidebar instead of facet-tabs
+        # the webui facet can have only the facet-tabs OR sidebar, not both
+        if not facets_container:
+            facets_container = self.find(sidebar, By.CSS_SELECTOR, facet)
+
+        s = "li[name='%s'] a" % name
+        link = self.find(s, By.CSS_SELECTOR, facets_container, strict=True)
         link.click()
         # double wait because of facet's paging
         self.wait_for_request(0.5)

From d563e9c06e09be5611d4245f069553540f0754d6 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 25 Oct 2016 13:40:24 +0200
Subject: [PATCH 15/15] TESTS WebUI: Vaults management

Bunch of tests for WebUI Vault Management.

Covers:
Adding vaults
Modifying vaults
Adding members and owners to all types of vaults

https://fedorahosted.org/freeipa/ticket/5426
---
 install/ui/src/freeipa/vault.js   |  27 +++++-
 ipatests/test_webui/data_vault.py |  63 +++++++++++++
 ipatests/test_webui/test_vault.py | 184 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 273 insertions(+), 1 deletion(-)
 create mode 100644 ipatests/test_webui/data_vault.py
 create mode 100644 ipatests/test_webui/test_vault.py

diff --git a/install/ui/src/freeipa/vault.js b/install/ui/src/freeipa/vault.js
index b764bfb..b5cdc81 100644
--- a/install/ui/src/freeipa/vault.js
+++ b/install/ui/src/freeipa/vault.js
@@ -614,6 +614,7 @@ vault.custom_adder_dialog = function(spec) {
 
         var facet_name = that.entity.facet.name;
         facet_name = facet_name.substr(0, facet_name.indexOf('_'));
+        if (facet_name === "") facet_name = 'user';
 
         var type_f = that.fields.get_field('type');
         type_f.set_pristine_value([facet_name]);
@@ -621,6 +622,27 @@ vault.custom_adder_dialog = function(spec) {
         if (IPA.is_selfservice) type_f.set_writable(false);
     };
 
+    that.on_success = function(data) {
+        var result = data.result.result;
+        var my_vaults = that.entity.get_facet('search');
+
+        function update_facet(name) {
+            var fa = that.entity.get_facet(name);
+            fa.set_expired_flag();
+        }
+
+        if (result.service) {
+            update_facet('service_search');
+        } else if (result.shared) {
+            update_facet('shared_search');
+        } else {
+            update_facet('user_search');
+            my_vaults.set_expired_flag();
+        }
+    };
+
+    that.added.attach(that.on_success);
+
     return that;
 };
 
@@ -746,7 +768,10 @@ var make_vaultconfig_spec = function() {
                 check_rights: false,
                 no_update: true,
                 fields: [
-                    'kra_server_server',
+                    {
+                        $type: "multivalued",
+                        name: 'kra_server_server'
+                    },
                     {
                         $type: 'textarea',
                         name: 'transport_cert',
diff --git a/ipatests/test_webui/data_vault.py b/ipatests/test_webui/data_vault.py
new file mode 100644
index 0000000..fa51ffe
--- /dev/null
+++ b/ipatests/test_webui/data_vault.py
@@ -0,0 +1,63 @@
+# Authors:
+#   Pavel Vomacka <pvoma...@redhat.com>
+#
+# Copyright (C) 2016  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ENTITY = 'vault'
+
+PKEY = 'itest-user-vault'
+DATA = {
+    'pkey': PKEY,
+    'facet': 'user_search',
+    'add': [
+        ('radio', 'type', 'user'),
+        ('textbox', 'cn', PKEY),
+        ('textbox', 'description', 'test-desc')
+    ],
+    'mod': [
+        ('textbox', 'description', 'test-desc-mod'),
+    ],
+}
+
+PKEY2 = 'itest-service-vault'
+DATA2 = {
+    'pkey': PKEY2,
+    'facet': 'service_search',
+    'add': [
+        ('radio', 'type', 'service'),
+        # service
+        ('textbox', 'cn', PKEY2),
+        ('textbox', 'description', 'test-desc')
+    ],
+    'mod': [
+        ('textbox', 'description', 'test-desc-mod'),
+    ],
+}
+
+PKEY3 = 'itest-shared-vault'
+DATA3 = {
+    'pkey': PKEY3,
+    'facet': 'shared_search',
+    'add': [
+        ('radio', 'type', 'shared'),
+        ('textbox', 'cn', PKEY3),
+        ('textbox', 'description', 'test-desc')
+    ],
+    'mod': [
+        ('textbox', 'description', 'test-desc-mod'),
+    ],
+}
diff --git a/ipatests/test_webui/test_vault.py b/ipatests/test_webui/test_vault.py
new file mode 100644
index 0000000..1c094e6
--- /dev/null
+++ b/ipatests/test_webui/test_vault.py
@@ -0,0 +1,184 @@
+# Authors:
+#   Pavel Vomacka <pvoma...@redhat.com>
+#
+# Copyright (C) 2013  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Vault tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+from ipatests.test_webui.ui_driver import screenshot
+import ipatests.test_webui.data_vault as vault
+import ipatests.test_webui.data_user as user
+import ipatests.test_webui.data_group as group
+import pytest
+
+
+@pytest.mark.tier1
+class vault_tasks(UI_driver):
+
+    def prep_service_data(self):
+
+        host = self.config.get('ipa_server')
+        realm = self.config.get('ipa_realm')
+        pkey = 'itest'
+
+        return {
+            'entity': 'service',
+            'pkey': '%s/%s@%s' % (pkey, host, realm),
+            'add': [
+                ('textbox', 'service', pkey),
+                ('combobox', 'host', host)
+            ]
+        }
+
+    def prepare_vault_service_data(self, data):
+        s_data = self.prep_service_data()
+        service = s_data['pkey']
+
+        serv_field = [('combobox', 'service', service)]
+
+        data['add'].extend(serv_field)
+
+    def prepare_vault_user_data(self, data, user='admin'):
+        user_field = [('combobox', 'username', user)]
+
+        data['add'].extend(user_field)
+
+
+@pytest.mark.tier1
+class test_vault(vault_tasks):
+
+    def setup(self, *args, **kwargs):
+        super(test_vault, self).setup(*args, **kwargs)
+        if not self.has_kra():
+            self.skip('KRA not configured')
+
+    @screenshot
+    def test_crud(self):
+        """
+        Basic basic CRUD: user vault
+        """
+        self.init_app()
+        self.prepare_vault_user_data(vault.DATA)
+        self.basic_crud(vault.ENTITY, vault.DATA)
+
+    @screenshot
+    def test_add_service_vault(self):
+        """
+        Add Service vault
+        """
+        self.init_app()
+
+        # Add itest service
+        s_data = self.prep_service_data()
+        self.add_record(s_data['entity'], s_data)
+
+        self.prepare_vault_service_data(vault.DATA2)
+
+        # Add and remove service vault
+        self.add_record(vault.ENTITY, vault.DATA2, facet=vault.DATA2['facet'],
+                        delete=True)
+
+        # Remove test service
+        self.navigate_to_entity(s_data['entity'])
+        self.delete_record(s_data['pkey'])
+
+    @screenshot
+    def test_add_shared_vault(self):
+        """
+        Add Shared vault
+        """
+        self.init_app()
+
+        # Add shared vault
+        self.add_record(vault.ENTITY, vault.DATA3, facet=vault.DATA3['facet'],
+                        delete=True)
+
+    @screenshot
+    def test_member_owner_vault(self):
+        """
+        Add User Vault and try to add member and owner
+        """
+        def fill_tables():
+            self.add_table_associations('member_user', [user.PKEY])
+            self.add_table_associations('member_group', [group.PKEY])
+            self.add_table_associations('member_service', [s_data['pkey']])
+            self.add_table_associations('owner_user', [user.PKEY])
+            self.add_table_associations('owner_group', [group.PKEY])
+            self.add_table_associations('owner_service', [s_data['pkey']])
+
+        # Add user
+        self.init_app()
+        self.add_record(user.ENTITY, user.DATA)
+
+        # Prepare items - user already exists
+        s_data = self.prep_service_data()
+        self.add_record(s_data['entity'], s_data)
+        self.add_record(group.ENTITY, group.DATA)
+
+        # USER
+        # Add user vault
+        self.add_record(vault.ENTITY, vault.DATA, facet='user_search')
+
+        # Navigate to record
+        self.navigate_to_record(vault.DATA['pkey'])
+
+        # Try add values into table
+        fill_tables()
+
+        # Remove user vault record
+        self.navigate_to_entity(vault.ENTITY, vault.DATA['facet'])
+        self.delete_record(vault.PKEY)
+
+        # SERVICE
+        # Add service vault
+        self.prepare_vault_service_data(vault.DATA2)
+        self.add_record(vault.ENTITY, vault.DATA2, facet=vault.DATA2['facet'])
+
+        # Navigate to record
+        self.navigate_to_record(vault.DATA2['pkey'])
+
+        # Try add values into table
+        fill_tables()
+
+        # Remove service vault record
+        self.navigate_to_entity(vault.ENTITY, vault.DATA2['facet'])
+        self.delete_record(vault.DATA2['pkey'])
+
+        # SHARED
+        # Add shared vault
+        self.add_record(vault.ENTITY, vault.DATA3, facet=vault.DATA3['facet'])
+
+        # Navigate to record
+        self.navigate_to_record(vault.DATA3['pkey'])
+
+        # Try add values into table
+        fill_tables()
+
+        # Remove shared vault record
+        self.navigate_to_entity(vault.ENTITY, vault.DATA3['facet'])
+        self.delete_record(vault.DATA3['pkey'])
+
+        # Clean up
+        self.navigate_to_entity(s_data['entity'])
+        self.delete_record(s_data['pkey'])
+        self.navigate_to_entity(user.ENTITY)
+        self.delete_record(user.PKEY)
+        self.navigate_to_entity(group.ENTITY)
+        self.delete_record(group.PKEY)
-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to