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 0e038b2e6297e1045008d8aacccfcba067d6e7ab 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/13] 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 3f665d1ecff23515c68ff872e7d2fa23f570b0d6 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/13] 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 88fde1c79ca11d545bc4609b9bed80086cbe7335 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/13] 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 3088e22..3a9cd47 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 36958b962434d9acf58505b40ba98378456c3a8b 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/13] 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 4972c9e6eec7fdeb00d44afb5194a75b4e50af25 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/13] 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 e274e6f..f270500 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 33609f726fb9ae90e41350156e4bfd8618fc27f9 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/13] 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 f270500..9be14d0 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 f3dc7556a03f10c08ce5369acb374b8ab5f6e097 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/13] 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 6913e400018a035cfbf6f069c9f6ad60fee0ce93 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/13] 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 25f21e7..71057ff 100644
--- a/install/ui/src/freeipa/search.js
+++ b/install/ui/src/freeipa/search.js
@@ -346,6 +346,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',
@@ -365,7 +374,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]);
                         }
@@ -375,6 +385,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);
         }
 
@@ -401,6 +416,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 39f62a92d1a2bd4d09e801d4484bf09d4f460ef8 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/13] 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 b4872210b4fdda6faf5e95a6389b40cca9e95d3d Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Wed, 5 Oct 2016 12:06:27 +0200
Subject: [PATCH 10/13] 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                | 815 +++++++++++++++++++++++++
 install/ui/test/data/ipa_init.json             |  25 +
 ipaserver/plugins/internal.py                  |  33 +
 6 files changed, 939 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..c2c1cd1
--- /dev/null
+++ b/install/ui/src/freeipa/vault.js
@@ -0,0 +1,815 @@
+/*  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'],
+        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'],
+        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'],
+        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..3e51a44 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,30 @@ 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 70db0a5cb97796ead2bd05a3746bbb19e1cd1f53 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 25 Oct 2016 16:53:14 +0200
Subject: [PATCH 11/13] 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 459f4b7b5ca078a98934d9d21e72e7b4e121fe08 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 25 Oct 2016 16:54:28 +0200
Subject: [PATCH 12/13] 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 6c33c87408570998468819919100b861def2c4cc Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 25 Oct 2016 13:40:24 +0200
Subject: [PATCH 13/13] 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
---
 ipatests/test_webui/data_vault.py |  63 +++++++++++++
 ipatests/test_webui/test_vault.py | 186 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 249 insertions(+)
 create mode 100644 ipatests/test_webui/data_vault.py
 create mode 100644 ipatests/test_webui/test_vault.py

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..4810ae6
--- /dev/null
+++ b/ipatests/test_webui/test_vault.py
@@ -0,0 +1,186 @@
+# 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