From 5c4c10468c964243c8ac8e5ae02c23ffc05651ae Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Tue, 8 Oct 2013 16:02:19 -0400
Subject: [PATCH] Fixed problems finding user and group sub-resources.

Due to a regression RESTEasy is unable to find some sub-resources properly.
As a workaround some resources need to be merged into the parent resource.
The UserCertResource and UserMembershipResource have been merged into
UserResource. The GroupMemberResource has been merged into GroupResource.
---
 .../ca/CertificateAuthorityApplication.java        |   6 -
 .../com/netscape/certsrv/group/GroupClient.java    |  10 +-
 .../certsrv/group/GroupMemberResource.java         |  67 ---
 .../com/netscape/certsrv/group/GroupResource.java  |  25 +
 .../netscape/certsrv/user/UserCertResource.java    |  68 ---
 .../src/com/netscape/certsrv/user/UserClient.java  |  18 +-
 .../certsrv/user/UserMembershipResource.java       |  62 ---
 .../com/netscape/certsrv/user/UserResource.java    |  46 ++
 .../cms/servlet/admin/GroupMemberProcessor.java    |   6 +-
 .../cms/servlet/admin/GroupMemberService.java      | 117 -----
 .../netscape/cms/servlet/admin/GroupService.java   |  69 +++
 .../cms/servlet/admin/UserCertService.java         | 508 ------------------
 .../cms/servlet/admin/UserMembershipService.java   | 189 -------
 .../netscape/cms/servlet/admin/UserService.java    | 566 ++++++++++++++++++++-
 .../kra/KeyRecoveryAuthorityApplication.java       |   6 -
 .../src/com/netscape/ocsp/OCSPApplication.java     |   6 -
 base/tks/src/com/netscape/tks/TKSApplication.java  |   6 -
 .../org/dogtagpki/server/tps/TPSApplication.java   |   6 -
 18 files changed, 716 insertions(+), 1065 deletions(-)
 delete mode 100644 base/common/src/com/netscape/certsrv/group/GroupMemberResource.java
 delete mode 100644 base/common/src/com/netscape/certsrv/user/UserCertResource.java
 delete mode 100644 base/common/src/com/netscape/certsrv/user/UserMembershipResource.java
 delete mode 100644 base/common/src/com/netscape/cms/servlet/admin/GroupMemberService.java
 delete mode 100644 base/common/src/com/netscape/cms/servlet/admin/UserCertService.java
 delete mode 100644 base/common/src/com/netscape/cms/servlet/admin/UserMembershipService.java

diff --git a/base/ca/src/com/netscape/ca/CertificateAuthorityApplication.java b/base/ca/src/com/netscape/ca/CertificateAuthorityApplication.java
index 478376c65ed7a9d1295867c94958f32e5317af7a..b26182ddabc0e7ad4e4e49e89232054754e08473 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthorityApplication.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthorityApplication.java
@@ -12,12 +12,9 @@ import com.netscape.certsrv.base.PKIException;
 import com.netscape.cms.authorization.ACLInterceptor;
 import com.netscape.cms.authorization.AuthMethodInterceptor;
 import com.netscape.cms.servlet.account.AccountService;
-import com.netscape.cms.servlet.admin.GroupMemberService;
 import com.netscape.cms.servlet.admin.GroupService;
 import com.netscape.cms.servlet.admin.KRAConnectorService;
 import com.netscape.cms.servlet.admin.SystemCertService;
-import com.netscape.cms.servlet.admin.UserCertService;
-import com.netscape.cms.servlet.admin.UserMembershipService;
 import com.netscape.cms.servlet.admin.UserService;
 import com.netscape.cms.servlet.cert.CertService;
 import com.netscape.cms.servlet.csadmin.SecurityDomainService;
@@ -53,10 +50,7 @@ public class CertificateAuthorityApplication extends Application {
         classes.add(SelfTestService.class);
 
         // user and group management
-        classes.add(GroupMemberService.class);
         classes.add(GroupService.class);
-        classes.add(UserCertService.class);
-        classes.add(UserMembershipService.class);
         classes.add(UserService.class);
 
         // system certs
diff --git a/base/common/src/com/netscape/certsrv/group/GroupClient.java b/base/common/src/com/netscape/certsrv/group/GroupClient.java
index 2f11e21b46205d57180567251cb086ae8bd56c2f..7470349f481f1f1a1f58cb499fe65b0b98a5dfa3 100644
--- a/base/common/src/com/netscape/certsrv/group/GroupClient.java
+++ b/base/common/src/com/netscape/certsrv/group/GroupClient.java
@@ -30,7 +30,6 @@ import com.netscape.certsrv.client.PKIClient;
 public class GroupClient extends Client {
 
     public GroupResource groupClient;
-    public GroupMemberResource groupMemberClient;
 
     public GroupClient(PKIClient client, String subsystem) throws URISyntaxException {
         super(client, subsystem, "group");
@@ -39,7 +38,6 @@ public class GroupClient extends Client {
 
     public void init() throws URISyntaxException {
         groupClient = createProxy(GroupResource.class);
-        groupMemberClient = createProxy(GroupMemberResource.class);
     }
 
     public GroupCollection findGroups(String groupIDFilter, Integer start, Integer size) {
@@ -67,20 +65,20 @@ public class GroupClient extends Client {
     }
 
     public GroupMemberCollection findGroupMembers(String groupID, Integer start, Integer size) {
-        return groupMemberClient.findGroupMembers(groupID, start, size);
+        return groupClient.findGroupMembers(groupID, start, size);
     }
 
     public GroupMemberData getGroupMember(String groupID, String memberID) {
-        return groupMemberClient.getGroupMember(groupID, memberID);
+        return groupClient.getGroupMember(groupID, memberID);
     }
 
     public GroupMemberData addGroupMember(String groupID, String memberID) {
         @SuppressWarnings("unchecked")
-        ClientResponse<GroupMemberData> response = (ClientResponse<GroupMemberData>)groupMemberClient.addGroupMember(groupID, memberID);
+        ClientResponse<GroupMemberData> response = (ClientResponse<GroupMemberData>)groupClient.addGroupMember(groupID, memberID);
         return client.getEntity(response);
     }
 
     public void removeGroupMember(String groupID, String memberID) {
-        groupMemberClient.removeGroupMember(groupID, memberID);
+        groupClient.removeGroupMember(groupID, memberID);
     }
 }
diff --git a/base/common/src/com/netscape/certsrv/group/GroupMemberResource.java b/base/common/src/com/netscape/certsrv/group/GroupMemberResource.java
deleted file mode 100644
index cd4d2eb24f3f439517f090515efcaaeae0022f5c..0000000000000000000000000000000000000000
--- a/base/common/src/com/netscape/certsrv/group/GroupMemberResource.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// --- 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.
-//
-// (C) 2012 Red Hat, Inc.
-// All rights reserved.
-// --- END COPYRIGHT BLOCK ---
-
-package com.netscape.certsrv.group;
-
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.jboss.resteasy.annotations.ClientResponseType;
-
-import com.netscape.certsrv.acls.ACLMapping;
-import com.netscape.certsrv.authentication.AuthMethodMapping;
-
-/**
- * @author Endi S. Dewata
- */
-@Path("admin/groups/{groupID}/members")
-@ACLMapping("admin.groups")
-@AuthMethodMapping("admin")
-public interface GroupMemberResource {
-
-    @GET
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    public GroupMemberCollection findGroupMembers(
-            @PathParam("groupID") String groupID,
-            @QueryParam("start") Integer start,
-            @QueryParam("size") Integer size);
-
-    @POST
-    @ClientResponseType(entityType=GroupMemberData.class)
-    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    public Response addGroupMember(@PathParam("groupID") String groupID, String memberID);
-
-    @GET
-    @Path("{memberID}")
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    public GroupMemberData getGroupMember(@PathParam("groupID") String groupID, @PathParam("memberID") String memberID);
-
-    @DELETE
-    @Path("{memberID}")
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    public void removeGroupMember(@PathParam("groupID") String groupID, @PathParam("memberID") String memberID);
-}
diff --git a/base/common/src/com/netscape/certsrv/group/GroupResource.java b/base/common/src/com/netscape/certsrv/group/GroupResource.java
index ffe32e2cdcc23ea13ed2e9d69dbbb7f885c3d8d0..ea59922a5bbd6cb151b0e2464fccc45367c347fc 100644
--- a/base/common/src/com/netscape/certsrv/group/GroupResource.java
+++ b/base/common/src/com/netscape/certsrv/group/GroupResource.java
@@ -71,4 +71,29 @@ public interface GroupResource {
     @Path("{groupID}")
     @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     public void removeGroup(@PathParam("groupID") String groupID);
+
+    @GET
+    @Path("{groupID}/members")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public GroupMemberCollection findGroupMembers(
+            @PathParam("groupID") String groupID,
+            @QueryParam("start") Integer start,
+            @QueryParam("size") Integer size);
+
+    @POST
+    @Path("{groupID}/members")
+    @ClientResponseType(entityType=GroupMemberData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public Response addGroupMember(@PathParam("groupID") String groupID, String memberID);
+
+    @GET
+    @Path("{groupID}/members/{memberID}")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public GroupMemberData getGroupMember(@PathParam("groupID") String groupID, @PathParam("memberID") String memberID);
+
+    @DELETE
+    @Path("{groupID}/members/{memberID}")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public void removeGroupMember(@PathParam("groupID") String groupID, @PathParam("memberID") String memberID);
 }
diff --git a/base/common/src/com/netscape/certsrv/user/UserCertResource.java b/base/common/src/com/netscape/certsrv/user/UserCertResource.java
deleted file mode 100644
index 81133df3bf962a4db2988918eb4d7c3e3ab6d66f..0000000000000000000000000000000000000000
--- a/base/common/src/com/netscape/certsrv/user/UserCertResource.java
+++ /dev/null
@@ -1,68 +0,0 @@
-// --- 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.
-//
-// (C) 2012 Red Hat, Inc.
-// All rights reserved.
-// --- END COPYRIGHT BLOCK ---
-
-package com.netscape.certsrv.user;
-
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.jboss.resteasy.annotations.ClientResponseType;
-
-import com.netscape.certsrv.acls.ACLMapping;
-import com.netscape.certsrv.authentication.AuthMethodMapping;
-
-/**
- * @author Endi S. Dewata
- */
-@Path("admin/users/{userID}/certs")
-@ACLMapping("admin.users")
-@AuthMethodMapping("admin")
-public interface UserCertResource {
-
-    @GET
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    public UserCertCollection findUserCerts(
-            @PathParam("userID") String userID,
-            @QueryParam("start") Integer start,
-            @QueryParam("size") Integer size);
-
-
-    @POST
-    @ClientResponseType(entityType=UserCertData.class)
-    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    public Response addUserCert(@PathParam("userID") String userID, UserCertData userCertData);
-
-    @GET
-    @Path("{certID}")
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    public UserCertData getUserCert(@PathParam("userID") String userID, @PathParam("certID") String certID);
-
-    @DELETE
-    @Path("{certID}")
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    public void removeUserCert(@PathParam("userID") String userID, @PathParam("certID") String certID);
-}
diff --git a/base/common/src/com/netscape/certsrv/user/UserClient.java b/base/common/src/com/netscape/certsrv/user/UserClient.java
index 85b7f059209e53a12274ae46b237222054ce7871..59de6465229605f46abe876d1b49fe69fd6798ff 100644
--- a/base/common/src/com/netscape/certsrv/user/UserClient.java
+++ b/base/common/src/com/netscape/certsrv/user/UserClient.java
@@ -30,8 +30,6 @@ import com.netscape.certsrv.client.PKIClient;
 public class UserClient extends Client {
 
     public UserResource userClient;
-    public UserCertResource userCertClient;
-    public UserMembershipResource userMembershipClient;
 
     public UserClient(PKIClient client, String subsystem) throws URISyntaxException {
         super(client, subsystem, "user");
@@ -40,8 +38,6 @@ public class UserClient extends Client {
 
     public void init() throws URISyntaxException {
         userClient = createProxy(UserResource.class);
-        userCertClient = createProxy(UserCertResource.class);
-        userMembershipClient = createProxy(UserMembershipResource.class);
     }
 
     public UserCollection findUsers(String filter, Integer start, Integer size) {
@@ -69,34 +65,34 @@ public class UserClient extends Client {
     }
 
     public UserCertCollection findUserCerts(String userID, Integer start, Integer size) {
-        return userCertClient.findUserCerts(userID, start, size);
+        return userClient.findUserCerts(userID, start, size);
     }
 
     public UserCertData getUserCert(String userID, String certID) {
-        return userCertClient.getUserCert(userID, certID);
+        return userClient.getUserCert(userID, certID);
     }
 
     public UserCertData addUserCert(String userID, UserCertData userCertData) {
         @SuppressWarnings("unchecked")
-        ClientResponse<UserCertData> response = (ClientResponse<UserCertData>)userCertClient.addUserCert(userID, userCertData);
+        ClientResponse<UserCertData> response = (ClientResponse<UserCertData>)userClient.addUserCert(userID, userCertData);
         return client.getEntity(response);
     }
 
     public void removeUserCert(String userID, String certID) {
-        userCertClient.removeUserCert(userID, certID);
+        userClient.removeUserCert(userID, certID);
     }
 
     public UserMembershipCollection findUserMemberships(String userID, Integer start, Integer size) {
-        return userMembershipClient.findUserMemberships(userID, start, size);
+        return userClient.findUserMemberships(userID, start, size);
     }
 
     public UserMembershipData addUserMembership(String userID, String groupID) {
         @SuppressWarnings("unchecked")
-        ClientResponse<UserMembershipData> response = (ClientResponse<UserMembershipData>)userMembershipClient.addUserMembership(userID, groupID);
+        ClientResponse<UserMembershipData> response = (ClientResponse<UserMembershipData>)userClient.addUserMembership(userID, groupID);
         return client.getEntity(response);
     }
 
     public void removeUserMembership(String userD, String groupID) {
-        userMembershipClient.removeUserMembership(userD, groupID);
+        userClient.removeUserMembership(userD, groupID);
     }
 }
diff --git a/base/common/src/com/netscape/certsrv/user/UserMembershipResource.java b/base/common/src/com/netscape/certsrv/user/UserMembershipResource.java
deleted file mode 100644
index 665a419e36caee1dd54ca503720fd262cb5a89b6..0000000000000000000000000000000000000000
--- a/base/common/src/com/netscape/certsrv/user/UserMembershipResource.java
+++ /dev/null
@@ -1,62 +0,0 @@
-// --- 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.
-//
-// (C) 2013 Red Hat, Inc.
-// All rights reserved.
-// --- END COPYRIGHT BLOCK ---
-
-package com.netscape.certsrv.user;
-
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.jboss.resteasy.annotations.ClientResponseType;
-
-import com.netscape.certsrv.acls.ACLMapping;
-import com.netscape.certsrv.authentication.AuthMethodMapping;
-
-/**
- * @author Endi S. Dewata
- */
-@Path("admin/users/{userID}/memberships")
-@ACLMapping("admin.users")
-@AuthMethodMapping("admin")
-public interface UserMembershipResource {
-
-    @GET
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    public UserMembershipCollection findUserMemberships(
-            @PathParam("userID") String userID,
-            @QueryParam("start") Integer start,
-            @QueryParam("size") Integer size);
-
-    @POST
-    @ClientResponseType(entityType=UserMembershipData.class)
-    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    public Response addUserMembership(@PathParam("userID") String userID, String groupID);
-
-    @DELETE
-    @Path("{groupID}")
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    public void removeUserMembership(@PathParam("userID") String userID, @PathParam("groupID") String groupID);
-}
diff --git a/base/common/src/com/netscape/certsrv/user/UserResource.java b/base/common/src/com/netscape/certsrv/user/UserResource.java
index a0f5f1db599c1076fea6710f06253a4f79b63423..961f5ebda913f7461ce5f4042e46654b7c6c9be6 100644
--- a/base/common/src/com/netscape/certsrv/user/UserResource.java
+++ b/base/common/src/com/netscape/certsrv/user/UserResource.java
@@ -74,4 +74,50 @@ public interface UserResource {
     @Path("{userID}")
     @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     public void removeUser(@PathParam("userID") String userID);
+
+    @GET
+    @Path("{userID}/certs")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public UserCertCollection findUserCerts(
+            @PathParam("userID") String userID,
+            @QueryParam("start") Integer start,
+            @QueryParam("size") Integer size);
+
+
+    @POST
+    @Path("{userID}/certs")
+    @ClientResponseType(entityType=UserCertData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public Response addUserCert(@PathParam("userID") String userID, UserCertData userCertData);
+
+    @GET
+    @Path("{userID}/certs/{certID}")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public UserCertData getUserCert(@PathParam("userID") String userID, @PathParam("certID") String certID);
+
+    @DELETE
+    @Path("{userID}/certs/{certID}")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public void removeUserCert(@PathParam("userID") String userID, @PathParam("certID") String certID);
+
+    @GET
+    @Path("{userID}/memberships")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public UserMembershipCollection findUserMemberships(
+            @PathParam("userID") String userID,
+            @QueryParam("start") Integer start,
+            @QueryParam("size") Integer size);
+
+    @POST
+    @Path("{userID}/memberships")
+    @ClientResponseType(entityType=UserMembershipData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public Response addUserMembership(@PathParam("userID") String userID, String groupID);
+
+    @DELETE
+    @Path("{userID}/memberships/{groupID}")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public void removeUserMembership(@PathParam("userID") String userID, @PathParam("groupID") String groupID);
 }
diff --git a/base/common/src/com/netscape/cms/servlet/admin/GroupMemberProcessor.java b/base/common/src/com/netscape/cms/servlet/admin/GroupMemberProcessor.java
index bb8b73c10117964d4c5e37db123ef89488df4786..399b97d0c324d526958975a10228d097805ed534 100644
--- a/base/common/src/com/netscape/cms/servlet/admin/GroupMemberProcessor.java
+++ b/base/common/src/com/netscape/cms/servlet/admin/GroupMemberProcessor.java
@@ -41,8 +41,8 @@ import com.netscape.certsrv.common.OpDef;
 import com.netscape.certsrv.common.ScopeDef;
 import com.netscape.certsrv.group.GroupMemberCollection;
 import com.netscape.certsrv.group.GroupMemberData;
-import com.netscape.certsrv.group.GroupMemberResource;
 import com.netscape.certsrv.group.GroupNotFoundException;
+import com.netscape.certsrv.group.GroupResource;
 import com.netscape.certsrv.logging.AuditFormat;
 import com.netscape.certsrv.logging.IAuditor;
 import com.netscape.certsrv.logging.ILogger;
@@ -85,8 +85,8 @@ public class GroupMemberProcessor extends Processor {
         groupMemberData.setGroupID(groupID);
 
         URI uri = uriInfo.getBaseUriBuilder()
-                .path(GroupMemberResource.class)
-                .path("{userID}")
+                .path(GroupResource.class)
+                .path("{groupID}/members/{memberID}")
                 .build(
                         URLEncoder.encode(groupID, "UTF-8"),
                         URLEncoder.encode(memberID, "UTF-8"));
diff --git a/base/common/src/com/netscape/cms/servlet/admin/GroupMemberService.java b/base/common/src/com/netscape/cms/servlet/admin/GroupMemberService.java
deleted file mode 100644
index 88702708cb28e9acca40a0c5dcbf64ab0b7bbba7..0000000000000000000000000000000000000000
--- a/base/common/src/com/netscape/cms/servlet/admin/GroupMemberService.java
+++ /dev/null
@@ -1,117 +0,0 @@
-// --- 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.
-//
-// (C) 2012 Red Hat, Inc.
-// All rights reserved.
-// --- END COPYRIGHT BLOCK ---
-
-package com.netscape.cms.servlet.admin;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.Request;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-
-import com.netscape.certsrv.base.PKIException;
-import com.netscape.certsrv.group.GroupMemberCollection;
-import com.netscape.certsrv.group.GroupMemberData;
-import com.netscape.certsrv.group.GroupMemberResource;
-import com.netscape.cms.servlet.base.PKIService;
-
-/**
- * @author Endi S. Dewata
- */
-public class GroupMemberService extends PKIService implements GroupMemberResource {
-
-    @Context
-    private UriInfo uriInfo;
-
-    @Context
-    private HttpHeaders headers;
-
-    @Context
-    private Request request;
-
-    @Context
-    private HttpServletRequest servletRequest;
-
-    @Override
-    public GroupMemberCollection findGroupMembers(String groupID, Integer start, Integer size) {
-        try {
-            GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
-            processor.setUriInfo(uriInfo);
-            return processor.findGroupMembers(groupID, start, size);
-
-        } catch (PKIException e) {
-            throw e;
-
-        } catch (Exception e) {
-            throw new PKIException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public GroupMemberData getGroupMember(String groupID, String memberID) {
-        try {
-            GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
-            processor.setUriInfo(uriInfo);
-            return processor.getGroupMember(groupID, memberID);
-
-        } catch (PKIException e) {
-            throw e;
-
-        } catch (Exception e) {
-            throw new PKIException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public Response addGroupMember(String groupID, String memberID) {
-        GroupMemberData groupMemberData = new GroupMemberData();
-        groupMemberData.setID(memberID);
-        groupMemberData.setGroupID(groupID);
-        return addGroupMember(groupMemberData);
-    }
-
-    public Response addGroupMember(GroupMemberData groupMemberData) {
-        try {
-            GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
-            processor.setUriInfo(uriInfo);
-            return processor.addGroupMember(groupMemberData);
-
-        } catch (PKIException e) {
-            throw e;
-
-        } catch (Exception e) {
-            throw new PKIException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public void removeGroupMember(String groupID, String memberID) {
-        try {
-            GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
-            processor.setUriInfo(uriInfo);
-            processor.removeGroupMember(groupID, memberID);
-
-        } catch (PKIException e) {
-            throw e;
-
-        } catch (Exception e) {
-            throw new PKIException(e.getMessage(), e);
-        }
-    }
-}
diff --git a/base/common/src/com/netscape/cms/servlet/admin/GroupService.java b/base/common/src/com/netscape/cms/servlet/admin/GroupService.java
index 69573549f229c69e018a309f221391ea71a96c31..91fec08c417047d2fe37aa1a05bdb71dcf05fc78 100644
--- a/base/common/src/com/netscape/cms/servlet/admin/GroupService.java
+++ b/base/common/src/com/netscape/cms/servlet/admin/GroupService.java
@@ -42,6 +42,8 @@ import com.netscape.certsrv.common.OpDef;
 import com.netscape.certsrv.common.ScopeDef;
 import com.netscape.certsrv.group.GroupCollection;
 import com.netscape.certsrv.group.GroupData;
+import com.netscape.certsrv.group.GroupMemberCollection;
+import com.netscape.certsrv.group.GroupMemberData;
 import com.netscape.certsrv.group.GroupNotFoundException;
 import com.netscape.certsrv.group.GroupResource;
 import com.netscape.certsrv.logging.IAuditor;
@@ -332,6 +334,73 @@ public class GroupService extends PKIService implements GroupResource {
         }
     }
 
+    @Override
+    public GroupMemberCollection findGroupMembers(String groupID, Integer start, Integer size) {
+        try {
+            GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
+            processor.setUriInfo(uriInfo);
+            return processor.findGroupMembers(groupID, start, size);
+
+        } catch (PKIException e) {
+            throw e;
+
+        } catch (Exception e) {
+            throw new PKIException(e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public GroupMemberData getGroupMember(String groupID, String memberID) {
+        try {
+            GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
+            processor.setUriInfo(uriInfo);
+            return processor.getGroupMember(groupID, memberID);
+
+        } catch (PKIException e) {
+            throw e;
+
+        } catch (Exception e) {
+            throw new PKIException(e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public Response addGroupMember(String groupID, String memberID) {
+        GroupMemberData groupMemberData = new GroupMemberData();
+        groupMemberData.setID(memberID);
+        groupMemberData.setGroupID(groupID);
+        return addGroupMember(groupMemberData);
+    }
+
+    public Response addGroupMember(GroupMemberData groupMemberData) {
+        try {
+            GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
+            processor.setUriInfo(uriInfo);
+            return processor.addGroupMember(groupMemberData);
+
+        } catch (PKIException e) {
+            throw e;
+
+        } catch (Exception e) {
+            throw new PKIException(e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public void removeGroupMember(String groupID, String memberID) {
+        try {
+            GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
+            processor.setUriInfo(uriInfo);
+            processor.removeGroupMember(groupID, memberID);
+
+        } catch (PKIException e) {
+            throw e;
+
+        } catch (Exception e) {
+            throw new PKIException(e.getMessage(), e);
+        }
+    }
+
     public void log(int level, String message) {
         log(ILogger.S_USRGRP, level, message);
     }
diff --git a/base/common/src/com/netscape/cms/servlet/admin/UserCertService.java b/base/common/src/com/netscape/cms/servlet/admin/UserCertService.java
deleted file mode 100644
index 374c8616a39e197147bf3577b96d893aea20b3d4..0000000000000000000000000000000000000000
--- a/base/common/src/com/netscape/cms/servlet/admin/UserCertService.java
+++ /dev/null
@@ -1,508 +0,0 @@
-// --- 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.
-//
-// (C) 2012 Red Hat, Inc.
-// All rights reserved.
-// --- END COPYRIGHT BLOCK ---
-
-package com.netscape.cms.servlet.admin;
-
-import java.net.URI;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.X509Certificate;
-import java.util.Map;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Request;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-
-import netscape.ldap.LDAPException;
-import netscape.security.pkcs.PKCS7;
-import netscape.security.x509.X509CertImpl;
-
-import org.jboss.resteasy.plugins.providers.atom.Link;
-import org.mozilla.jss.CryptoManager;
-import org.mozilla.jss.crypto.InternalCertificate;
-
-import com.netscape.certsrv.apps.CMS;
-import com.netscape.certsrv.base.BadRequestException;
-import com.netscape.certsrv.base.ICertPrettyPrint;
-import com.netscape.certsrv.base.PKIException;
-import com.netscape.certsrv.base.ResourceNotFoundException;
-import com.netscape.certsrv.base.UserNotFoundException;
-import com.netscape.certsrv.common.OpDef;
-import com.netscape.certsrv.common.ScopeDef;
-import com.netscape.certsrv.dbs.certdb.CertId;
-import com.netscape.certsrv.logging.IAuditor;
-import com.netscape.certsrv.logging.ILogger;
-import com.netscape.certsrv.user.UserCertCollection;
-import com.netscape.certsrv.user.UserCertData;
-import com.netscape.certsrv.user.UserCertResource;
-import com.netscape.certsrv.usrgrp.IUGSubsystem;
-import com.netscape.certsrv.usrgrp.IUser;
-import com.netscape.cms.servlet.base.PKIService;
-import com.netscape.cmsutil.util.Cert;
-import com.netscape.cmsutil.util.Utils;
-
-/**
- * @author Endi S. Dewata
- */
-public class UserCertService extends PKIService implements UserCertResource {
-
-    @Context
-    private UriInfo uriInfo;
-
-    @Context
-    private HttpHeaders headers;
-
-    @Context
-    private Request request;
-
-    @Context
-    private HttpServletRequest servletRequest;
-
-    public final static int DEFAULT_SIZE = 20;
-
-    public IUGSubsystem userGroupManager = (IUGSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_UG);
-
-    public UserCertData createUserCertData(String userID, X509Certificate cert) throws Exception {
-
-        UserCertData userCertData = new UserCertData();
-
-        userCertData.setVersion(cert.getVersion());
-        userCertData.setSerialNumber(new CertId(cert.getSerialNumber()));
-        userCertData.setIssuerDN(cert.getIssuerDN().toString());
-        userCertData.setSubjectDN(cert.getSubjectDN().toString());
-
-        userID = URLEncoder.encode(userID, "UTF-8");
-        String certID = URLEncoder.encode(userCertData.getID(), "UTF-8");
-        URI uri = uriInfo.getBaseUriBuilder().path(UserCertResource.class).path("{certID}").build(userID, certID);
-        userCertData.setLink(new Link("self", uri));
-
-        return userCertData;
-    }
-
-    /**
-     * List user certificate(s)
-     *
-     * Request/Response Syntax:
-     * http://warp.mcom.com/server/certificate/columbo/design/
-     * ui/admin-protocol-definition.html#user-admin
-     */
-    @Override
-    public UserCertCollection findUserCerts(String userID, Integer start, Integer size) {
-        try {
-            start = start == null ? 0 : start;
-            size = size == null ? DEFAULT_SIZE : size;
-
-            if (userID == null) {
-                log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_NULL_RS_ID"));
-                throw new BadRequestException(getUserMessage("CMS_ADMIN_SRVLT_NULL_RS_ID", headers));
-            }
-
-            IUser user = null;
-
-            try {
-                user = userGroupManager.getUser(userID);
-            } catch (Exception e) {
-                throw new PKIException(getUserMessage("CMS_USRGRP_SRVLT_USER_NOT_EXIST", headers));
-            }
-
-            if (user == null) {
-                log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_USER_NOT_EXIST"));
-                throw new UserNotFoundException(userID);
-            }
-
-            UserCertCollection response = new UserCertCollection();
-
-            X509Certificate[] certs = user.getX509Certificates();
-            if (certs != null) {
-                for (int i=start; i<start+size && i<certs.length; i++) {
-                    X509Certificate cert = certs[i];
-                    response.addCert(createUserCertData(userID, cert));
-                }
-
-                if (start > 0) {
-                    URI uri = uriInfo.getRequestUriBuilder().replaceQueryParam("start", Math.max(start-size, 0)).build();
-                    response.addLink(new Link("prev", uri));
-                }
-
-                if (start+size < certs.length) {
-                    URI uri = uriInfo.getRequestUriBuilder().replaceQueryParam("start", start+size).build();
-                    response.addLink(new Link("next", uri));
-                }
-            }
-
-            return response;
-
-        } catch (PKIException e) {
-            throw e;
-
-        } catch (Exception e) {
-            throw new PKIException(e.getMessage());
-        }
-    }
-
-    @Override
-    public UserCertData getUserCert(String userID, String certID) {
-        try {
-            if (userID == null) {
-                log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_NULL_RS_ID"));
-
-                throw new BadRequestException(getUserMessage("CMS_ADMIN_SRVLT_NULL_RS_ID", headers));
-            }
-
-            IUser user = null;
-
-            try {
-                user = userGroupManager.getUser(userID);
-            } catch (Exception e) {
-                throw new PKIException(getUserMessage("CMS_USRGRP_SRVLT_USER_NOT_EXIST", headers));
-            }
-
-            if (user == null) {
-                log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_USER_NOT_EXIST"));
-                throw new UserNotFoundException(userID);
-            }
-
-            X509Certificate[] certs = user.getX509Certificates();
-
-            if (certs == null) {
-                throw new ResourceNotFoundException("No certificates found for " + userID);
-            }
-
-            try {
-                certID = URLDecoder.decode(certID, "UTF-8");
-            } catch (Exception e) {
-                throw new PKIException(e.getMessage());
-            }
-
-            for (X509Certificate cert : certs) {
-
-                UserCertData userCertData = createUserCertData(userID, cert);
-
-                if (!userCertData.getID().equals(certID)) continue;
-
-                ICertPrettyPrint print = CMS.getCertPrettyPrint(cert);
-                userCertData.setPrettyPrint(print.toString(getLocale(headers)));
-
-                // add base64 encoding
-                String base64 = CMS.getEncodedCert(cert);
-                userCertData.setEncoded(base64);
-
-                return userCertData;
-            }
-
-            throw new ResourceNotFoundException("No certificates found for " + userID);
-
-        } catch (PKIException e) {
-            throw e;
-
-        } catch (Exception e) {
-            throw new PKIException(e.getMessage());
-        }
-    }
-
-    /**
-     * Adds a certificate to a user
-     * <P>
-     *
-     * Request/Response Syntax: http://warp.mcom.com/server/certificate/columbo/design/
-     * ui/admin-protocol-definition.html#user-admin
-     * <P>
-     *
-     * <ul>
-     * <li>signed.audit LOGGING_SIGNED_AUDIT_CONFIG_ROLE used when configuring role information (anything under
-     * users/groups)
-     * </ul>
-     */
-    @Override
-    public Response addUserCert(String userID, UserCertData userCertData) {
-
-        // ensure that any low-level exceptions are reported
-        // to the signed audit log and stored as failures
-        try {
-            if (userID == null) {
-                log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_NULL_RS_ID"));
-                throw new BadRequestException(getUserMessage("CMS_ADMIN_SRVLT_NULL_RS_ID", headers));
-            }
-
-            IUser user = userGroupManager.createUser(userID);
-
-            String encoded = userCertData.getEncoded();
-            encoded = Cert.normalizeCertStrAndReq(encoded);
-            encoded = Cert.stripBrackets(encoded);
-
-            // no cert is a success
-            if (encoded == null) {
-                auditAddUserCert(userID, userCertData, ILogger.SUCCESS);
-                return Response.ok().build();
-            }
-
-            // only one cert added per operation
-            X509Certificate cert = null;
-
-            // Base64 decode cert
-            byte binaryCert[] = Utils.base64decode(encoded);
-
-            try {
-                cert = new X509CertImpl(binaryCert);
-
-            } catch (CertificateException e) {
-                // ignore
-            }
-
-            if (cert == null) {
-                // cert chain direction
-                boolean assending = true;
-
-                // could it be a pkcs7 blob?
-                CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_IS_PK_BLOB"));
-
-                try {
-                    CryptoManager manager = CryptoManager.getInstance();
-
-                    PKCS7 pkcs7 = new PKCS7(binaryCert);
-
-                    X509Certificate p7certs[] = pkcs7.getCertificates();
-
-                    if (p7certs.length == 0) {
-                        throw new BadRequestException(getUserMessage("CMS_USRGRP_SRVLT_CERT_ERROR", headers));
-                    }
-
-                    // fix for 370099 - cert ordering can not be assumed
-                    // find out the ordering ...
-
-                    // self-signed and alone? take it. otherwise test
-                    // the ordering
-                    if (p7certs[0].getSubjectDN().toString().equals(
-                            p7certs[0].getIssuerDN().toString()) &&
-                            (p7certs.length == 1)) {
-                        cert = p7certs[0];
-                        CMS.debug("UserCertResourceService: " + 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"));
-
-                    } else if (p7certs[1].getIssuerDN().toString().equals(p7certs[0].getSubjectDN().toString())) {
-                        assending = false;
-                        CMS.debug("UserCertResourceService: " + 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"));
-                        throw new BadRequestException(getUserMessage("CMS_USRGRP_SRVLT_CERT_ERROR", headers));
-                    }
-
-                    CMS.debug("UserCertResourceService: "
-                            + CMS.getLogMessage("ADMIN_SRVLT_CHAIN_STORED_DB", String.valueOf(p7certs.length)));
-
-                    int j = 0;
-                    int jBegin = 0;
-                    int jEnd = 0;
-
-                    if (assending == true) {
-                        jBegin = 1;
-                        jEnd = p7certs.length;
-                    } else {
-                        jBegin = 0;
-                        jEnd = p7certs.length - 1;
-                    }
-
-                    // store the chain into cert db, except for the user cert
-                    for (j = jBegin; j < jEnd; j++) {
-                        CMS.debug("UserCertResourceService: "
-                                + 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) {
-                            log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_LEAF_CERT_NULL"));
-                        } else {
-                            CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_LEAF_CERT_NON_NULL"));
-                        }
-
-                        if (leafCert instanceof InternalCertificate) {
-                            ((InternalCertificate) leafCert).setSSLTrust(
-                                    InternalCertificate.VALID_CA |
-                                            InternalCertificate.TRUSTED_CA |
-                                            InternalCertificate.TRUSTED_CLIENT_CA);
-                        } else {
-                            log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_NOT_INTERNAL_CERT",
-                                    String.valueOf(p7certs[j].getSubjectDN())));
-                        }
-                    }
-
-                    /*
-                    } catch (CryptoManager.UserCertConflictException e) {
-                        // got a "user cert" in the chain, most likely the CA
-                        // cert of this instance, which has a private key.  Ignore
-                        log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_PKS7_IGNORED", e.toString()));
-                    */
-                } catch (PKIException e) {
-                    log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_CERT_ERROR", e.toString()));
-                    throw e;
-                } catch (Exception e) {
-                    log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_CERT_ERROR", e.toString()));
-                    throw new PKIException(getUserMessage("CMS_USRGRP_SRVLT_CERT_ERROR", headers));
-                }
-            }
-
-            try {
-                CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_BEFORE_VALIDITY"));
-                cert.checkValidity(); // throw exception if fails
-
-                user.setX509Certificates(new X509Certificate[] { cert });
-                userGroupManager.addUserCert(user);
-
-                auditAddUserCert(userID, userCertData, ILogger.SUCCESS);
-
-                // read the data back
-
-                userCertData.setVersion(cert.getVersion());
-                userCertData.setSerialNumber(new CertId(cert.getSerialNumber()));
-                userCertData.setIssuerDN(cert.getIssuerDN().toString());
-                userCertData.setSubjectDN(cert.getSubjectDN().toString());
-                String certID = userCertData.getID();
-
-                userCertData = getUserCert(userID, URLEncoder.encode(certID, "UTF-8"));
-
-                return Response
-                        .created(userCertData.getLink().getHref())
-                        .entity(userCertData)
-                        .type(MediaType.APPLICATION_XML)
-                        .build();
-
-            } catch (CertificateExpiredException 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));
-
-            } catch (CertificateNotYetValidException 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));
-
-            } catch (LDAPException e) {
-                if (e.getLDAPResultCode() == LDAPException.ATTRIBUTE_OR_VALUE_EXISTS) {
-                    throw new PKIException(getUserMessage("CMS_USRGRP_SRVLT_USER_CERT_EXISTS", headers));
-                } else {
-                    throw new PKIException(getUserMessage("CMS_USRGRP_USER_MOD_FAILED", headers));
-                }
-            }
-
-        } catch (PKIException e) {
-            auditAddUserCert(userID, userCertData, ILogger.FAILURE);
-            throw e;
-
-        } catch (Exception e) {
-            log(ILogger.LL_FAILURE, e.toString());
-            auditAddUserCert(userID, userCertData, ILogger.FAILURE);
-            throw new PKIException(getUserMessage("CMS_USRGRP_USER_MOD_FAILED", headers));
-        }
-    }
-
-    /**
-     * Removes a certificate for a user
-     * <P>
-     *
-     * Request/Response Syntax: http://warp.mcom.com/server/certificate/columbo/design/
-     * ui/admin-protocol-definition.html#user-admin
-     * <P>
-     *
-     * In this method, "certDN" is actually a combination of version, serialNumber, issuerDN, and SubjectDN.
-     * <P>
-     *
-     * <ul>
-     * <li>signed.audit LOGGING_SIGNED_AUDIT_CONFIG_ROLE used when configuring role information (anything under
-     * users/groups)
-     * </ul>
-     */
-    @Override
-    public void removeUserCert(String userID, String certID) {
-
-        try {
-            certID = URLDecoder.decode(certID, "UTF-8");
-        } catch (Exception e) {
-            throw new PKIException(e.getMessage());
-        }
-
-        UserCertData userCertData = new UserCertData();
-        userCertData.setID(certID);
-        removeUserCert(userID, userCertData);
-    }
-
-    public void removeUserCert(String userID, UserCertData userCertData) {
-
-        // ensure that any low-level exceptions are reported
-        // to the signed audit log and stored as failures
-        try {
-            if (userID == null) {
-                log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_NULL_RS_ID"));
-                throw new BadRequestException(getUserMessage("CMS_ADMIN_SRVLT_NULL_RS_ID", headers));
-            }
-
-            IUser user = userGroupManager.createUser(userID);
-            String certID = userCertData.getID();
-
-            // no certDN is a success
-            if (certID == null) {
-                auditDeleteUserCert(userID, userCertData, ILogger.SUCCESS);
-                return;
-            }
-
-            user.setCertDN(certID);
-
-            userGroupManager.removeUserCert(user);
-
-            auditDeleteUserCert(userID, userCertData, ILogger.SUCCESS);
-
-        } catch (PKIException e) {
-            auditDeleteUserCert(userID, userCertData, ILogger.FAILURE);
-            throw e;
-
-        } catch (Exception e) {
-            log(ILogger.LL_FAILURE, e.toString());
-            auditDeleteUserCert(userID, userCertData, ILogger.FAILURE);
-            throw new PKIException(getUserMessage("CMS_USRGRP_USER_MOD_FAILED", headers));
-        }
-    }
-
-    public void log(int level, String message) {
-        log(ILogger.S_USRGRP, level, message);
-    }
-
-    public void auditAddUserCert(String id, UserCertData userCertData, String status) {
-        audit(OpDef.OP_ADD, id, getParams(userCertData), status);
-    }
-
-    public void auditDeleteUserCert(String id, UserCertData userCertData, String status) {
-        audit(OpDef.OP_DELETE, id, getParams(userCertData), status);
-    }
-
-    public void audit(String type, String id, Map<String, String> params, String status) {
-        audit(IAuditor.LOGGING_SIGNED_AUDIT_CONFIG_ROLE, ScopeDef.SC_USER_CERTS, type, id, params, status);
-    }
-}
diff --git a/base/common/src/com/netscape/cms/servlet/admin/UserMembershipService.java b/base/common/src/com/netscape/cms/servlet/admin/UserMembershipService.java
deleted file mode 100644
index 35068f5a039110405495a23f0d8ed9e3bb1d7f12..0000000000000000000000000000000000000000
--- a/base/common/src/com/netscape/cms/servlet/admin/UserMembershipService.java
+++ /dev/null
@@ -1,189 +0,0 @@
-// --- 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.
-//
-// (C) 2013 Red Hat, Inc.
-// All rights reserved.
-// --- END COPYRIGHT BLOCK ---
-
-package com.netscape.cms.servlet.admin;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URLEncoder;
-import java.util.Enumeration;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Request;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-
-import org.jboss.resteasy.plugins.providers.atom.Link;
-
-import com.netscape.certsrv.apps.CMS;
-import com.netscape.certsrv.base.BadRequestException;
-import com.netscape.certsrv.base.PKIException;
-import com.netscape.certsrv.base.UserNotFoundException;
-import com.netscape.certsrv.group.GroupMemberData;
-import com.netscape.certsrv.logging.ILogger;
-import com.netscape.certsrv.user.UserMembershipCollection;
-import com.netscape.certsrv.user.UserMembershipData;
-import com.netscape.certsrv.user.UserMembershipResource;
-import com.netscape.certsrv.usrgrp.IGroup;
-import com.netscape.certsrv.usrgrp.IUGSubsystem;
-import com.netscape.certsrv.usrgrp.IUser;
-import com.netscape.cms.servlet.base.PKIService;
-
-/**
- * @author Endi S. Dewata
- */
-public class UserMembershipService extends PKIService implements UserMembershipResource {
-
-    @Context
-    private UriInfo uriInfo;
-
-    @Context
-    private HttpHeaders headers;
-
-    @Context
-    private Request request;
-
-    @Context
-    private HttpServletRequest servletRequest;
-
-    public final static int DEFAULT_SIZE = 20;
-
-    public IUGSubsystem userGroupManager = (IUGSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_UG);
-
-    public UserMembershipData createUserMembershipData(String userID, String groupID) throws UnsupportedEncodingException {
-
-        UserMembershipData userMembershipData = new UserMembershipData();
-        userMembershipData.setID(groupID);
-        userMembershipData.setUserID(userID);
-
-        URI uri = uriInfo.getBaseUriBuilder().path(UserMembershipResource.class)
-                .path("{groupID}")
-                .build(
-                        URLEncoder.encode(userID, "UTF-8"),
-                        URLEncoder.encode(groupID, "UTF-8"));
-
-        userMembershipData.setLink(new Link("self", uri));
-
-        return userMembershipData;
-    }
-
-    @Override
-    public UserMembershipCollection findUserMemberships(String userID, Integer start, Integer size) {
-        try {
-            start = start == null ? 0 : start;
-            size = size == null ? DEFAULT_SIZE : size;
-
-            if (userID == null) {
-                log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_NULL_RS_ID"));
-                throw new BadRequestException(getUserMessage("CMS_ADMIN_SRVLT_NULL_RS_ID", headers));
-            }
-
-            IUser user = userGroupManager.getUser(userID);
-
-            if (user == null) {
-                log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_USER_NOT_EXIST"));
-                throw new UserNotFoundException(userID);
-            }
-
-            UserMembershipCollection response = new UserMembershipCollection();
-
-            Enumeration<IGroup> groups = userGroupManager.findGroupsByUser(user.getUserDN());
-
-            int i = 0;
-
-            // skip to the start of the page
-            for ( ; i<start && groups.hasMoreElements(); i++) groups.nextElement();
-
-            // return entries up to the page size
-            for ( ; i<start+size && groups.hasMoreElements(); i++) {
-                IGroup group = groups.nextElement();
-                response.addMembership(createUserMembershipData(userID, group.getName()));
-            }
-
-            // count the total entries
-            for ( ; groups.hasMoreElements(); i++) groups.nextElement();
-
-            if (start > 0) {
-                URI uri = uriInfo.getRequestUriBuilder().replaceQueryParam("start", Math.max(start-size, 0)).build();
-                response.addLink(new Link("prev", uri));
-            }
-
-            if (start+size < i) {
-                URI uri = uriInfo.getRequestUriBuilder().replaceQueryParam("start", start+size).build();
-                response.addLink(new Link("next", uri));
-            }
-
-            return response;
-
-        } catch (PKIException e) {
-            throw e;
-
-        } catch (Exception e) {
-            throw new PKIException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public Response addUserMembership(String userID, String groupID) {
-        try {
-            GroupMemberData groupMemberData = new GroupMemberData();
-            groupMemberData.setID(userID);
-            groupMemberData.setGroupID(groupID);
-
-            GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
-            processor.setUriInfo(uriInfo);
-            processor.addGroupMember(groupMemberData);
-
-            UserMembershipData userMembershipData = createUserMembershipData(userID, groupID);
-
-            return Response
-                    .created(userMembershipData.getLink().getHref())
-                    .entity(userMembershipData)
-                    .type(MediaType.APPLICATION_XML)
-                    .build();
-
-        } catch (PKIException e) {
-            throw e;
-
-        } catch (Exception e) {
-            throw new PKIException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public void removeUserMembership(String userID, String groupID) {
-        try {
-            GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
-            processor.setUriInfo(uriInfo);
-            processor.removeGroupMember(groupID, userID);
-
-        } catch (PKIException e) {
-            throw e;
-
-        } catch (Exception e) {
-            throw new PKIException(e.getMessage(), e);
-        }
-    }
-
-    public void log(int level, String message) {
-        log(ILogger.S_USRGRP, level, message);
-    }
-}
diff --git a/base/common/src/com/netscape/cms/servlet/admin/UserService.java b/base/common/src/com/netscape/cms/servlet/admin/UserService.java
index a6cd154e88dc0883f9437f4bda17ccd54c7cdf80..c14605c5404edc620a48081c5afb2aad5c475ec0 100644
--- a/base/common/src/com/netscape/cms/servlet/admin/UserService.java
+++ b/base/common/src/com/netscape/cms/servlet/admin/UserService.java
@@ -18,8 +18,14 @@
 
 package com.netscape.cms.servlet.admin;
 
+import java.io.UnsupportedEncodingException;
 import java.net.URI;
+import java.net.URLDecoder;
 import java.net.URLEncoder;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.List;
@@ -34,26 +40,38 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 
 import netscape.ldap.LDAPException;
+import netscape.security.pkcs.PKCS7;
+import netscape.security.x509.X509CertImpl;
 
 import org.apache.commons.lang.StringUtils;
 import org.jboss.resteasy.plugins.providers.atom.Link;
+import org.mozilla.jss.CryptoManager;
+import org.mozilla.jss.crypto.InternalCertificate;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.BadRequestDataException;
 import com.netscape.certsrv.base.BadRequestException;
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.ForbiddenException;
+import com.netscape.certsrv.base.ICertPrettyPrint;
 import com.netscape.certsrv.base.IConfigStore;
 import com.netscape.certsrv.base.PKIException;
+import com.netscape.certsrv.base.ResourceNotFoundException;
 import com.netscape.certsrv.base.UserNotFoundException;
 import com.netscape.certsrv.common.OpDef;
 import com.netscape.certsrv.common.ScopeDef;
+import com.netscape.certsrv.dbs.certdb.CertId;
+import com.netscape.certsrv.group.GroupMemberData;
 import com.netscape.certsrv.ldap.LDAPExceptionConverter;
 import com.netscape.certsrv.logging.IAuditor;
 import com.netscape.certsrv.logging.ILogger;
 import com.netscape.certsrv.password.IPasswordCheck;
+import com.netscape.certsrv.user.UserCertCollection;
+import com.netscape.certsrv.user.UserCertData;
 import com.netscape.certsrv.user.UserCollection;
 import com.netscape.certsrv.user.UserData;
+import com.netscape.certsrv.user.UserMembershipCollection;
+import com.netscape.certsrv.user.UserMembershipData;
 import com.netscape.certsrv.user.UserResource;
 import com.netscape.certsrv.usrgrp.EUsrGrpException;
 import com.netscape.certsrv.usrgrp.IGroup;
@@ -61,6 +79,8 @@ import com.netscape.certsrv.usrgrp.IUGSubsystem;
 import com.netscape.certsrv.usrgrp.IUser;
 import com.netscape.cms.servlet.base.PKIService;
 import com.netscape.cmsutil.ldap.LDAPUtil;
+import com.netscape.cmsutil.util.Cert;
+import com.netscape.cmsutil.util.Utils;
 
 /**
  * @author Endi S. Dewata
@@ -521,23 +541,561 @@ public class UserService extends PKIService implements UserResource {
         }
     }
 
+    public UserCertData createUserCertData(String userID, X509Certificate cert) throws Exception {
+
+        UserCertData userCertData = new UserCertData();
+
+        userCertData.setVersion(cert.getVersion());
+        userCertData.setSerialNumber(new CertId(cert.getSerialNumber()));
+        userCertData.setIssuerDN(cert.getIssuerDN().toString());
+        userCertData.setSubjectDN(cert.getSubjectDN().toString());
+
+        userID = URLEncoder.encode(userID, "UTF-8");
+        String certID = URLEncoder.encode(userCertData.getID(), "UTF-8");
+        URI uri = uriInfo.getBaseUriBuilder()
+                .path(UserResource.class)
+                .path("{userID}/certs/{certID}")
+                .build(userID, certID);
+        userCertData.setLink(new Link("self", uri));
+
+        return userCertData;
+    }
+
+    /**
+     * List user certificate(s)
+     *
+     * Request/Response Syntax:
+     * http://warp.mcom.com/server/certificate/columbo/design/
+     * ui/admin-protocol-definition.html#user-admin
+     */
+    @Override
+    public UserCertCollection findUserCerts(String userID, Integer start, Integer size) {
+        try {
+            start = start == null ? 0 : start;
+            size = size == null ? DEFAULT_SIZE : size;
+
+            if (userID == null) {
+                log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_NULL_RS_ID"));
+                throw new BadRequestException(getUserMessage("CMS_ADMIN_SRVLT_NULL_RS_ID", headers));
+            }
+
+            IUser user = null;
+
+            try {
+                user = userGroupManager.getUser(userID);
+            } catch (Exception e) {
+                throw new PKIException(getUserMessage("CMS_USRGRP_SRVLT_USER_NOT_EXIST", headers));
+            }
+
+            if (user == null) {
+                log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_USER_NOT_EXIST"));
+                throw new UserNotFoundException(userID);
+            }
+
+            UserCertCollection response = new UserCertCollection();
+
+            X509Certificate[] certs = user.getX509Certificates();
+            if (certs != null) {
+                for (int i=start; i<start+size && i<certs.length; i++) {
+                    X509Certificate cert = certs[i];
+                    response.addCert(createUserCertData(userID, cert));
+                }
+
+                if (start > 0) {
+                    URI uri = uriInfo.getRequestUriBuilder().replaceQueryParam("start", Math.max(start-size, 0)).build();
+                    response.addLink(new Link("prev", uri));
+                }
+
+                if (start+size < certs.length) {
+                    URI uri = uriInfo.getRequestUriBuilder().replaceQueryParam("start", start+size).build();
+                    response.addLink(new Link("next", uri));
+                }
+            }
+
+            return response;
+
+        } catch (PKIException e) {
+            throw e;
+
+        } catch (Exception e) {
+            throw new PKIException(e.getMessage());
+        }
+    }
+
+    @Override
+    public UserCertData getUserCert(String userID, String certID) {
+        try {
+            if (userID == null) {
+                log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_NULL_RS_ID"));
+
+                throw new BadRequestException(getUserMessage("CMS_ADMIN_SRVLT_NULL_RS_ID", headers));
+            }
+
+            IUser user = null;
+
+            try {
+                user = userGroupManager.getUser(userID);
+            } catch (Exception e) {
+                throw new PKIException(getUserMessage("CMS_USRGRP_SRVLT_USER_NOT_EXIST", headers));
+            }
+
+            if (user == null) {
+                log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_USER_NOT_EXIST"));
+                throw new UserNotFoundException(userID);
+            }
+
+            X509Certificate[] certs = user.getX509Certificates();
+
+            if (certs == null) {
+                throw new ResourceNotFoundException("No certificates found for " + userID);
+            }
+
+            try {
+                certID = URLDecoder.decode(certID, "UTF-8");
+            } catch (Exception e) {
+                throw new PKIException(e.getMessage());
+            }
+
+            for (X509Certificate cert : certs) {
+
+                UserCertData userCertData = createUserCertData(userID, cert);
+
+                if (!userCertData.getID().equals(certID)) continue;
+
+                ICertPrettyPrint print = CMS.getCertPrettyPrint(cert);
+                userCertData.setPrettyPrint(print.toString(getLocale(headers)));
+
+                // add base64 encoding
+                String base64 = CMS.getEncodedCert(cert);
+                userCertData.setEncoded(base64);
+
+                return userCertData;
+            }
+
+            throw new ResourceNotFoundException("No certificates found for " + userID);
+
+        } catch (PKIException e) {
+            throw e;
+
+        } catch (Exception e) {
+            throw new PKIException(e.getMessage());
+        }
+    }
+
+    /**
+     * Adds a certificate to a user
+     * <P>
+     *
+     * Request/Response Syntax: http://warp.mcom.com/server/certificate/columbo/design/
+     * ui/admin-protocol-definition.html#user-admin
+     * <P>
+     *
+     * <ul>
+     * <li>signed.audit LOGGING_SIGNED_AUDIT_CONFIG_ROLE used when configuring role information (anything under
+     * users/groups)
+     * </ul>
+     */
+    @Override
+    public Response addUserCert(String userID, UserCertData userCertData) {
+
+        // ensure that any low-level exceptions are reported
+        // to the signed audit log and stored as failures
+        try {
+            if (userID == null) {
+                log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_NULL_RS_ID"));
+                throw new BadRequestException(getUserMessage("CMS_ADMIN_SRVLT_NULL_RS_ID", headers));
+            }
+
+            IUser user = userGroupManager.createUser(userID);
+
+            String encoded = userCertData.getEncoded();
+            encoded = Cert.normalizeCertStrAndReq(encoded);
+            encoded = Cert.stripBrackets(encoded);
+
+            // no cert is a success
+            if (encoded == null) {
+                auditAddUserCert(userID, userCertData, ILogger.SUCCESS);
+                return Response.ok().build();
+            }
+
+            // only one cert added per operation
+            X509Certificate cert = null;
+
+            // Base64 decode cert
+            byte binaryCert[] = Utils.base64decode(encoded);
+
+            try {
+                cert = new X509CertImpl(binaryCert);
+
+            } catch (CertificateException e) {
+                // ignore
+            }
+
+            if (cert == null) {
+                // cert chain direction
+                boolean assending = true;
+
+                // could it be a pkcs7 blob?
+                CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_IS_PK_BLOB"));
+
+                try {
+                    CryptoManager manager = CryptoManager.getInstance();
+
+                    PKCS7 pkcs7 = new PKCS7(binaryCert);
+
+                    X509Certificate p7certs[] = pkcs7.getCertificates();
+
+                    if (p7certs.length == 0) {
+                        throw new BadRequestException(getUserMessage("CMS_USRGRP_SRVLT_CERT_ERROR", headers));
+                    }
+
+                    // fix for 370099 - cert ordering can not be assumed
+                    // find out the ordering ...
+
+                    // self-signed and alone? take it. otherwise test
+                    // the ordering
+                    if (p7certs[0].getSubjectDN().toString().equals(
+                            p7certs[0].getIssuerDN().toString()) &&
+                            (p7certs.length == 1)) {
+                        cert = p7certs[0];
+                        CMS.debug("UserCertResourceService: " + 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"));
+
+                    } else if (p7certs[1].getIssuerDN().toString().equals(p7certs[0].getSubjectDN().toString())) {
+                        assending = false;
+                        CMS.debug("UserCertResourceService: " + 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"));
+                        throw new BadRequestException(getUserMessage("CMS_USRGRP_SRVLT_CERT_ERROR", headers));
+                    }
+
+                    CMS.debug("UserCertResourceService: "
+                            + CMS.getLogMessage("ADMIN_SRVLT_CHAIN_STORED_DB", String.valueOf(p7certs.length)));
+
+                    int j = 0;
+                    int jBegin = 0;
+                    int jEnd = 0;
+
+                    if (assending == true) {
+                        jBegin = 1;
+                        jEnd = p7certs.length;
+                    } else {
+                        jBegin = 0;
+                        jEnd = p7certs.length - 1;
+                    }
+
+                    // store the chain into cert db, except for the user cert
+                    for (j = jBegin; j < jEnd; j++) {
+                        CMS.debug("UserCertResourceService: "
+                                + 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) {
+                            log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_LEAF_CERT_NULL"));
+                        } else {
+                            CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_LEAF_CERT_NON_NULL"));
+                        }
+
+                        if (leafCert instanceof InternalCertificate) {
+                            ((InternalCertificate) leafCert).setSSLTrust(
+                                    InternalCertificate.VALID_CA |
+                                            InternalCertificate.TRUSTED_CA |
+                                            InternalCertificate.TRUSTED_CLIENT_CA);
+                        } else {
+                            log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_NOT_INTERNAL_CERT",
+                                    String.valueOf(p7certs[j].getSubjectDN())));
+                        }
+                    }
+
+                    /*
+                    } catch (CryptoManager.UserCertConflictException e) {
+                        // got a "user cert" in the chain, most likely the CA
+                        // cert of this instance, which has a private key.  Ignore
+                        log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_PKS7_IGNORED", e.toString()));
+                    */
+                } catch (PKIException e) {
+                    log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_CERT_ERROR", e.toString()));
+                    throw e;
+                } catch (Exception e) {
+                    log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_CERT_ERROR", e.toString()));
+                    throw new PKIException(getUserMessage("CMS_USRGRP_SRVLT_CERT_ERROR", headers));
+                }
+            }
+
+            try {
+                CMS.debug("UserCertResourceService: " + CMS.getLogMessage("ADMIN_SRVLT_BEFORE_VALIDITY"));
+                cert.checkValidity(); // throw exception if fails
+
+                user.setX509Certificates(new X509Certificate[] { cert });
+                userGroupManager.addUserCert(user);
+
+                auditAddUserCert(userID, userCertData, ILogger.SUCCESS);
+
+                // read the data back
+
+                userCertData.setVersion(cert.getVersion());
+                userCertData.setSerialNumber(new CertId(cert.getSerialNumber()));
+                userCertData.setIssuerDN(cert.getIssuerDN().toString());
+                userCertData.setSubjectDN(cert.getSubjectDN().toString());
+                String certID = userCertData.getID();
+
+                userCertData = getUserCert(userID, URLEncoder.encode(certID, "UTF-8"));
+
+                return Response
+                        .created(userCertData.getLink().getHref())
+                        .entity(userCertData)
+                        .type(MediaType.APPLICATION_XML)
+                        .build();
+
+            } catch (CertificateExpiredException 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));
+
+            } catch (CertificateNotYetValidException 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));
+
+            } catch (LDAPException e) {
+                if (e.getLDAPResultCode() == LDAPException.ATTRIBUTE_OR_VALUE_EXISTS) {
+                    throw new PKIException(getUserMessage("CMS_USRGRP_SRVLT_USER_CERT_EXISTS", headers));
+                } else {
+                    throw new PKIException(getUserMessage("CMS_USRGRP_USER_MOD_FAILED", headers));
+                }
+            }
+
+        } catch (PKIException e) {
+            auditAddUserCert(userID, userCertData, ILogger.FAILURE);
+            throw e;
+
+        } catch (Exception e) {
+            log(ILogger.LL_FAILURE, e.toString());
+            auditAddUserCert(userID, userCertData, ILogger.FAILURE);
+            throw new PKIException(getUserMessage("CMS_USRGRP_USER_MOD_FAILED", headers));
+        }
+    }
+
+    /**
+     * Removes a certificate for a user
+     * <P>
+     *
+     * Request/Response Syntax: http://warp.mcom.com/server/certificate/columbo/design/
+     * ui/admin-protocol-definition.html#user-admin
+     * <P>
+     *
+     * In this method, "certDN" is actually a combination of version, serialNumber, issuerDN, and SubjectDN.
+     * <P>
+     *
+     * <ul>
+     * <li>signed.audit LOGGING_SIGNED_AUDIT_CONFIG_ROLE used when configuring role information (anything under
+     * users/groups)
+     * </ul>
+     */
+    @Override
+    public void removeUserCert(String userID, String certID) {
+
+        try {
+            certID = URLDecoder.decode(certID, "UTF-8");
+        } catch (Exception e) {
+            throw new PKIException(e.getMessage());
+        }
+
+        UserCertData userCertData = new UserCertData();
+        userCertData.setID(certID);
+        removeUserCert(userID, userCertData);
+    }
+
+    public void removeUserCert(String userID, UserCertData userCertData) {
+
+        // ensure that any low-level exceptions are reported
+        // to the signed audit log and stored as failures
+        try {
+            if (userID == null) {
+                log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_NULL_RS_ID"));
+                throw new BadRequestException(getUserMessage("CMS_ADMIN_SRVLT_NULL_RS_ID", headers));
+            }
+
+            IUser user = userGroupManager.createUser(userID);
+            String certID = userCertData.getID();
+
+            // no certDN is a success
+            if (certID == null) {
+                auditDeleteUserCert(userID, userCertData, ILogger.SUCCESS);
+                return;
+            }
+
+            user.setCertDN(certID);
+
+            userGroupManager.removeUserCert(user);
+
+            auditDeleteUserCert(userID, userCertData, ILogger.SUCCESS);
+
+        } catch (PKIException e) {
+            auditDeleteUserCert(userID, userCertData, ILogger.FAILURE);
+            throw e;
+
+        } catch (Exception e) {
+            log(ILogger.LL_FAILURE, e.toString());
+            auditDeleteUserCert(userID, userCertData, ILogger.FAILURE);
+            throw new PKIException(getUserMessage("CMS_USRGRP_USER_MOD_FAILED", headers));
+        }
+    }
+
+
+    public UserMembershipData createUserMembershipData(String userID, String groupID) throws UnsupportedEncodingException {
+
+        UserMembershipData userMembershipData = new UserMembershipData();
+        userMembershipData.setID(groupID);
+        userMembershipData.setUserID(userID);
+
+        URI uri = uriInfo.getBaseUriBuilder()
+                .path(UserResource.class)
+                .path("{userID}/memberships/{groupID}")
+                .build(
+                        URLEncoder.encode(userID, "UTF-8"),
+                        URLEncoder.encode(groupID, "UTF-8"));
+
+        userMembershipData.setLink(new Link("self", uri));
+
+        return userMembershipData;
+    }
+
+    @Override
+    public UserMembershipCollection findUserMemberships(String userID, Integer start, Integer size) {
+        try {
+            start = start == null ? 0 : start;
+            size = size == null ? DEFAULT_SIZE : size;
+
+            if (userID == null) {
+                log(ILogger.LL_FAILURE, CMS.getLogMessage("ADMIN_SRVLT_NULL_RS_ID"));
+                throw new BadRequestException(getUserMessage("CMS_ADMIN_SRVLT_NULL_RS_ID", headers));
+            }
+
+            IUser user = userGroupManager.getUser(userID);
+
+            if (user == null) {
+                log(ILogger.LL_FAILURE, CMS.getLogMessage("USRGRP_SRVLT_USER_NOT_EXIST"));
+                throw new UserNotFoundException(userID);
+            }
+
+            UserMembershipCollection response = new UserMembershipCollection();
+
+            Enumeration<IGroup> groups = userGroupManager.findGroupsByUser(user.getUserDN());
+
+            int i = 0;
+
+            // skip to the start of the page
+            for ( ; i<start && groups.hasMoreElements(); i++) groups.nextElement();
+
+            // return entries up to the page size
+            for ( ; i<start+size && groups.hasMoreElements(); i++) {
+                IGroup group = groups.nextElement();
+                response.addMembership(createUserMembershipData(userID, group.getName()));
+            }
+
+            // count the total entries
+            for ( ; groups.hasMoreElements(); i++) groups.nextElement();
+
+            if (start > 0) {
+                URI uri = uriInfo.getRequestUriBuilder().replaceQueryParam("start", Math.max(start-size, 0)).build();
+                response.addLink(new Link("prev", uri));
+            }
+
+            if (start+size < i) {
+                URI uri = uriInfo.getRequestUriBuilder().replaceQueryParam("start", start+size).build();
+                response.addLink(new Link("next", uri));
+            }
+
+            return response;
+
+        } catch (PKIException e) {
+            throw e;
+
+        } catch (Exception e) {
+            throw new PKIException(e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public Response addUserMembership(String userID, String groupID) {
+        try {
+            GroupMemberData groupMemberData = new GroupMemberData();
+            groupMemberData.setID(userID);
+            groupMemberData.setGroupID(groupID);
+
+            GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
+            processor.setUriInfo(uriInfo);
+            processor.addGroupMember(groupMemberData);
+
+            UserMembershipData userMembershipData = createUserMembershipData(userID, groupID);
+
+            return Response
+                    .created(userMembershipData.getLink().getHref())
+                    .entity(userMembershipData)
+                    .type(MediaType.APPLICATION_XML)
+                    .build();
+
+        } catch (PKIException e) {
+            throw e;
+
+        } catch (Exception e) {
+            throw new PKIException(e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public void removeUserMembership(String userID, String groupID) {
+        try {
+            GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
+            processor.setUriInfo(uriInfo);
+            processor.removeGroupMember(groupID, userID);
+
+        } catch (PKIException e) {
+            throw e;
+
+        } catch (Exception e) {
+            throw new PKIException(e.getMessage(), e);
+        }
+    }
+
     public void log(int level, String message) {
         log(ILogger.S_USRGRP, level, message);
     }
 
     public void auditAddUser(String id, UserData userData, String status) {
-        audit(OpDef.OP_ADD, id, getParams(userData), status);
+        auditUser(OpDef.OP_ADD, id, getParams(userData), status);
     }
 
     public void auditModifyUser(String id, UserData userData, String status) {
-        audit(OpDef.OP_MODIFY, id, getParams(userData), status);
+        auditUser(OpDef.OP_MODIFY, id, getParams(userData), status);
     }
 
     public void auditDeleteUser(String id, String status) {
-        audit(OpDef.OP_DELETE, id, null, status);
+        auditUser(OpDef.OP_DELETE, id, null, status);
     }
 
-    public void audit(String type, String id, Map<String, String> params, String status) {
+    public void auditAddUserCert(String id, UserCertData userCertData, String status) {
+        auditUserCert(OpDef.OP_ADD, id, getParams(userCertData), status);
+    }
+
+    public void auditDeleteUserCert(String id, UserCertData userCertData, String status) {
+        auditUserCert(OpDef.OP_DELETE, id, getParams(userCertData), status);
+    }
+
+    public void auditUser(String type, String id, Map<String, String> params, String status) {
         audit(IAuditor.LOGGING_SIGNED_AUDIT_CONFIG_ROLE, ScopeDef.SC_USERS, type, id, params, status);
     }
+
+    public void auditUserCert(String type, String id, Map<String, String> params, String status) {
+        audit(IAuditor.LOGGING_SIGNED_AUDIT_CONFIG_ROLE, ScopeDef.SC_USER_CERTS, type, id, params, status);
+    }
 }
diff --git a/base/kra/src/com/netscape/kra/KeyRecoveryAuthorityApplication.java b/base/kra/src/com/netscape/kra/KeyRecoveryAuthorityApplication.java
index 04b4989ef4f5cd97447fee310d5e8a24ce72e5c3..188bb6c6ccb98030fe5a1ddf8456e70623a74445 100644
--- a/base/kra/src/com/netscape/kra/KeyRecoveryAuthorityApplication.java
+++ b/base/kra/src/com/netscape/kra/KeyRecoveryAuthorityApplication.java
@@ -9,11 +9,8 @@ import com.netscape.certsrv.base.PKIException;
 import com.netscape.cms.authorization.ACLInterceptor;
 import com.netscape.cms.authorization.AuthMethodInterceptor;
 import com.netscape.cms.servlet.account.AccountService;
-import com.netscape.cms.servlet.admin.GroupMemberService;
 import com.netscape.cms.servlet.admin.GroupService;
 import com.netscape.cms.servlet.admin.SystemCertService;
-import com.netscape.cms.servlet.admin.UserCertService;
-import com.netscape.cms.servlet.admin.UserMembershipService;
 import com.netscape.cms.servlet.admin.UserService;
 import com.netscape.cms.servlet.csadmin.SystemConfigService;
 import com.netscape.cms.servlet.key.KeyService;
@@ -45,10 +42,7 @@ public class KeyRecoveryAuthorityApplication extends Application {
         classes.add(SelfTestService.class);
 
         // user and group management
-        classes.add(GroupMemberService.class);
         classes.add(GroupService.class);
-        classes.add(UserCertService.class);
-        classes.add(UserMembershipService.class);
         classes.add(UserService.class);
 
         // system certs
diff --git a/base/ocsp/src/com/netscape/ocsp/OCSPApplication.java b/base/ocsp/src/com/netscape/ocsp/OCSPApplication.java
index 39c17cede189635f19a480e16cf06066c22288cc..1cc72152112eb1cc64d76e49c288e1f0d5cb1181 100644
--- a/base/ocsp/src/com/netscape/ocsp/OCSPApplication.java
+++ b/base/ocsp/src/com/netscape/ocsp/OCSPApplication.java
@@ -9,11 +9,8 @@ import com.netscape.certsrv.base.PKIException;
 import com.netscape.cms.authorization.ACLInterceptor;
 import com.netscape.cms.authorization.AuthMethodInterceptor;
 import com.netscape.cms.servlet.account.AccountService;
-import com.netscape.cms.servlet.admin.GroupMemberService;
 import com.netscape.cms.servlet.admin.GroupService;
 import com.netscape.cms.servlet.admin.SystemCertService;
-import com.netscape.cms.servlet.admin.UserCertService;
-import com.netscape.cms.servlet.admin.UserMembershipService;
 import com.netscape.cms.servlet.admin.UserService;
 import com.netscape.cms.servlet.csadmin.SystemConfigService;
 import com.netscape.cmscore.logging.AuditService;
@@ -39,10 +36,7 @@ public class OCSPApplication extends Application {
         classes.add(SelfTestService.class);
 
         // user and group management
-        classes.add(GroupMemberService.class);
         classes.add(GroupService.class);
-        classes.add(UserCertService.class);
-        classes.add(UserMembershipService.class);
         classes.add(UserService.class);
 
         // system certs
diff --git a/base/tks/src/com/netscape/tks/TKSApplication.java b/base/tks/src/com/netscape/tks/TKSApplication.java
index ff9d66ed265bda273cb7cafa6a90684980493e1d..1f31bae376ca0fd73224baa6e344135b855e1954 100644
--- a/base/tks/src/com/netscape/tks/TKSApplication.java
+++ b/base/tks/src/com/netscape/tks/TKSApplication.java
@@ -9,11 +9,8 @@ import com.netscape.certsrv.base.PKIException;
 import com.netscape.cms.authorization.ACLInterceptor;
 import com.netscape.cms.authorization.AuthMethodInterceptor;
 import com.netscape.cms.servlet.account.AccountService;
-import com.netscape.cms.servlet.admin.GroupMemberService;
 import com.netscape.cms.servlet.admin.GroupService;
 import com.netscape.cms.servlet.admin.SystemCertService;
-import com.netscape.cms.servlet.admin.UserCertService;
-import com.netscape.cms.servlet.admin.UserMembershipService;
 import com.netscape.cms.servlet.admin.UserService;
 import com.netscape.cms.servlet.csadmin.SystemConfigService;
 import com.netscape.cms.servlet.tks.TPSConnectorService;
@@ -40,10 +37,7 @@ public class TKSApplication extends Application {
         classes.add(SelfTestService.class);
 
         // user and group management
-        classes.add(GroupMemberService.class);
         classes.add(GroupService.class);
-        classes.add(UserCertService.class);
-        classes.add(UserMembershipService.class);
         classes.add(UserService.class);
 
         // system certs
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/TPSApplication.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/TPSApplication.java
index bdce5831f67fd44c8167291a5fe14e28eebc09a1..194c398a287d2fb9e0fcf32d153c31240272b28b 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/TPSApplication.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/TPSApplication.java
@@ -34,11 +34,8 @@ import com.netscape.certsrv.base.PKIException;
 import com.netscape.cms.authorization.ACLInterceptor;
 import com.netscape.cms.authorization.AuthMethodInterceptor;
 import com.netscape.cms.servlet.account.AccountService;
-import com.netscape.cms.servlet.admin.GroupMemberService;
 import com.netscape.cms.servlet.admin.GroupService;
 import com.netscape.cms.servlet.admin.SystemCertService;
-import com.netscape.cms.servlet.admin.UserCertService;
-import com.netscape.cms.servlet.admin.UserMembershipService;
 import com.netscape.cms.servlet.admin.UserService;
 import com.netscape.cms.servlet.csadmin.SystemConfigService;
 import com.netscape.cmscore.logging.AuditService;
@@ -64,10 +61,7 @@ public class TPSApplication extends Application {
         classes.add(SystemConfigService.class);
 
         // user and group management
-        classes.add(GroupMemberService.class);
         classes.add(GroupService.class);
-        classes.add(UserCertService.class);
-        classes.add(UserMembershipService.class);
         classes.add(UserService.class);
 
         // system certs
-- 
1.8.1.4

