>From 6176ce70a64999d007dd4f6d91606a304a04278a Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Fri, 20 May 2016 19:31:45 +0200
Subject: [PATCH] Added TPS UI for managing user certificates.

The TPS UI has been modified to provide an interface to manage the
user certificates.

The UserService has been modified to provide better error messages.

https://fedorahosted.org/pki/ticket/1434
---
 .../src/org/dogtagpki/server/rest/UserService.java |  38 ++++---
 base/server/share/webapps/pki/js/pki-ui.js         |   6 ++
 base/tps/shared/webapps/tps/js/user.js             | 113 +++++++++++++++++++++
 base/tps/shared/webapps/tps/ui/index.html          |   8 ++
 .../webapps/tps/ui/{user.html => user-certs.html}  |  71 +++++--------
 base/tps/shared/webapps/tps/ui/user.html           |   1 +
 6 files changed, 176 insertions(+), 61 deletions(-)
 copy base/tps/shared/webapps/tps/ui/{user.html => user-certs.html} (65%)

diff --git a/base/server/cms/src/org/dogtagpki/server/rest/UserService.java b/base/server/cms/src/org/dogtagpki/server/rest/UserService.java
index 3de7384ee0dc8789e74b619191e7e4c4e739ae6f..0893c4bed36ed1485f5452085ca66953af4d3b53 100644
--- a/base/server/cms/src/org/dogtagpki/server/rest/UserService.java
+++ b/base/server/cms/src/org/dogtagpki/server/rest/UserService.java
@@ -858,6 +858,7 @@ public class UserService extends PKIService implements UserResource {
                 cert = new X509CertImpl(binaryCert);
 
             } catch (CertificateException e) {
+                CMS.debug("UserService: Submitted data is not an X.509 certificate: " + e);
                 // ignore
             }
 
@@ -866,7 +867,7 @@ public class UserService extends PKIService implements UserResource {
                 boolean assending = true;
 
                 // could it be a pkcs7 blob?
-                CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_IS_PK_BLOB"));
+                CMS.debug("UserService: " + CMS.getLogMessage("ADMIN_SRVLT_IS_PK_BLOB"));
 
                 try {
                     CryptoManager manager = CryptoManager.getInstance();
@@ -876,7 +877,8 @@ public class UserService extends PKIService implements UserResource {
                     X509Certificate p7certs[] = pkcs7.getCertificates();
 
                     if (p7certs.length == 0) {
-                        throw new BadRequestException(getUserMessage("CMS_USRGRP_SRVLT_CERT_ERROR", headers));
+                        CMS.debug("UserService: PKCS #7 data contains no certificates");
+                        throw new BadRequestException("PKCS #7 data contains no certificates");
                     }
 
                     // fix for 370099 - cert ordering can not be assumed
@@ -888,24 +890,24 @@ public class UserService extends PKIService implements UserResource {
                             p7certs[0].getIssuerDN().toString()) &&
                             (p7certs.length == 1)) {
                         cert = p7certs[0];
-                        CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_SINGLE_CERT_IMPORT"));
+                        CMS.debug("UserService: " + CMS.getLogMessage("ADMIN_SRVLT_SINGLE_CERT_IMPORT"));
 
                     } else if (p7certs[0].getIssuerDN().toString().equals(p7certs[1].getSubjectDN().toString())) {
                         cert = p7certs[0];
-                        CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_CERT_CHAIN_ACEND_ORD"));
+                        CMS.debug("UserService: " + CMS.getLogMessage("ADMIN_SRVLT_CERT_CHAIN_ACEND_ORD"));
 
                     } else if (p7certs[1].getIssuerDN().toString().equals(p7certs[0].getSubjectDN().toString())) {
                         assending = false;
-                        CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_CERT_CHAIN_DESC_ORD"));
+                        CMS.debug("UserService: " + CMS.getLogMessage("ADMIN_SRVLT_CERT_CHAIN_DESC_ORD"));
                         cert = p7certs[p7certs.length - 1];
 
                     } else {
                         // not a chain, or in random order
-                        CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_CERT_BAD_CHAIN"));
+                        CMS.debug("UserService: " + CMS.getLogMessage("ADMIN_SRVLT_CERT_BAD_CHAIN"));
                         throw new BadRequestException(getUserMessage("CMS_USRGRP_SRVLT_CERT_ERROR", headers));
                     }
 
-                    CMS.debug("UserCertResourceService: "
+                    CMS.debug("UserService: "
                             + CMS.getLogMessage("ADMIN_SRVLT_CHAIN_STORED_DB", String.valueOf(p7certs.length)));
 
                     int j = 0;
@@ -922,16 +924,17 @@ public class UserService extends PKIService implements UserResource {
 
                     // store the chain into cert db, except for the user cert
                     for (j = jBegin; j < jEnd; j++) {
-                        CMS.debug("UserCertResourceService: "
+                        CMS.debug("UserService: "
                                 + CMS.getLogMessage("ADMIN_SRVLT_CERT_IN_CHAIN", String.valueOf(j),
                                         String.valueOf(p7certs[j].getSubjectDN())));
                         org.mozilla.jss.crypto.X509Certificate leafCert =
                                 manager.importCACertPackage(p7certs[j].getEncoded());
 
                         if (leafCert == null) {
+                            CMS.debug("UserService: missing leaf certificate");
                             log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_LEAF_CERT_NULL"));
                         } else {
-                            CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_LEAF_CERT_NON_NULL"));
+                            CMS.debug("UserService: " + CMS.getLogMessage("ADMIN_SRVLT_LEAF_CERT_NON_NULL"));
                         }
 
                         if (leafCert instanceof InternalCertificate) {
@@ -952,16 +955,19 @@ public class UserService extends PKIService implements UserResource {
                         log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_PKS7_IGNORED", e.toString()));
                     */
                 } catch (PKIException e) {
+                    CMS.debug("UserService: Unable to import user certificate from PKCS #7 data: " + e);
                     log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_CERT_ERROR", e.toString()));
                     throw e;
+
                 } catch (Exception e) {
+                    CMS.debug(e);
                     log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_CERT_ERROR", e.toString()));
-                    throw new PKIException(getUserMessage("CMS_USRGRP_SRVLT_CERT_ERROR", headers));
+                    throw new PKIException("Unable to import user certificate from PKCS #7 data: " + e.getMessage(), e);
                 }
             }
 
             try {
-                CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_BEFORE_VALIDITY"));
+                CMS.debug("UserService: " + CMS.getLogMessage("ADMIN_SRVLT_BEFORE_VALIDITY"));
                 cert.checkValidity(); // throw exception if fails
 
                 user.setX509Certificates(new X509Certificate[] { cert });
@@ -982,24 +988,28 @@ public class UserService extends PKIService implements UserResource {
                 return createCreatedResponse(userCertData, userCertData.getLink().getHref());
 
             } catch (CertificateExpiredException e) {
+                CMS.debug("UserService: Certificate expired: " + e);
                 log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_ADD_CERT_EXPIRED",
                         String.valueOf(cert.getSubjectDN())));
-                throw new BadRequestException(getUserMessage("CMS_USRGRP_SRVLT_CERT_EXPIRED", headers));
+                throw new BadRequestException("Certificate expired: " + e.getMessage(), e);
 
             } catch (CertificateNotYetValidException e) {
+                CMS.debug("UserService: Certificate not yet valid: " + e);
                 log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_CERT_NOT_YET_VALID",
                         String.valueOf(cert.getSubjectDN())));
-                throw new BadRequestException(getUserMessage("CMS_USRGRP_SRVLT_CERT_NOT_YET_VALID", headers));
+                throw new BadRequestException("Certificate not yet valid: " + e.getMessage(), e);
             }
 
         } catch (PKIException e) {
+            CMS.debug("UserService: Unable to import user certificate: " + e);
             auditAddUserCert(userID, userCertData, ILogger.FAILURE);
             throw e;
 
         } catch (Exception e) {
+            CMS.debug(e);
             log(ILogger.LL_FAILURE, e.toString());
             auditAddUserCert(userID, userCertData, ILogger.FAILURE);
-            throw new PKIException(getUserMessage("CMS_USRGRP_USER_MOD_FAILED", headers));
+            throw new PKIException("Unable to import user certificate: " + e.getMessage(), e);
         }
     }
 
diff --git a/base/server/share/webapps/pki/js/pki-ui.js b/base/server/share/webapps/pki/js/pki-ui.js
index a59a283d8d4e4fd5782fa42197384ad12c4ada5b..288461fa20d314581dac5163e7fad8c7e03a76de 100644
--- a/base/server/share/webapps/pki/js/pki-ui.js
+++ b/base/server/share/webapps/pki/js/pki-ui.js
@@ -324,6 +324,12 @@ var Dialog = Backbone.View.extend({
             var input = $(this);
             self.saveField(input);
         });
+
+        // save textareas
+        self.$(".modal-body textarea").each(function(index) {
+            var input = $(this);
+            self.saveField(input);
+        });
     },
     saveField: function(input) {
         var self = this;
diff --git a/base/tps/shared/webapps/tps/js/user.js b/base/tps/shared/webapps/tps/js/user.js
index 49835f0040f47795490a61292a8a8aa27354d2de..e8ed7fc7b84ee010984d117e3645dd08672518cb 100644
--- a/base/tps/shared/webapps/tps/js/user.js
+++ b/base/tps/shared/webapps/tps/js/user.js
@@ -150,6 +150,83 @@ var UserRoleCollection = Collection.extend({
     }
 });
 
+var UserCertModel = Model.extend({
+    url: function() {
+        var self = this;
+
+        var userID = self.get("userID");
+        var url = "/tps/rest/admin/users/" + userID + "/certs";
+
+        if (self.id) url = url + "/" + encodeURIComponent(self.id);
+
+        return url;
+    },
+    parseResponse: function(response) {
+        return {
+            id: response.id,
+            certID: response.id,
+            serialNumber: response.SerialNumber,
+            subjectDN: response.SubjectDN,
+            issuerDN: response.IssuerDN,
+            userID: response.UserID
+        };
+    },
+    createRequest: function(entry) {
+        return {
+            Encoded: entry.encoded
+        };
+    },
+    save: function(attributes, options) {
+        var self = this;
+        var request = self.createRequest(attributes);
+        $.ajax({
+            type: "POST",
+            url: self.url(),
+            dataType: "json",
+            contentType: "application/json",
+            data: JSON.stringify(request),
+        }).done(function(data, textStatus, response) {
+            self.set(self.parseResponse(data));
+            if (options.success) options.success.call(self, self, response, options);
+        }).fail(function(response, textStatus, errorThrown) {
+            if (options.error) options.error.call(self, self, response, options);
+        });
+    }
+});
+
+var UserCertCollection = Collection.extend({
+    initialize: function(models, options) {
+        var self = this;
+        UserCertCollection.__super__.initialize.call(self, models, options);
+        options = options || {};
+        self.userID = options.userID;
+        self.urlRoot = "/tps/rest/admin/users/" + self.userID + "/certs";
+    },
+    getEntries: function(response) {
+        return response.Cert;
+    },
+    getLinks: function(response) {
+        return response.Link;
+    },
+    model: function(attrs, options) {
+        var self = this;
+        return new UserCertModel({
+            userID: self.userID
+        });
+    },
+    parseEntry: function(entry) {
+        var self = this;
+        return new UserCertModel({
+            id: entry.id,
+            certID: entry.id,
+            serialNumber: entry.SerialNumber,
+            subjectDN: entry.SubjectDN,
+            issuerDN: entry.IssuerDN,
+            userID: self.userID
+        });
+    }
+});
+
 var UserProfilesTableItem = TableItem.extend({
     initialize: function(options) {
         var self = this;
@@ -289,6 +366,13 @@ var UserPage = EntryPage.extend({
             e.preventDefault();
             window.location.hash = window.location.hash + "/roles";
         });
+
+        self.showCertsAction = $("[name='showCerts']", self.viewMenu);
+
+        $("a", self.showCertsAction).click(function(e) {
+            e.preventDefault();
+            window.location.hash = window.location.hash + "/certs";
+        });
     },
     saveFields: function() {
         var self = this;
@@ -403,3 +487,32 @@ var UserRolesPage = Page.extend({
         table.render();
     }
 });
+
+var UserCertsPage = Page.extend({
+    load: function() {
+        var self = this;
+
+        if (self.collection && self.collection.options && self.collection.options.userID) {
+            $(".breadcrumb li[name='user'] a")
+                .attr("href", "#users/" + self.collection.options.userID)
+                .text("User " + self.collection.options.userID);
+            $(".pki-title").text("Certificates for User " + self.collection.options.userID);
+        }
+
+        var addCertDialog = new Dialog({
+            el: self.$("#user-cert-dialog"),
+            title: "Add Cert",
+            readonly: ["userID"],
+            actions: ["cancel", "add"]
+        });
+
+        var table = new ModelTable({
+            el: self.$("table[name='certs']"),
+            pageSize: 10,
+            addDialog: addCertDialog,
+            collection: self.collection
+        });
+
+        table.render();
+    }
+});
diff --git a/base/tps/shared/webapps/tps/ui/index.html b/base/tps/shared/webapps/tps/ui/index.html
index 9446cfbf4e0a4e5ff47b69ce8617dc42ddea2b46..f2a9d758457d98234b5baa1cbe85773a509173bb 100644
--- a/base/tps/shared/webapps/tps/ui/index.html
+++ b/base/tps/shared/webapps/tps/ui/index.html
@@ -354,6 +354,14 @@ $(function() {
         }).open();
     });
 
+    router.route("users/:id/certs", "user-certs", function(id) {
+        new UserCertsPage({
+            el: content,
+            url: "user-certs.html",
+            collection: new UserCertCollection(null, { userID: id })
+        }).open();
+    });
+
     router.route("new-user", "new-user", function() {
         new UserPage({
             el: content,
diff --git a/base/tps/shared/webapps/tps/ui/user.html b/base/tps/shared/webapps/tps/ui/user-certs.html
similarity index 65%
copy from base/tps/shared/webapps/tps/ui/user.html
copy to base/tps/shared/webapps/tps/ui/user-certs.html
index 22ca2575a73f7c0fead3a013f48299c96874d928..5837a8c3e315d09b1c2fc81ea67e8409f61b5456 100644
--- a/base/tps/shared/webapps/tps/ui/user.html
+++ b/base/tps/shared/webapps/tps/ui/user-certs.html
@@ -12,53 +12,24 @@
      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.
+     Copyright (C) 2013 Red Hat, Inc.
      All rights reserved.
      --- END COPYRIGHT BLOCK --- -->
 <div class="pki-header">
 <ol class="breadcrumb">
     <li><a href="#">Home</a></li>
     <li><a href="#users">Users</a></li>
-    <li class="active"><strong><span name="title">User ${id}</span></strong></li>
+    <li name="user"><a href="#users/${userID}">User ${id}</a></li>
+    <li class="active"><strong>Certificates</strong></li>
 </ol>
 
-<span name="title" class="pki-title">User ${id}</span>
-
-<span class="pki-actions">
-
-<ul name="view" class="pki-actions-menu">
-<li name="edit"><a href="#">Edit</a></li>
-<li name="showRoles"><a href="#">Show Roles</a></li>
-</ul>
-
-<span name="edit" class="pki-actions-menu" style="display: none;">
-<button name="cancel">Cancel</button>
-<button name="save" class="primary">Save</button>
-</span>
-
-</span>
-
+<span class="pki-title">User Certificates</span>
 </div>
 
-<div name="user" class="pki-fields">
-<fieldset>
-    <label>User ID</label>
-    <input name="userID" readonly="readonly"><br>
-    <label>Full Name</label>
-    <input name="fullName" readonly="readonly"><br>
-    <label>Email</label>
-    <input name="email" readonly="readonly"><br>
-</fieldset>
-</div>
-
-<div name="profiles">
-
-<h2>Profiles</h2>
-
-<table name="list">
+<table name="certs">
 <thead>
     <tr>
-         <th class="pki-table-actions" colspan="2">
+         <th class="pki-table-actions" colspan="10">
              <span name="search">
                  <input name="search" type="text" placeholder="Search...">
              </span>
@@ -69,19 +40,23 @@
          </th>
     </tr>
     <tr>
-        <th class="pki-select-column"><input id="user-profiles-selectall" type="checkbox"><label for="user-profiles-selectall">&nbsp;</label></th>
-        <th>Profile ID</th>
+        <th class="pki-select-column"><input id="certs-selectall" type="checkbox"><label for="certs-selectall">&nbsp;</label></tdh>
+        <th>Serial Number</th>
+        <th>Subject DN</th>
+        <th>Issuer DN</th>
     </tr>
 </thead>
 <tbody>
     <tr>
-        <td class="pki-select-column"><input id="user-profiles-select" type="checkbox"><label for="user-profiles-select">&nbsp;</label></td>
-        <td name="id">${id}</td>
+        <td class="pki-select-column"><input id="certs-select" type="checkbox"><label for="certs-select">&nbsp;</label></td>
+        <td name="serialNumber">${serialNumber}</td>
+        <td name="subjectDN">${subjectDN}</td>
+        <td name="issuerDN">${issuerDN}</td>
     </tr>
 </tbody>
 <tfoot>
     <tr>
-         <th class="pki-table-actions" colspan="2">
+         <th class="pki-table-actions" colspan="10">
              <div class="pki-table-info">
                  Total: <span name="totalEntries">0</span> entries
              </div>
@@ -103,24 +78,26 @@
 </tfoot>
 </table>
 
-</div>
-
-<div id="user-profile-dialog" class="modal">
+<div id="user-cert-dialog" class="modal">
     <div class="modal-dialog">
         <div class="modal-content">
             <div class="modal-header">
                 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">
                     <span class="pficon pficon-close"></span>
                 </button>
-                <h4 class="modal-title">Add Profile</h4>
+                <h4 class="modal-title">User Certificate</h4>
             </div>
             <div class="modal-body">
-                Profile:
-                <select name="id">
-                </select>
+                <fieldset>
+                    <label>User ID</label>
+                    <input name="userID" readonly="readonly"><br>
+                    <label>Certificate</label>
+                    <textarea name="encoded" rows="20" cols="80"></textarea><br>
+                </fieldset>
             </div>
             <div class="modal-footer">
                 <button name="add" class="btn btn-primary">Add</button>
+                <button name="close" class="btn btn-primary">Close</button>
                 <button name="cancel" class="btn btn-default" data-dismiss="modal">Cancel</button>
             </div>
         </div>
diff --git a/base/tps/shared/webapps/tps/ui/user.html b/base/tps/shared/webapps/tps/ui/user.html
index 22ca2575a73f7c0fead3a013f48299c96874d928..792c9703f9813217a7ec17cc8c79998327d3b645 100644
--- a/base/tps/shared/webapps/tps/ui/user.html
+++ b/base/tps/shared/webapps/tps/ui/user.html
@@ -29,6 +29,7 @@
 <ul name="view" class="pki-actions-menu">
 <li name="edit"><a href="#">Edit</a></li>
 <li name="showRoles"><a href="#">Show Roles</a></li>
+<li name="showCerts"><a href="#">Show Certificates</a></li>
 </ul>
 
 <span name="edit" class="pki-actions-menu" style="display: none;">
-- 
2.4.11

