From df0973b08dc17275c8563fbb07d6727a85795b9b Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Fri, 13 Dec 2013 09:34:32 -0500
Subject: [PATCH] Added dialog for adding TPS users.

A new dialog box has been added for adding TPS users. Backbone requires
that a separate attribute is used to specify the user ID (instead of id)
when creating new users, so a new userID field has been added to the
backend service as well.

Ticket #654
---
 .../src/com/netscape/certsrv/user/UserData.java    |  22 ++++
 .../src/com/netscape/cmstools/user/UserAddCLI.java |   6 +-
 .../netscape/cms/servlet/admin/UserService.java    |  23 +++-
 base/server/share/webapps/pki/js/pki-ui.js         | 129 +++++++++++++++++----
 base/tps-tomcat/shared/webapps/tps/js/user.js      |  31 +++--
 base/tps-tomcat/shared/webapps/tps/users.html      |  22 +++-
 6 files changed, 188 insertions(+), 45 deletions(-)

diff --git a/base/common/src/com/netscape/certsrv/user/UserData.java b/base/common/src/com/netscape/certsrv/user/UserData.java
index da771f2e73f155c45a4e415219632aacaaf29c2a..8fc0eab9749ed6547f51609898e7851a3d8d0ccd 100644
--- a/base/common/src/com/netscape/certsrv/user/UserData.java
+++ b/base/common/src/com/netscape/certsrv/user/UserData.java
@@ -60,6 +60,7 @@ public class UserData {
     }
 
     String id;
+    String userID;
     String fullName;
     String email;
     String password;
@@ -90,6 +91,15 @@ public class UserData {
         this.id = id;
     }
 
+    @XmlElement(name="UserID")
+    public String getUserID() {
+        return userID;
+    }
+
+    public void setUserID(String userID) {
+        this.userID = userID;
+    }
+
     @FormParam(Constants.PR_USER_FULLNAME)
     @XmlElement(name="FullName")
     public String getFullName() {
@@ -167,10 +177,12 @@ public class UserData {
         result = prime * result + ((email == null) ? 0 : email.hashCode());
         result = prime * result + ((fullName == null) ? 0 : fullName.hashCode());
         result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((link == null) ? 0 : link.hashCode());
         result = prime * result + ((password == null) ? 0 : password.hashCode());
         result = prime * result + ((phone == null) ? 0 : phone.hashCode());
         result = prime * result + ((state == null) ? 0 : state.hashCode());
         result = prime * result + ((type == null) ? 0 : type.hashCode());
+        result = prime * result + ((userID == null) ? 0 : userID.hashCode());
         return result;
     }
 
@@ -203,6 +215,11 @@ public class UserData {
                 return false;
         } else if (!id.equals(other.id))
             return false;
+        if (link == null) {
+            if (other.link != null)
+                return false;
+        } else if (!link.equals(other.link))
+            return false;
         if (password == null) {
             if (other.password != null)
                 return false;
@@ -223,6 +240,11 @@ public class UserData {
                 return false;
         } else if (!type.equals(other.type))
             return false;
+        if (userID == null) {
+            if (other.userID != null)
+                return false;
+        } else if (!userID.equals(other.userID))
+            return false;
         return true;
     }
 
diff --git a/base/java-tools/src/com/netscape/cmstools/user/UserAddCLI.java b/base/java-tools/src/com/netscape/cmstools/user/UserAddCLI.java
index 33a6786e7ffdd91835782747c5b365e21a30a4fe..5e22522c891b9c592a3b88995cfb1847666dc5f5 100644
--- a/base/java-tools/src/com/netscape/cmstools/user/UserAddCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/user/UserAddCLI.java
@@ -86,10 +86,10 @@ public class UserAddCLI extends CLI {
             System.exit(1);
         }
 
-        String userId = cmdArgs[0];
+        String userID = cmdArgs[0];
 
         UserData userData = new UserData();
-        userData.setID(userId);
+        userData.setUserID(userID);
         userData.setFullName(cmd.getOptionValue("fullName"));
         userData.setEmail(cmd.getOptionValue("email"));
         userData.setPassword(cmd.getOptionValue("password"));
@@ -99,7 +99,7 @@ public class UserAddCLI extends CLI {
 
         userData = userCLI.userClient.addUser(userData);
 
-        MainCLI.printMessage("Added user \"" + userId + "\"");
+        MainCLI.printMessage("Added user \"" + userID + "\"");
 
         UserCLI.printUser(userData);
     }
diff --git a/base/server/cms/src/com/netscape/cms/servlet/admin/UserService.java b/base/server/cms/src/com/netscape/cms/servlet/admin/UserService.java
index 63a4613555a48faa916a194c6d77d6befe9ad28c..265fe3e1bdef2e4c04f4796dbddc1beda0fe042f 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/admin/UserService.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/admin/UserService.java
@@ -108,14 +108,17 @@ public class UserService extends PKIService implements UserResource {
 
         UserData userData = new UserData();
 
-        String id = user.getUserID();
-        if (!StringUtils.isEmpty(id)) userData.setID(id);
+        String userID = user.getUserID();
+        if (!StringUtils.isEmpty(userID)) {
+            userData.setID(userID);
+            userData.setUserID(userID);
+        }
 
         String fullName = user.getFullName();
         if (!StringUtils.isEmpty(fullName)) userData.setFullName(fullName);
 
-        String userID = URLEncoder.encode(id, "UTF-8");
-        URI uri = uriInfo.getBaseUriBuilder().path(UserResource.class).path("{userID}").build(userID);
+        String encodedUserID = URLEncoder.encode(userID, "UTF-8");
+        URI uri = uriInfo.getBaseUriBuilder().path(UserResource.class).path("{userID}").build(encodedUserID);
         userData.setLink(new Link("self", uri));
 
         return userData;
@@ -256,10 +259,13 @@ public class UserService extends PKIService implements UserResource {
     @Override
     public Response addUser(UserData userData) {
 
+        CMS.debug("UserService.addUser()");
+
         if (userData == null) throw new BadRequestException("User data is null.");
 
         IConfigStore cs = CMS.getConfigStore();
-        String userID = userData.getID();
+        String userID = userData.getUserID();
+        CMS.debug("User ID: " + userID);
 
         // ensure that any low-level exceptions are reported
         // to the signed audit log and stored as failures
@@ -284,6 +290,7 @@ public class UserService extends PKIService implements UserResource {
             IUser user = userGroupManager.createUser(userID);
 
             String fname = userData.getFullName();
+            CMS.debug("Full name: " + fname);
             if (fname == null || fname.length() == 0) {
                 String msg = getUserMessage("CMS_USRGRP_USER_ADD_FAILED_1", headers, "full name");
 
@@ -295,6 +302,7 @@ public class UserService extends PKIService implements UserResource {
             }
 
             String email = userData.getEmail();
+            CMS.debug("Email: " + email);
             if (email != null) {
                 user.setEmail(email);
             } else {
@@ -302,6 +310,7 @@ public class UserService extends PKIService implements UserResource {
             }
 
             String pword = userData.getPassword();
+            CMS.debug("Password: " + (pword == null ? null : "********"));
             if (pword != null && !pword.equals("")) {
                 IPasswordCheck passwdCheck = CMS.getPasswordChecker();
 
@@ -315,6 +324,7 @@ public class UserService extends PKIService implements UserResource {
             }
 
             String phone = userData.getPhone();
+            CMS.debug("Phone: " + phone);
             if (phone != null) {
                 user.setPhone(phone);
             } else {
@@ -322,6 +332,7 @@ public class UserService extends PKIService implements UserResource {
             }
 
             String type = userData.getType();
+            CMS.debug("Type: " + type);
             if (type != null) {
                 user.setUserType(type);
             } else {
@@ -329,11 +340,13 @@ public class UserService extends PKIService implements UserResource {
             }
 
             String state = userData.getState();
+            CMS.debug("State: " + state);
             if (state != null) {
                 user.setState(state);
             }
 
             String tpsProfiles = userData.getAttribute(ATTR_TPS_PROFILES);
+            CMS.debug("TPS profiles: " + tpsProfiles);
             String csType = cs.getString("cs.type");
             if (tpsProfiles != null) {
                 if (!csType.equals("TPS")) {
diff --git a/base/server/share/webapps/pki/js/pki-ui.js b/base/server/share/webapps/pki/js/pki-ui.js
index 7fbb8341abf6ddf21165e8031caaae19d51c1cc0..0ac313952490b058f6169a3437149c4ecdd3b8ef 100644
--- a/base/server/share/webapps/pki/js/pki-ui.js
+++ b/base/server/share/webapps/pki/js/pki-ui.js
@@ -32,7 +32,11 @@ var Model = Backbone.Model.extend({
     save: function(attributes, options) {
         var self = this;
         if (attributes == undefined) attributes = self.attributes;
+        // convert attributes into JSON request
         var request = self.createRequest(attributes);
+        // remove old attributes
+        if (self.isNew()) self.clear();
+        // send JSON request
         Model.__super__.save.call(self, request, options);
     }
 });
@@ -137,15 +141,14 @@ var Dialog = Backbone.View.extend({
             e.preventDefault();
         });
 
+        // set/unset readonly fields
         $("input", self.$el).each(function(index) {
             var input = $(this);
-
-            self.loadField(input);
-
-            // mark some fields readonly
             var name = input.attr("name");
             if ( _.contains(self.readonly, name)) {
                 input.attr("readonly", "readonly");
+            } else {
+                input.removeAttr("readonly");
             }
         });
 
@@ -155,6 +158,7 @@ var Dialog = Backbone.View.extend({
 
             if (_.contains(self.actions, action)) {
                 // enable buttons for specified actions
+                button.show();
                 button.click(function(e) {
                     self.performAction(action);
                     e.preventDefault();
@@ -164,12 +168,30 @@ var Dialog = Backbone.View.extend({
                 button.hide();
             }
         });
+
+        self.loadFields();
+        // save the fields back into model so the model
+        // can detect which fields are changed
+        self.saveFields();
     },
     performAction: function(action) {
         var self = this;
 
-        if (action == "save") {
-            // save changes
+        if (action == "add") {
+            self.add({
+                success: function(model, response, options) {
+                    self.close();
+                },
+                error: function(model, response, options) {
+                    if (response.status == 201) {
+                        self.close();
+                        return;
+                    }
+                    alert("ERROR: " + response.responseText);
+                }
+            });
+
+        } else if (action == "save") {
             self.save({
                 success: function(model, response, options) {
                     self.close();
@@ -189,8 +211,27 @@ var Dialog = Backbone.View.extend({
     },
     open: function() {
         var self = this;
+        if (self.model.isNew()) {
+            self.render();
+            self.$el.show();
+        } else {
+            self.load();
+        }
+    },
+    close: function() {
+        var self = this;
+        self.$el.hide();
 
-        // load data
+        // remove event handlers
+        self.$(".rcue-button-close").off("click");
+        self.$("button").each(function(index) {
+            var button = $(this);
+            button.off("click");
+        });
+        self.trigger("close");
+    },
+    load: function() {
+        var self = this;
         self.model.fetch({
             success: function(model, response, options) {
                 self.render();
@@ -201,35 +242,61 @@ var Dialog = Backbone.View.extend({
             }
         });
     },
-    close: function() {
-        this.$el.hide();
+    loadFields: function() {
+        var self = this;
+
+        $("input", self.$el).each(function(index) {
+            var input = $(this);
+            self.loadField(input);
+        });
     },
     loadField: function(input) {
         var self = this;
         var name = input.attr("name");
         var value = self.model.get(name);
+        if (!value) value = "";
         input.val(value);
     },
+    add: function(options) {
+        var self = this;
+
+        self.saveFields();
+
+        var changedAttributes = self.model.changedAttributes();
+        if (!changedAttributes) return;
+
+        // save non-empty attributes with POST
+        self.model.save(changedAttributes, {
+            wait: true,
+            success: options.success,
+            error: options.error
+        });
+    },
     save: function(options) {
         var self = this;
 
+        self.saveFields();
+
+        var changedAttributes = self.model.changedAttributes();
+        if (!changedAttributes) return;
+
+        // save changed attributes with PATCH
+        self.model.save(changedAttributes, {
+            patch: true,
+            wait: true,
+            success: options.success,
+            error: options.error
+        });
+    },
+    saveFields: function() {
+        var self = this;
+
         var attributes = {};
         $("input", self.$el).each(function(index) {
             var input = $(this);
             self.saveField(input, attributes);
         });
         self.model.set(attributes);
-
-        var changedAttributes = self.model.changedAttributes();
-        if (!changedAttributes) return;
-
-        // save changed attributes only
-        self.model.save(changedAttributes, {
-            patch: true,
-            wait: true,
-            success: options.success,
-            error: options.error
-        });
     },
     saveField: function(input, attributes) {
         var self = this;
@@ -252,7 +319,8 @@ var TableItemView = Backbone.View.extend({
             var name = item.attr("name");
             var value = self.model.get(name);
 
-            if (name == "id") {
+            if (index == 0) {
+                // link first cell to edit dialog
                 item.empty();
                 $("<a/>", {
                     href: "#",
@@ -264,8 +332,11 @@ var TableItemView = Backbone.View.extend({
                         e.preventDefault();
                     }
                 }).appendTo(item);
+
             } else {
+                // show cell content in plain text
                 item.text(value);
+                // update cell automatically on model change
                 self.model.on("change:" + name, function(event) {
                     item.text(self.model.get(name));
                 });
@@ -279,13 +350,25 @@ var TableView = Backbone.View.extend({
         var self = this;
 
         TableView.__super__.initialize.call(self, options);
+        self.addDialog = options.addDialog;
         self.editDialog = options.editDialog;
 
-        self.tbody = $("tbody", self.el);
+        self.thead = $("thead", self.$el);
+        $("button[name=add]", self.thead).click(function(e) {
+            var dialog = self.addDialog;
+            dialog.model = new self.collection.model();
+            dialog.on("close", function(event) {
+                self.render();
+            });
+            dialog.open();
+            e.preventDefault();
+        });
+
+        self.tbody = $("tbody", self.$el);
         self.template = $("tr", self.tbody).detach();
 
         // attach link handlers
-        self.tfoot = $("tfoot", self.el);
+        self.tfoot = $("tfoot", self.$el);
         $("a.prev", self.tfoot).click(function(e) {
             if (self.collection.link("prev") == undefined) return;
             self.collection.go("prev");
diff --git a/base/tps-tomcat/shared/webapps/tps/js/user.js b/base/tps-tomcat/shared/webapps/tps/js/user.js
index 9f36eb4819bb493cbe4053a2e98357a31c7eb8b4..670780c40ccbc2ee283f62b19420a5b7fbe22c25 100644
--- a/base/tps-tomcat/shared/webapps/tps/js/user.js
+++ b/base/tps-tomcat/shared/webapps/tps/js/user.js
@@ -22,18 +22,24 @@
 var UserModel = Model.extend({
     urlRoot: "/tps/rest/admin/users",
     parseResponse: function(response) {
-        var attributes = response.User.Attributes.Attribute;
-        attributes = attributes == undefined ? [] : [].concat(attributes);
+
+        if (!response || !response.User) return {};
 
         var attrs = {};
-        _(attributes).each(function(attribute) {
-            var name = attribute["@name"];
-            var value = attribute["$"];
-            attrs[name] = value;
-        });
+        if (response.User.Attributes) {
+            var attributes = response.User.Attributes.Attribute;
+            attributes = attributes == undefined ? [] : [].concat(attributes);
+
+            _(attributes).each(function(attribute) {
+                var name = attribute["@name"];
+                var value = attribute["$"];
+                attrs[name] = value;
+            });
+        }
 
         return {
             id: response.User["@id"],
+            userID: response.User.UserID,
             fullName: response.User.FullName,
             email: response.User.Email,
             state: response.User.State,
@@ -54,7 +60,8 @@ var UserModel = Model.extend({
 
         return {
             User: {
-                "@id": attributes.id,
+                "@id": this.id,
+                UserID: attributes.userID,
                 FullName: attributes.fullName,
                 Email: attributes.email,
                 State: attributes.state,
@@ -66,6 +73,7 @@ var UserModel = Model.extend({
 });
 
 var UserCollection = Collection.extend({
+    model: UserModel,
     urlRoot: "/tps/rest/admin/users",
     getEntries: function(response) {
         return response.Users.User;
@@ -76,6 +84,7 @@ var UserCollection = Collection.extend({
     parseEntry: function(entry) {
         return new UserModel({
             id: entry["@id"],
+            userID: entry.UserID,
             fullName: entry.FullName
         });
     }
@@ -92,8 +101,10 @@ var UserDialog = Dialog.extend({
         }
 
         var attributes = self.model.get("attributes");
-        var value = attributes.tpsProfiles;
-        input.val(value);
+        if (attributes) {
+            var value = attributes.tpsProfiles;
+            input.val(value);
+        }
     },
     saveField: function(input, attributes) {
         var self = this;
diff --git a/base/tps-tomcat/shared/webapps/tps/users.html b/base/tps-tomcat/shared/webapps/tps/users.html
index fdf7e741837fa1f983f150a338ae4e27948f1bc4..0c95ad7cec94b4057e8936af7dc05c4ebd9c8299 100644
--- a/base/tps-tomcat/shared/webapps/tps/users.html
+++ b/base/tps-tomcat/shared/webapps/tps/users.html
@@ -25,15 +25,24 @@
     <script src="/tps/js/user.js"></script>
     <script>
 $(function() {
+    var addDialog = new UserDialog({
+        el: $("#user-dialog"),
+        title: "Add User",
+        readonly: ["type"],
+        actions: ["cancel", "add"]
+    });
+
     var editDialog = new UserDialog({
         el: $("#user-dialog"),
         title: "Edit User",
-        readonly: ["id", "type"]
+        readonly: ["userID", "type"],
+        actions: ["cancel", "save"]
     });
 
     new TableView({
         el: $("table[name='users']"),
         collection: new UserCollection({ size: 3 }),
+        addDialog: addDialog,
         editDialog: editDialog
     });
 });
@@ -48,7 +57,10 @@ $(function() {
     <tr>
          <th class="rcue-table-actions" colspan="2">
              <span><input type="text" placeholder="Search..."></span>
-             <span><button>Add</button><button>Remove</button></span>
+             <span>
+                 <button name="add">Add</button>
+                 <button name="remove">Remove</button>
+             </span>
          </th>
     </tr>
     <tr>
@@ -58,7 +70,7 @@ $(function() {
 </thead>
 <tbody>
     <tr>
-        <td name="id">&nbsp;</td>
+        <td name="userID">&nbsp;</td>
         <td name="fullName">&nbsp;</td>
     </tr>
 </tbody>
@@ -81,7 +93,7 @@ $(function() {
             <a class="rcue-button-close" href="#"></a>
         </header>
         <fieldset>
-            <label>User ID</label><input name="id" type="text"><br>
+            <label>User ID</label><input name="userID" type="text"><br>
             <label>Full Name</label><input name="fullName" type="text"><br>
             <label>Email</label><input name="email" type="text"><br>
             <label>Type</label><input name="type" type="text"><br>
@@ -89,7 +101,9 @@ $(function() {
             <label>TPS Profiles</label><input name="tpsProfiles" type="text"><br>
         </fieldset>
         <footer>
+            <button name="add" class="primary">Add</button>
             <button name="save" class="primary">Save</button>
+            <button name="remove" class="primary">Remove</button>
             <button name="cancel">Cancel</button>
         </footer>
     </div>
-- 
1.8.4.2

