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 2364407f3b7a26e046d78e7eaae147d327a36af0 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/14] 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 bf1b9f872f5592987f824e2e1cbafe75152837a8 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/14] 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 | 24 +++++++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)

diff --git a/install/ui/src/freeipa/association.js b/install/ui/src/freeipa/association.js
index d44f8c8..63beeb8 100644
--- a/install/ui/src/freeipa/association.js
+++ b/install/ui/src/freeipa/association.js
@@ -429,6 +429,17 @@ 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 other_option_name {String}
+     */
+    that.other_option_name = spec.other_option_name;
+
     that.other_entity = IPA.get_entity(spec.other_entity);
     that.attribute_member = spec.attribute_member;
 
@@ -683,9 +694,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 +712,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 +764,6 @@ IPA.association_table_widget = function (spec) {
             );
         };
 
-
         dialog.open();
     };
 
@@ -761,8 +779,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 cbf91388909d93d06b99f2cbe04aa26e37d9a9fc 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/14] 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 | 25 +++++++++++++++++++
 install/ui/src/freeipa/field.js       | 46 ++++++++++++++++++++++++++++-------
 2 files changed, 62 insertions(+), 9 deletions(-)

diff --git a/install/ui/src/freeipa/association.js b/install/ui/src/freeipa/association.js
index 63beeb8..b1073eb 100644
--- a/install/ui/src/freeipa/association.js
+++ b/install/ui/src/freeipa/association.js
@@ -822,12 +822,37 @@ 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);
 
+    /**
+     * In case that there is set acl_param in spec then this property will be
+     * set to true and it checks ACLs.
+     */
+    that.acl_param_set = !!spec.acl_param;
+
     that.load = function(data) {
         that.values = that.adapter.load(data);
         that.widget.update(that.values);
         that.widget.unselect_all();
+
+        if (that.acl_param_set) {
+            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 d70a778..89749e5 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`
@@ -451,10 +461,34 @@ 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;
+            }
+
+            if (that.metadata.flags && array.indexOf(that.metadata.flags, 'no_update') > -1) {
+                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
@@ -479,14 +513,8 @@ field.field = IPA.field = function(spec) {
             return has;
         }
 
-        if (that.metadata) {
-            if (that.metadata.primary_key) {
-                writable = false;
-            }
-
-            if (that.metadata.flags && array.indexOf(that.metadata.flags, 'no_update') > -1) {
-                writable = false;
-            }
+        if (that.check_writable_from_metadata) {
+            writable = that.load_writable_from_metadata(writable);
         }
 
         if (record && record.attributelevelrights) {

From f5803c1e5cd5fd9aa7e3b35ff5a3b7a1a2750630 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/14] 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 b1073eb..6c6b202 100644
--- a/install/ui/src/freeipa/association.js
+++ b/install/ui/src/freeipa/association.js
@@ -839,6 +839,15 @@ IPA.association_table_field = function (spec) {
      */
     that.acl_param_set = !!spec.acl_param;
 
+    /**
+     * 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);
@@ -866,14 +875,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 c08972f309c4c343d3e86bee5fe6dba4542d3ed1 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/14] Add property which allows refresh command to use url
 value

'refresh_url_arg' 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 9f0e632..37a0781 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_url_arg = spec.refresh_url_arg || null;
+
+    /**
      * Name of update command
      *
      * - defaults to 'mod'
@@ -884,6 +891,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
@@ -1033,6 +1057,10 @@ exp.details_facet = IPA.details_facet = function(spec, no_init) {
             command.args = that.get_pkeys();
         }
 
+        if (that.refresh_url_arg) {
+            that.add_url_arg_to_command(command, that.refresh_url_arg);
+        }
+
         return command;
     };
 

From 90d01805ddb04752e432fda6aee9695d2ddd5d89 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/14] Add possibility to pass url parameter to update command
 of details page

'update_url_arg' 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 37a0781..1d8e94e 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_url_arg = spec.update_url_arg || null;
+
+    /**
      * Command mode
      * Command mode determines how update information on update is collected.
      * There are two modes:
@@ -928,6 +935,10 @@ exp.details_facet = IPA.details_facet = function(spec, no_init) {
             options: options
         });
 
+        if (that.update_url_arg) {
+            that.add_url_arg_to_command(command, that.update_url_arg);
+        }
+
         //set command options
         that.add_fields_to_command(update_info, command);
 

From a81b934caa739e594dc7fcba75207f337ed31cdf 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/14] 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. 'load_page_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 06eca18..06e5f9b 100644
--- a/install/ui/src/freeipa/facet.js
+++ b/install/ui/src/freeipa/facet.js
@@ -1845,6 +1845,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.load_page_additional_attr = spec.load_page_additional_attr || null;
+
+    /**
      * Member resolution(no_member: true ) in rpc request is skipped by default
      * to improve performance of getting data.
      *
@@ -2142,7 +2150,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++) {
@@ -2225,7 +2233,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(),
@@ -2242,6 +2252,10 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
                 args: [pkey]
             });
 
+            if (that.load_page_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});
             }
@@ -2252,6 +2266,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.load_page_additional_attr];
+        if (item) {
+            var temp_option = {};
+            temp_option[that.load_page_additional_attr] = item;
+            command.set_options(temp_option);
+        }
+    };
+
+
+
     /**
      * Execute command for obtaining complete records
      *
@@ -2421,6 +2453,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 6db84df61033615e998507c0b5da402379b70616 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/14] 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 8e6e3febc103d1e5c2d25f6592e7e52e9b3b44a8 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/14] 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 06e5f9b..12ee94c 100644
--- a/install/ui/src/freeipa/facet.js
+++ b/install/ui/src/freeipa/facet.js
@@ -747,6 +747,20 @@ exp.facet = IPA.facet = function(spec, no_init) {
         that.header.set_tabs_visible(visible);
     };
 
+
+    /**
+     * Removes one tab from sidebar element. Finds tab according to its name
+     * attribute, then checks whether the element exists and if it exists, then
+     * it removes it.
+     */
+    that.remove_tab_from_sidebar = function(tab_name) {
+        var sidebar_el = that.sidebar_el;
+        if (!sidebar_el) return;
+
+        var el = sidebar_el.find('li[name=' + tab_name + ']');
+        if (el) el.remove();
+    };
+
     /**
      * Update h1 element in title container
      *

From 5df220de06dfa518f3d8f2403b9277ce61b462e6 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 9 Dec 2016 13:08:05 +0100
Subject: [PATCH 10/14] 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       | 57 ++++++++++++++++++++++++++---------
 install/ui/src/freeipa/hbactest.js    | 16 +++++++---
 install/ui/src/freeipa/service.js     | 10 ++++--
 install/ui/src/freeipa/topology.js    |  4 +--
 7 files changed, 91 insertions(+), 30 deletions(-)

diff --git a/install/ui/src/freeipa/association.js b/install/ui/src/freeipa/association.js
index 6c6b202..2e5e6c6 100644
--- a/install/ui/src/freeipa/association.js
+++ b/install/ui/src/freeipa/association.js
@@ -1043,6 +1043,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';
@@ -1319,6 +1320,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;
 
@@ -1327,10 +1329,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() {
@@ -1489,16 +1499,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 12ee94c..953d8d0 100644
--- a/install/ui/src/freeipa/facet.js
+++ b/install/ui/src/freeipa/facet.js
@@ -1833,6 +1833,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.
      *
@@ -2072,11 +2080,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 ||
@@ -2087,11 +2097,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
+        };
     };
 
     /**
@@ -2108,7 +2128,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;
@@ -2144,11 +2166,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
@@ -2165,16 +2187,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([]);
@@ -2247,9 +2272,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(),
@@ -2259,13 +2284,15 @@ 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.load_page_additional_attr) {
                 that.extend_get_records_command(command, records, pkey);
             }
@@ -2302,13 +2329,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 a6607d2..469fb0c 100644
--- a/install/ui/src/freeipa/service.js
+++ b/install/ui/src/freeipa/service.js
@@ -429,6 +429,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 ||
@@ -442,11 +443,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 c33adba..7b09d7e 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
@@ -545,7 +545,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 71e990ad4883aeb0dc3056c2e2cd9791dadc4a5f Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Wed, 5 Oct 2016 12:06:27 +0200
Subject: [PATCH 11/14] 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                | 818 +++++++++++++++++++++++++
 install/ui/test/data/ipa_init.json             |  25 +
 ipaserver/plugins/internal.py                  |  38 ++
 6 files changed, 947 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 4eb045d..f6ba481 100644
--- a/install/ui/src/freeipa/app.js
+++ b/install/ui/src/freeipa/app.js
@@ -49,11 +49,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 e8ad832..1afb2c7 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 7d121d9..580a765 100644
--- a/install/ui/src/freeipa/navigation/menu_spec.js
+++ b/install/ui/src/freeipa/navigation/menu_spec.js
@@ -182,6 +182,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
+                        }
+                    ]
                 }
             ]
         },
@@ -266,7 +292,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..d11fc71
--- /dev/null
+++ b/install/ui/src/freeipa/vault.js
@@ -0,0 +1,818 @@
+/*  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/>.
+ */
+
+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,
+                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_url_arg = facet_type;
+        facet.update_url_arg = 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_st_w = that.widgets.get_widget('warning_st.warn_standard');
+        var warn_arch_ret_w = that.widgets.get_widget('warning_ar.warn_arch_ret');
+        var warn_standard = $('<div />', {
+            'class': 'alert alert-info',
+            text: text.get("@i18n:objects.vault.add_warn_standard")
+        });
+
+        $('<i />', {
+            'class': 'fa fa-info-circle'
+        }).appendTo(warn_standard);
+
+        warn_st_w.html = warn_standard;
+
+        var warn_arch_ret = $('<div />', {
+            'class': 'alert alert-info',
+            text: text.get("@i18n:objects.vault.add_warn_arch_ret")
+        });
+
+        $('<i />', {
+            'class': 'fa fa-info-circle'
+        }).appendTo(warn_arch_ret);
+
+        warn_arch_ret_w.html = 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('_'));
+
+        that.fields.get_field('type').set_pristine_value([facet_name]);
+    };
+
+    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',
+        additional_navigation_arguments: ['username'],
+        show_values_with_dup_key: true,
+        details_facet: 'vault_user',
+        load_page_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',
+        load_page_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,
+        load_page_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.remove_tab_from_sidebar('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 514206a..108f95c 100644
--- a/install/ui/test/data/ipa_init.json
+++ b/install/ui/test/data/ipa_init.json
@@ -665,6 +665,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 6107a14..849ac9e 100644
--- a/ipaserver/plugins/internal.py
+++ b/ipaserver/plugins/internal.py
@@ -659,6 +659,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"),
             },
@@ -819,6 +828,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 f5a208d82d0195c544ecdddc9178e797983b2ce1 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 25 Oct 2016 16:53:14 +0200
Subject: [PATCH 12/14] 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 5bd60f90d0738193aac2733bfc79a8374c9e2b52 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 25 Oct 2016 16:54:28 +0200
Subject: [PATCH 13/14] 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 c902b036a07e9665133f8c79d389d0415eac1462 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 25 Oct 2016 13:40:24 +0200
Subject: [PATCH 14/14] 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 d11fc71..eb33bbb 100644
--- a/install/ui/src/freeipa/vault.js
+++ b/install/ui/src/freeipa/vault.js
@@ -631,10 +631,32 @@ 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';
 
         that.fields.get_field('type').set_pristine_value([facet_name]);
     };
 
+    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;
 };
 
@@ -750,7 +772,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