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

PR body:
"""
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
"""

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 bb58c35a0707e89e0518f6f950f61af9021566d5 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/10] 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 a571d178aaff29e1c8aa4982827ccb0b6ab019f9 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/10] 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 4bf96bf919e1224d8d8a1457ee89d295a750f824 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/10] 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 cc3d8740b3164e01d65312d942251d1e41dfd49f 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/10] 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 ac2997beba35bd981f77ad214e2c15a097601070 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/10] 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 50f56991dd06a788f9b2082b93ffb1222fb75b30 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/10] 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 369cac2387dbe6c6dabdbabcdf7f26f8e6866e84 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/10] 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 d24850f31c59c74bcd914f3e46f3a1cdc6d4564e 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/10] 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..3120119 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];
+            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 bb7c26b5b1bedc185d467c6c82ffdfa6b7a3a0dd 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/10] 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 a457c6687fe598955fd4943e238ce9efe371250c 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/10] 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/navigation/menu_spec.js |  52 ++-
 install/ui/src/freeipa/vault.js                | 553 +++++++++++++++++++++++++
 install/ui/test/data/ipa_init.json             |  15 +
 ipaserver/plugins/internal.py                  |  15 +
 5 files changed, 636 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/navigation/menu_spec.js b/install/ui/src/freeipa/navigation/menu_spec.js
index 1abd720..e06c69d 100644
--- a/install/ui/src/freeipa/navigation/menu_spec.js
+++ b/install/ui/src/freeipa/navigation/menu_spec.js
@@ -182,6 +182,31 @@ var nav = {};
                         { entity: 'dnsserver' },
                         { entity: 'dnsconfig' }
                     ]
+                },
+                {
+                    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 +291,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..5994cfd
--- /dev/null
+++ b/install/ui/src/freeipa/vault.js
@@ -0,0 +1,553 @@
+/*  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',
+        './rpc',
+        './reg'],
+            function(on, IPA, $, phases, rpc, reg) {
+
+/**
+ * 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',
+        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
+                    }
+                ]
+            },
+            {
+                $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,
+                tabs_in_sidebar: true,
+                columns: [
+                    'cn'
+                ],
+                policies: [
+                    vault.config_sidebar_policy
+                ]
+            }
+        ],
+        adder_dialog: {
+            $factory: vault.custom_adder_dialog,
+            method: 'add_internal',
+            policies: [
+                { $factory: vault.adder_policy }
+            ],
+            fields: [
+                {
+                    $type: 'radio',
+                    name: 'type',
+                    flags: ['no_command'],
+                    default_value: 'user',
+                    label: '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',
+                    options: [
+                        {
+                            value: 'standard',
+                            label: '@i18n:objects.vault.standard_type'
+                        }
+                    ],
+                    tooltip: "@i18n:objects.vault.type_tooltip"
+                }
+            ]
+        },
+        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;
+};
+
+/**
+ * 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 || {};
+
+    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;
+    };
+
+    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'
+                ],
+                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.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;
+
+    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);
+
+return vault;
+});
diff --git a/install/ui/test/data/ipa_init.json b/install/ui/test/data/ipa_init.json
index 99851ab..31ab1e6 100644
--- a/install/ui/test/data/ipa_init.json
+++ b/install/ui/test/data/ipa_init.json
@@ -665,6 +665,21 @@
                             "status_link": "Click to ${action}",
                             "unlock": "Unlock",
                             "unlock_confirm": "Are you sure you want to unlock user ${object}?"
+                        },
+                        "vault": {
+                            "config_title": "Vaults Config",
+                            "group": "Group",
+                            "members": "Members",
+                            "my_vaults_title": "My Vaults",
+                            "owners": "Owners",
+                            "service": "Service",
+                            "service_vaults_title": "Service Vaults",
+                            "shared": "Shared",
+                            "shared_vaults_title": "Shared Vaults",
+                            "standard_type": "Standard",
+                            "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 530458d..ab4bea8 100644
--- a/ipaserver/plugins/internal.py
+++ b/ipaserver/plugins/internal.py
@@ -819,6 +819,21 @@ class i18n_messages(Command):
                 "unlock": _("Unlock"),
                 "unlock_confirm": _("Are you sure you want to unlock user ${object}?"),
             },
+            "vault": {
+                "config_title": _("Vaults Config"),
+                "group": _("Group"),
+                "members": _("Members"),
+                "my_vaults_title": _("My Vaults"),
+                "owners": _("Owners"),
+                "service": _("Service"),
+                "service_vaults_title": _("Service Vaults"),
+                "shared": _("Shared"),
+                "shared_vaults_title": _("Shared Vaults"),
+                "standard_type": _("Standard"),
+                "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"),
-- 
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