From 4bc3ccd176c41b999003dc7a29113e13b1dd339d Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Tue, 1 Apr 2014 15:28:16 -0400
Subject: [PATCH] Added profile mapping page.

A new profile mapping page has been added to TPS UI. The page is fully
functional like other existing pages. The REST service for profile
mappings has been updated to be more consistent with other resources.

Ticket #654
---
 .../certsrv/tps/profile/ProfileMappingData.java    |  29 ++---
 .../tps/profile/ProfileMappingResource.java        |   4 +-
 .../cmstools/tps/profile/ProfileMappingCLI.java    |   8 +-
 .../shared/webapps/tps/js/profile-mapping.js       | 129 +++++++++++++++++++++
 base/tps-tomcat/shared/webapps/tps/ui/index.html   |  13 ++-
 .../shared/webapps/tps/ui/profile-mapping.html     | 118 +++++++++++++++++++
 .../shared/webapps/tps/ui/profile-mappings.html    |  70 +++++++++++
 .../server/tps/rest/ProfileMappingService.java     |  32 +++--
 8 files changed, 365 insertions(+), 38 deletions(-)
 create mode 100644 base/tps-tomcat/shared/webapps/tps/js/profile-mapping.js
 create mode 100644 base/tps-tomcat/shared/webapps/tps/ui/profile-mapping.html
 create mode 100644 base/tps-tomcat/shared/webapps/tps/ui/profile-mappings.html

diff --git a/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingData.java b/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingData.java
index 2ca955b4e9eed5da5112ca25028cf3a688acd0ce..96ec4e078c321add2af025f9cee8b35c355fe16c 100644
--- a/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingData.java
+++ b/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingData.java
@@ -21,7 +21,6 @@ package com.netscape.certsrv.tps.profile;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -59,7 +58,7 @@ public class ProfileMappingData {
 
     String id;
     String status;
-    Map<String, String> properties = new LinkedHashMap<String, String>();
+    Map<String, String> properties;
 
     Link link;
 
@@ -88,24 +87,7 @@ public class ProfileMappingData {
     }
 
     public void setProperties(Map<String, String> properties) {
-        this.properties.clear();
-        this.properties.putAll(properties);
-    }
-
-    public Collection<String> getPropertyNames() {
-        return properties.keySet();
-    }
-
-    public String getProperty(String name) {
-        return properties.get(name);
-    }
-
-    public void setProperty(String name, String value) {
-        properties.put(name, value);
-    }
-
-    public String removeProperty(String name) {
-        return properties.remove(name);
+        this.properties = properties;
     }
 
     public static class MapAdapter extends XmlAdapter<PropertyList, Map<String, String>> {
@@ -220,8 +202,11 @@ public class ProfileMappingData {
         ProfileMappingData before = new ProfileMappingData();
         before.setID("profileMapping1");
         before.setStatus("ENABLED");
-        before.setProperty("param1", "value1");
-        before.setProperty("param2", "value2");
+
+        Map<String, String> properties = new LinkedHashMap<String, String>();
+        properties.put("param1", "value1");
+        properties.put("param2", "value2");
+        before.setProperties(properties);
 
         String string = before.toString();
         System.out.println(string);
diff --git a/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingResource.java b/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingResource.java
index 5828a3317046475b9c605614d815138ca262aa3c..5b819b8991c34206c5472c94d6137fc3b6ea24c3 100644
--- a/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingResource.java
+++ b/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingResource.java
@@ -20,7 +20,6 @@ package com.netscape.certsrv.tps.profile;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
@@ -30,6 +29,7 @@ import org.jboss.resteasy.annotations.ClientResponseType;
 
 import com.netscape.certsrv.acls.ACLMapping;
 import com.netscape.certsrv.authentication.AuthMethodMapping;
+import com.netscape.certsrv.base.PATCH;
 
 
 /**
@@ -57,7 +57,7 @@ public interface ProfileMappingResource {
     @ClientResponseType(entityType=ProfileMappingData.class)
     public Response addProfileMapping(ProfileMappingData profileMappingData);
 
-    @PUT
+    @PATCH
     @Path("{profileMappingID}")
     @ACLMapping("profile-mappings.modify")
     @ClientResponseType(entityType=ProfileMappingData.class)
diff --git a/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileMappingCLI.java b/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileMappingCLI.java
index 6f7e6c5d9b6f44d6357f87965cd963373af234de..9c2f7183c0df351ec095a8b3ecb60a4a7fd9a8ca 100644
--- a/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileMappingCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileMappingCLI.java
@@ -59,9 +59,11 @@ public class ProfileMappingCLI extends CLI {
         if (showProperties) {
             System.out.println("  Properties:");
             Map<String, String> properties = profileMappingData.getProperties();
-            for (String name : properties.keySet()) {
-                String value = properties.get(name);
-                System.out.println("    " + name + ": " + value);
+            if (properties != null) {
+                for (String name : properties.keySet()) {
+                    String value = properties.get(name);
+                    System.out.println("    " + name + ": " + value);
+                }
             }
         }
 
diff --git a/base/tps-tomcat/shared/webapps/tps/js/profile-mapping.js b/base/tps-tomcat/shared/webapps/tps/js/profile-mapping.js
new file mode 100644
index 0000000000000000000000000000000000000000..a3cea331dc91a95ecfa1a37dc35ee95e709e5b32
--- /dev/null
+++ b/base/tps-tomcat/shared/webapps/tps/js/profile-mapping.js
@@ -0,0 +1,129 @@
+/* --- BEGIN COPYRIGHT BLOCK ---
+ * 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; version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2014 Red Hat, Inc.
+ * All rights reserved.
+ * --- END COPYRIGHT BLOCK ---
+ *
+ * @author Endi S. Dewata
+ */
+
+var ProfileMappingModel = Model.extend({
+    urlRoot: "/tps/rest/profile-mappings",
+    parseResponse: function(response) {
+        return {
+            id: response.id,
+            profileMappingID: response.id,
+            status: response.Status,
+            properties: response.Properties.Property
+        };
+    },
+    createRequest: function(attributes) {
+        return {
+            id: attributes.profileMappingID,
+            Status: attributes.status,
+            Properties: {
+                Property: attributes.properties
+            }
+        };
+    },
+    enable: function(options) {
+        var self = this;
+        $.ajax({
+            type: "POST",
+            url: self.url() + "?action=enable",
+            dataType: "json"
+        }).done(function(data, textStatus, jqXHR) {
+            self.set(self.parseResponse(data));
+            if (options.success) options.success.call(self, data, textStatus, jqXHR);
+        }).fail(function(jqXHR, textStatus, errorThrown) {
+            if (options.error) options.error.call(self, jqXHR, textStatus, errorThrown);
+        });
+    },
+    disable: function(options) {
+        var self = this;
+        $.ajax({
+            type: "POST",
+            url: self.url() + "?action=disable",
+            dataType: "json"
+        }).done(function(data, textStatus, jqXHR) {
+            self.set(self.parseResponse(data));
+            if (options.success) options.success.call(self, data, textStatus, jqXHR);
+        }).fail(function(jqXHR, textStatus, errorThrown) {
+            if (options.error) options.error.call(self, jqXHR, textStatus, errorThrown);
+        });
+    }
+});
+
+var ProfileMappingCollection = Collection.extend({
+    urlRoot: "/tps/rest/profile-mappings",
+    getEntries: function(response) {
+        return response.entries;
+    },
+    getLinks: function(response) {
+        return response.Link;
+    },
+    parseEntry: function(entry) {
+        return new ProfileMappingModel({
+            id: entry.id,
+            status: entry.Status
+        });
+    }
+});
+
+var ProfileMappingsTable = Table.extend({
+    initialize: function(options) {
+        var self = this;
+        ProfileMappingsTable.__super__.initialize.call(self, options);
+        self.parentPage = options.parentPage;
+    },
+    open: function(item) {
+        var self = this;
+
+        var page = new EntryPage({
+            el: self.parentPage.$el,
+            url: "profile-mapping.html",
+            model: item.model
+        });
+
+        page.open();
+    },
+    add: function() {
+        var self = this;
+
+        var page = new AddEntryPage({
+            el: self.parentPage.$el,
+            url: "profile-mapping.html",
+            model: new ProfileMappingModel(),
+            mode: "add",
+            parentPage: self.parentPage
+        });
+
+        page.open();
+    }
+});
+
+var ProfileMappingsPage = Page.extend({
+    load: function() {
+        var self = this;
+
+        var table = new ProfileMappingsTable({
+            el: $("table[name='profile-mappings']"),
+            collection: new ProfileMappingCollection(),
+            parentPage: self
+        });
+
+        table.render();
+    }
+});
diff --git a/base/tps-tomcat/shared/webapps/tps/ui/index.html b/base/tps-tomcat/shared/webapps/tps/ui/index.html
index 31f4ecacc366b7253efefd912a5d755e76cb2182..41732a8e1da4a16e0032d15dabcdc7228f867208 100644
--- a/base/tps-tomcat/shared/webapps/tps/ui/index.html
+++ b/base/tps-tomcat/shared/webapps/tps/ui/index.html
@@ -32,6 +32,7 @@
     <script src="/tps/js/connection.js"></script>
     <script src="/tps/js/group.js"></script>
     <script src="/tps/js/profile.js"></script>
+    <script src="/tps/js/profile-mapping.js"></script>
     <script src="/tps/js/selftest.js"></script>
     <script src="/tps/js/token.js"></script>
     <script src="/tps/js/user.js"></script>
@@ -48,6 +49,7 @@ $(function() {
             connections: new ConnectionsPage({ el: content, url: "connections.html" }),
             groups: new GroupPage({ el: content, url: "groups.html" }),
             profiles: new ProfilesPage({ el: content, url: "profiles.html" }),
+            profileMappings: new ProfileMappingsPage({ el: content, url: "profile-mappings.html" }),
             selftests: new SelfTestPage({ el: content, url: "selftests.html" }),
             tokens: new TokenPage({ el: content, url: "tokens.html" }),
             users: new UserPage({ el: content, url: "users.html" })
@@ -101,7 +103,16 @@ $(function() {
     <li><a href="#tokens">Tokens</a></li>
     <li><a href="#certs">Certificates</a></li>
     <li><a href="#activities">Activities</a></li>
-    <li><a href="#profiles">Profiles</a></li>
+    <li class="dropdown context">
+      <a href="#" class="dropdown-toggle" data-toggle="dropdown">
+        Profiles
+        <b class="caret"></b>
+      </a>
+      <ul class="dropdown-menu">
+        <li><a href="#profiles">Profiles</a></li>
+        <li><a href="#profileMappings">Profile Mappings</a></li>
+      </ul>
+    </li>
     <li><a href="#selftests">SelfTests</a></li>
     <li><a href="#users">Users</a></li>
     <li><a href="#groups">Groups</a></li>
diff --git a/base/tps-tomcat/shared/webapps/tps/ui/profile-mapping.html b/base/tps-tomcat/shared/webapps/tps/ui/profile-mapping.html
new file mode 100644
index 0000000000000000000000000000000000000000..4bd76b7313fb725fce70bb47f79487a30903680b
--- /dev/null
+++ b/base/tps-tomcat/shared/webapps/tps/ui/profile-mapping.html
@@ -0,0 +1,118 @@
+<!-- --- BEGIN COPYRIGHT BLOCK ---
+     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; version 2 of the License.
+
+     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, write to the Free Software Foundation, Inc.,
+     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+     Copyright (C) 2014 Red Hat, Inc.
+     All rights reserved.
+     --- END COPYRIGHT BLOCK --- -->
+<div class="pki-header">
+
+<span class="pki-title">Profile Mapping <span name="id"></span></span>
+
+<span class="pki-actions">
+
+<span class="pki-menu" style="display: none;">
+<a name="edit" href="#">Edit Profile Mapping</a><br>
+<a name="enable" href="#">Enable Profile Mapping</a><a name="disable" href="#" style="display: none;">Disable Profile Mapping</a><br>
+</span>
+
+<span class="pki-buttons" style="display: none;">
+<button name="cancel">Cancel</button>
+<button name="save" class="primary">Save</button>
+</span>
+
+</span>
+
+</div>
+
+<div name="profile-mapping" class="pki-fields">
+<fieldset>
+    <label>Profile Mapping ID</label>
+    <input name="profileMappingID" readonly="readonly">
+    <br>
+    <label>Status</label>
+    <input name="status" readonly="readonly">
+    <br>
+</fieldset>
+</div>
+
+<h2>Properties</h2>
+
+<table name="properties">
+<thead>
+    <tr>
+         <th class="pki-table-actions" colspan="3">
+             <span name="search">
+                 <input name="search" type="text" placeholder="Search...">
+             </span>
+             <span class="pki-table-buttons" style="display: none;">
+                 <button name="add">Add</button>
+                 <button name="remove">Remove</button>
+             </span>
+         </th>
+    </tr>
+    <tr>
+        <th class="pki-select-column"><input id="profile_selectall" type="checkbox"><label for="profile_selectall">&nbsp;</label></th>
+        <th class="pki-property-name-column">Name</th>
+        <th>Value</th>
+    </tr>
+</thead>
+<tbody>
+    <tr>
+        <td class="pki-select-column"><input id="profile_select" type="checkbox"><label for="profile_select">&nbsp;</label></td>
+        <td name="id" class="pki-property-name-column"><a href="#">&nbsp;</a></td>
+        <td name="status">&nbsp;</td>
+    </tr>
+</tbody>
+<tfoot>
+    <tr>
+         <th class="pki-table-actions" colspan="3">
+             <div class="pki-table-info">
+                 Total: <span name="totalEntries">0</span> entries
+             </div>
+             <div class="pki-page-controls">
+                 <ul class="pagination">
+                     <li><a href="#" name="first"><span class="i fa fa-angle-double-left"></span></a></li>
+                     <li><a href="#" name="prev"><span class="i fa fa-angle-left"></span></a></li>
+                 </ul>
+                 <span class="pki-page-jump">
+                     <input name="page" type="text" value="1"> of <span name="totalPages">1</span>
+                 </span>
+                 <ul class="pagination">
+                     <li><a href="#" name="next"><span class="i fa fa-angle-right"></span></a></li>
+                     <li><a href="#" name="last"><span class="i fa fa-angle-double-right"></span></a></li>
+                 </ul>
+             </div>
+         </th>
+    </tr>
+</tfoot>
+</table>
+
+<div id="property-dialog" class="rcue-dialog-background">
+    <div class="rcue-dialog">
+        <header>
+            <h1>Edit Property</h1>
+            <a class="rcue-button-close" href="#"></a>
+        </header>
+        <fieldset>
+            <label>Name</label><input name="name" type="text"><br>
+            <label>Value</label><input name="value" type="text"><br>
+        </fieldset>
+        <footer>
+            <button name="add" class="primary">Add</button>
+            <button name="save" class="primary">Save</button>
+            <button name="close" class="primary">Close</button>
+            <button name="cancel">Cancel</button>
+        </footer>
+    </div>
+</div>
diff --git a/base/tps-tomcat/shared/webapps/tps/ui/profile-mappings.html b/base/tps-tomcat/shared/webapps/tps/ui/profile-mappings.html
new file mode 100644
index 0000000000000000000000000000000000000000..c393446b1397e8bfdc207e8ddfa52eb2ca195907
--- /dev/null
+++ b/base/tps-tomcat/shared/webapps/tps/ui/profile-mappings.html
@@ -0,0 +1,70 @@
+<!-- --- BEGIN COPYRIGHT BLOCK ---
+     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; version 2 of the License.
+
+     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, write to the Free Software Foundation, Inc.,
+     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+     Copyright (C) 2014 Red Hat, Inc.
+     All rights reserved.
+     --- END COPYRIGHT BLOCK --- -->
+<div class="pki-header">
+<span class="pki-title">Profile Mappings</span>
+</div>
+
+<table name="profile-mappings">
+<thead>
+    <tr>
+         <th class="pki-table-actions" colspan="3">
+             <span name="search">
+                 <input name="search" type="text" placeholder="Search...">
+             </span>
+             <span class="pki-table-buttons">
+                 <button name="add">Add</button>
+                 <button name="remove">Remove</button>
+             </span>
+         </th>
+    </tr>
+    <tr>
+        <th class="pki-select-column"><input id="profile_mapping_selectall" type="checkbox"><label for="profile_mapping_selectall">&nbsp;</label></th>
+        <th>Profile Mapping ID</th>
+        <th>Status</th>
+    </tr>
+</thead>
+<tbody>
+    <tr>
+        <td class="pki-select-column"><input id="profile_mapping_select" type="checkbox"><label for="profile_mapping_select">&nbsp;</label></td>
+        <td name="id"><a href="profile-mapping.html">&nbsp;</a></td>
+        <td name="status">&nbsp;</td>
+    </tr>
+</tbody>
+<tfoot>
+    <tr>
+         <th class="pki-table-actions" colspan="3">
+             <div class="pki-table-info">
+                 Total: <span name="totalEntries">0</span> entries
+             </div>
+             <div class="pki-page-controls">
+                 <ul class="pagination">
+                     <li><a href="#" name="first"><span class="i fa fa-angle-double-left"></span></a></li>
+                     <li><a href="#" name="prev"><span class="i fa fa-angle-left"></span></a></li>
+                 </ul>
+                 <span class="pki-page-jump">
+                     <input name="page" type="text" value="1"> of <span name="totalPages">1</span>
+                 </span>
+                 <ul class="pagination">
+                     <li><a href="#" name="next"><span class="i fa fa-angle-right"></span></a></li>
+                     <li><a href="#" name="last"><span class="i fa fa-angle-double-right"></span></a></li>
+                 </ul>
+             </div>
+         </th>
+    </tr>
+</tfoot>
+</table>
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/rest/ProfileMappingService.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/rest/ProfileMappingService.java
index 75087cd6119e51658ad881940e235612ecbfbb38..1425457f325d8268e56e100433cce2c72dfb2f71 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/rest/ProfileMappingService.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/rest/ProfileMappingService.java
@@ -23,6 +23,7 @@ import java.net.URI;
 import java.net.URLEncoder;
 import java.security.Principal;
 import java.util.Iterator;
+import java.util.Map;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.core.Context;
@@ -198,23 +199,34 @@ public class ProfileMappingService extends PKIService implements ProfileMappingR
 
             ProfileMappingRecord record = database.getRecord(profileMappingID);
 
-            String status = record.getStatus();
-            if (!"Disabled".equals(status)) {
+            // only disabled profile mapping can be updated
+            if (!"Disabled".equals(record.getStatus())) {
                 throw new ForbiddenException("Unable to update profile mapping " + profileMappingID);
             }
 
-            status = profileMappingData.getStatus();
-            if (!"Enabled".equals(status)) {
-                throw new ForbiddenException("Invalid profile mapping status: " + status);
+            // update status if specified
+            String status = profileMappingData.getStatus();
+            if (status != null && !"Disabled".equals(status)) {
+                if (!"Enabled".equals(status)) {
+                    throw new ForbiddenException("Invalid profile mapping status: " + status);
+                }
+
+                // if user doesn't have rights, set to pending
+                Principal principal = servletRequest.getUserPrincipal();
+                if (database.requiresApproval() && !database.canApprove(principal)) {
+                    status = "Pending_Approval";
+                }
+
+                // enable profile mapping
+                record.setStatus(status);
             }
 
-            Principal principal = servletRequest.getUserPrincipal();
-            if (database.requiresApproval() && !database.canApprove(principal)) {
-                status = "Pending_Approval";
+            // update properties if specified
+            Map<String, String> properties = profileMappingData.getProperties();
+            if (properties != null) {
+                record.setProperties(properties);
             }
 
-            record.setStatus(status);
-            record.setProperties(profileMappingData.getProperties());
             database.updateRecord(profileMappingID, record);
 
             profileMappingData = createProfileMappingData(database.getRecord(profileMappingID));
-- 
1.8.4.2

