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 fa1ff996452da2ec6dc114a62a0c69dc0218474d 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 f0fdd68f7f1cfdfba0660d0e99e0ac3b999d88ee 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 418c40f081f7912e26f3e643f385703527e84918 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 948ca6b2733db7024ee9d6766bc02467be9f5589 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 bbfa43518192f830654cccecb593c972ccfa7d3b 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 2771182396f91125a7fcea02984ddf7c002d6d8e 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 0ffa69b73cebbea68c4eddc8965681c969cc3e73 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 b5ff344e12b583af516b91e21eb1a390cbca2a66 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 e84dfc1449e1a144ed31440981a750a1ed3b4d2e 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 3e55cedf789955f93b1dff97d9e1f4a78557991e 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 e22b53facb5bd7ea27c283962069cae80aea3782 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 ae94f98..3f480b0 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
@@ -549,7 +549,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 e278251ba6fbefc314a633847db960d4fe074596 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..3d8e965
--- /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 7d5b32b..68fa1c3 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 a0bfa55..3e6a751 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 f666a927381ec0ad2c57b6fc7311548998843bd6 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 e05a355df47e14ae061cb709b14168f6cc949652 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 dfbc3fb09ecbf69fd0a6450bb90fe8faf5c70df2 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 3d8e965..29b1c2a 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