From 49b5c7e3d46ce8e4c8620efa5e9077687f8b2f7e Mon Sep 17 00:00:00 2001
From: Jeff Burke <Jeff.Burke@nrc-cnrc.gc.ca>
Date: Fri, 28 Aug 2015 11:25:50 -0700
Subject: [PATCH] ac2 rework: fixes for unit and int tests

---
 .../ca/nrc/cadc/ac/server/ldap/LdapDAO.java   |    4 +
 .../nrc/cadc/ac/server/ldap/LdapUserDAO.java  |   34 +-
 .../ca/nrc/cadc/ac/server/web/SyncOutput.java |    6 +-
 .../server/web/users/AbstractUserAction.java  |   44 +-
 .../ac/server/web/users/ModifyUserAction.java |    7 +-
 .../server/web/users/UserActionFactory.java   |   21 +-
 .../cadc/ac/server/web/users/UserServlet.java |    2 +-
 .../web/users/ModifyUserActionTest.java       |   15 +-
 .../web/users/UserActionFactoryTest.java      |    9 +-
 .../src/ca/nrc/cadc/ac/User.java              |   12 +
 .../src/ca/nrc/cadc/ac/client/GMSClient.java  |    3 +-
 .../JSONUserListInputStreamWrapper.java       |  154 ---
 .../JsonUserListInputStreamWrapper.java       |    8 +-
 .../src/ca/nrc/cadc/ac/client/UserClient.java |    2 +-
 ...ractXML.java => AbstractReaderWriter.java} | 1141 ++++++++---------
 .../ca/nrc/cadc/ac/xml/GroupListReader.java   |    2 +-
 .../ca/nrc/cadc/ac/xml/GroupListWriter.java   |    2 +-
 .../src/ca/nrc/cadc/ac/xml/GroupReader.java   |    2 +-
 .../src/ca/nrc/cadc/ac/xml/GroupWriter.java   |    2 +-
 .../ca/nrc/cadc/ac/xml/UserListReader.java    |    2 +-
 .../ca/nrc/cadc/ac/xml/UserListWriter.java    |    3 +-
 .../src/ca/nrc/cadc/ac/xml/UserReader.java    |    2 +-
 .../ca/nrc/cadc/ac/xml/UserRequestReader.java |    2 +-
 .../ca/nrc/cadc/ac/xml/UserRequestWriter.java |    2 +-
 .../src/ca/nrc/cadc/ac/xml/UserWriter.java    |    2 +-
 .../JsonUserListInputStreamWrapperTest.java   |    6 +-
 .../ac/xml/GroupPropertyReaderWriterTest.java |    2 +-
 .../cadc/ac/xml/IdentityReaderWriterTest.java |    4 +-
 .../ac/xml/UserDetailsReaderWriterTest.java   |    2 +-
 29 files changed, 697 insertions(+), 800 deletions(-)
 delete mode 100644 projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/JSONUserListInputStreamWrapper.java
 rename projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/{AbstractXML.java => AbstractReaderWriter.java} (99%)

diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java
index d4049e2f..dae9d245 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java
@@ -260,6 +260,10 @@ public abstract class LdapDAO
         {
             throw new TransientException("ldap timeout");
         }
+        else if (code == ResultCode.INVALID_DN_SYNTAX)
+        {
+            throw new IllegalArgumentException("Invalid DN syntax");
+        }
 
         throw new RuntimeException("Ldap error (" + code.getName() + ")");
     }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAO.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAO.java
index dc10edb0..5108b082 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAO.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAO.java
@@ -285,7 +285,16 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
         DN userDN;
         try
         {
-            userDN = getUserRequestsDN(userRequest.getUser().getUserID().getName());
+            T userID = userRequest.getUser().getUserID();
+            try
+            {
+                getUser(userID, config.getUsersDN(), false);
+                throw new UserAlreadyExistsException(userID.getName() + " found in " +
+                                                     config.getUsersDN());
+            }
+            catch (UserNotFoundException ignore) {}
+
+            userDN = getUserRequestsDN(userID.getName());
             addUser(userRequest, userDN);
 
             // AD: Search results sometimes come incomplete if
@@ -293,7 +302,7 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
             getConnection().reconnect();
             try
             {
-                return getUser(userRequest.getUser().getUserID(), config.getUserRequestsDN());
+                return getUser(userID, config.getUserRequestsDN());
             }
             catch (UserNotFoundException e)
             {
@@ -443,7 +452,6 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
         return getUser(userID, config.getUserRequestsDN());
     }
 
-
     /**
      * Get the user specified by userID.
      *
@@ -455,6 +463,24 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
      * @throws AccessControlException If the operation is not permitted.
      */
     private User<T> getUser(final T userID, final String usersDN)
+        throws UserNotFoundException, TransientException,
+        AccessControlException
+    {
+        return getUser(userID, usersDN, true);
+    }
+
+    /**
+     * Get the user specified by userID.
+     *
+     * @param userID  The userID.
+     * @param usersDN The LDAP tree to search.
+     * @param proxy   If true proxy the request as the calling user.
+     * @return User instance.
+     * @throws UserNotFoundException  when the user is not found.
+     * @throws TransientException     If an temporary, unexpected problem occurred.
+     * @throws AccessControlException If the operation is not permitted.
+     */
+    private User<T> getUser(final T userID, final String usersDN, boolean proxy)
             throws UserNotFoundException, TransientException,
                    AccessControlException
     {
@@ -474,7 +500,7 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
             SearchRequest searchRequest =
                     new SearchRequest(usersDN, SearchScope.SUB,
                                       searchField, userAttribs);
-            if (isSecure(usersDN))
+            if (proxy && isSecure(usersDN))
             {
                 searchRequest.addControl(
                         new ProxiedAuthorizationV2RequestControl(
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/SyncOutput.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/SyncOutput.java
index 9ebd42f7..1f67b9bf 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/SyncOutput.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/SyncOutput.java
@@ -97,16 +97,18 @@ public class SyncOutput
 
     public void setCode(int code)
     {
+        log.debug("setting code");
         if (writer != null)
-            return;
+            throw new IllegalStateException("attempted to set code after writer has been opened");
 
         response.setStatus(code);
+        log.debug("set code " + code);
     }
 
     public void setHeader(String key, Object value)
     {
         if (writer != null)
-            return;
+            throw new IllegalStateException("attempted to set header after writer has been opened");
 
         if (value == null)
             response.setHeader(key, null);
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/AbstractUserAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/AbstractUserAction.java
index 0a7266ca..af79d45f 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/AbstractUserAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/AbstractUserAction.java
@@ -69,7 +69,9 @@
 package ca.nrc.cadc.ac.server.web.users;
 
 import ca.nrc.cadc.ac.PersonalDetails;
+import ca.nrc.cadc.ac.ReaderException;
 import ca.nrc.cadc.ac.User;
+import ca.nrc.cadc.ac.UserAlreadyExistsException;
 import ca.nrc.cadc.ac.UserNotFoundException;
 import ca.nrc.cadc.ac.UserRequest;
 import ca.nrc.cadc.ac.json.JsonUserListWriter;
@@ -158,6 +160,13 @@ public abstract class AbstractUserAction implements PrivilegedExceptionAction<Ob
             this.logInfo.setMessage(message);
             sendError(400, message);
         }
+        catch (ReaderException e)
+        {
+            log.debug(e.getMessage(), e);
+            String message = e.getMessage();
+            this.logInfo.setMessage(message);
+            sendError(400, message);
+        }
         catch (UserNotFoundException e)
         {
             log.debug(e.getMessage(), e);
@@ -165,6 +174,13 @@ public abstract class AbstractUserAction implements PrivilegedExceptionAction<Ob
             this.logInfo.setMessage(message);
             sendError(404, message);
         }
+        catch (UserAlreadyExistsException e)
+        {
+            log.debug(e.getMessage(), e);
+            String message = "User not found: " + e.getMessage();
+            this.logInfo.setMessage(message);
+            sendError(409, message);
+        }
         catch (UnsupportedOperationException e)
         {
             log.debug(e.getMessage(), e);
@@ -198,19 +214,19 @@ public abstract class AbstractUserAction implements PrivilegedExceptionAction<Ob
 
     private void sendError(int responseCode, String message)
     {
+        syncOut.setCode(responseCode);
         syncOut.setHeader("Content-Type", "text/plain");
         if (message != null)
         {
             try
             {
-                syncOut.getWriter() .write(message);
+                syncOut.getWriter().write(message);
             }
             catch (IOException e)
             {
                 log.warn("Could not write error message to output stream");
             }
         }
-        syncOut.setCode(responseCode);
     }
 
     @SuppressWarnings("unchecked")
@@ -344,28 +360,4 @@ public abstract class AbstractUserAction implements PrivilegedExceptionAction<Ob
         }
     }
 
-    void redirectGet(User<?> user) throws Exception
-    {
-        final Set<Principal> httpPrincipals =  user.getIdentities();
-
-        String id = null;
-        String idType = null;
-        Iterator<Principal> i = httpPrincipals.iterator();
-        Principal next = null;
-        while (idType == null && i.hasNext())
-        {
-            next = i.next();
-            idType = AuthenticationUtil.getPrincipalType(next);
-            id = next.getName();
-        }
-
-        if (idType == null)
-        {
-            throw new IllegalStateException("No identities found.");
-        }
-
-        final String redirectURL = "/" + id + "?idType=" + idType;
-        syncOut.setHeader("Location", redirectURL);
-        syncOut.setCode(303);
-    }
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/ModifyUserAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/ModifyUserAction.java
index 4e7ebe37..51777edc 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/ModifyUserAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/ModifyUserAction.java
@@ -84,13 +84,15 @@ import java.util.Set;
 public class ModifyUserAction extends AbstractUserAction
 {
     private final InputStream inputStream;
+    private final String request;
 
 
-    ModifyUserAction(final InputStream inputStream)
+    ModifyUserAction(final InputStream inputStream, final String request)
     {
         super();
 
         this.inputStream = inputStream;
+        this.request = request;
     }
 
 
@@ -100,7 +102,8 @@ public class ModifyUserAction extends AbstractUserAction
         final User<Principal> modifiedUser = modifyUser(user);
         logUserInfo(modifiedUser.getUserID().getName());
 
-        redirectGet(modifiedUser);
+        syncOut.setHeader("Location", request);
+        syncOut.setCode(303);
     }
 
     /**
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserActionFactory.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserActionFactory.java
index 6c7b0c33..457dd4b1 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserActionFactory.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserActionFactory.java
@@ -77,6 +77,7 @@ import ca.nrc.cadc.auth.NumericPrincipal;
 import ca.nrc.cadc.auth.OpenIdPrincipal;
 
 import java.io.IOException;
+import java.net.URL;
 import java.security.Principal;
 import javax.security.auth.x500.X500Principal;
 import javax.servlet.http.HttpServletRequest;
@@ -169,7 +170,23 @@ public abstract class UserActionFactory
 
                 if (segments.length == 1)
                 {
-                    action = new ModifyUserAction(request.getInputStream());
+                    final URL requestURL = new URL(request.getRequestURL().toString());
+                    final StringBuilder sb = new StringBuilder();
+                    sb.append(requestURL.getProtocol());
+                    sb.append("://");
+                    sb.append(requestURL.getHost());
+                    if (requestURL.getPort() > 0)
+                    {
+                        sb.append(":");
+                        sb.append(requestURL.getPort());
+                    }
+                    sb.append(request.getContextPath());
+                    sb.append(request.getServletPath());
+                    sb.append(path);
+                    sb.append("?");
+                    sb.append(request.getQueryString());
+
+                    action = new ModifyUserAction(request.getInputStream(), sb.toString());
                 }
 
                 if (action != null)
@@ -242,7 +259,7 @@ public abstract class UserActionFactory
         {
             return new User<X500Principal>(new X500Principal(userName));
         }
-        else if (idType.equalsIgnoreCase(IdentityType.UID.getValue()))
+        else if (idType.equalsIgnoreCase(IdentityType.CADC.getValue()))
         {
             return new User<NumericPrincipal>(new NumericPrincipal(
                     Long.parseLong(userName)));
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserServlet.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserServlet.java
index 69a5c20a..6fbb19c3 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserServlet.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserServlet.java
@@ -192,7 +192,7 @@ public class UserServlet extends HttpServlet
     public void doPost(HttpServletRequest request, HttpServletResponse response)
         throws IOException
     {
-        doAction(UserActionFactory.httpGetFactory(), request, response);
+        doAction(UserActionFactory.httpPostFactory(), request, response);
     }
 
     @Override
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/ModifyUserActionTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/ModifyUserActionTest.java
index ff4538b2..861403fa 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/ModifyUserActionTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/ModifyUserActionTest.java
@@ -108,6 +108,7 @@ public class ModifyUserActionTest
 
         final byte[] input = sb.toString().getBytes();
         final InputStream inputStream = new ByteArrayInputStream(input);
+        final String request = "/CADCtest?idType=http";
 
         // Should match the JSON above, without the e-mail modification.
         Principal principal = new HttpPrincipal("CADCtest");
@@ -119,8 +120,6 @@ public class ModifyUserActionTest
         personalDetail.email = "CADC.Test@nrc-cnrc.gc.ca";
         userObject.details.add(personalDetail);
 
-        final HttpServletRequest mockRequest =
-                createMock(HttpServletRequest.class);
         final SyncOutput mockSyncOut =
                 createMock(SyncOutput.class);
 
@@ -130,11 +129,9 @@ public class ModifyUserActionTest
 
         expect(mockUserPersistence.modifyUser(userObject)).andReturn(
                 userObject).once();
-//
-//        expect(mockRequest.getRemoteAddr()).andReturn(requestURL).
-//                once();
 
-        mockSyncOut.setHeader("Location", "/CADCtest?idType=http");
+
+        mockSyncOut.setHeader("Location", request);
         expectLastCall().once();
 
         mockSyncOut.setCode(303);
@@ -143,9 +140,9 @@ public class ModifyUserActionTest
         mockSyncOut.setHeader("Content-Type", "application/json");
         expectLastCall().once();
 
-        replay(mockRequest, mockSyncOut, mockUserPersistence);
+        replay(mockSyncOut, mockUserPersistence);
 
-        final ModifyUserAction testSubject = new ModifyUserAction(inputStream)
+        final ModifyUserAction testSubject = new ModifyUserAction(inputStream, request)
         {
             @Override
             @SuppressWarnings("unchecked")
@@ -161,6 +158,6 @@ public class ModifyUserActionTest
         testSubject.setLogInfo(logInfo);
         testSubject.doAction();
 
-        verify(mockRequest, mockSyncOut, mockUserPersistence);
+        verify(mockSyncOut, mockUserPersistence);
     }
 }
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/UserActionFactoryTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/UserActionFactoryTest.java
index 93aa2a54..54db0b92 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/UserActionFactoryTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/UserActionFactoryTest.java
@@ -175,11 +175,12 @@ public class UserActionFactoryTest
 
             HttpServletRequest request = EasyMock.createMock(HttpServletRequest.class);
             EasyMock.expect(request.getPathInfo()).andReturn("userName");
-            //EasyMock.expect(request.getRequestURL()).andReturn(sb);
-            //EasyMock.expect(request.getContextPath()).andReturn("");
-            //EasyMock.expect(request.getServletPath()).andReturn("");
+            EasyMock.expect(request.getRequestURL()).andReturn(sb);
+            EasyMock.expect(request.getContextPath()).andReturn("");
+            EasyMock.expect(request.getServletPath()).andReturn("");
+            EasyMock.expect(request.getQueryString()).andReturn("");
             EasyMock.expect(request.getInputStream()).andReturn(null);
-            //EasyMock.expect(request.getParameter("idType")).andReturn("sessionID");
+//            EasyMock.expect(request.getParameter("idType")).andReturn("sessionID");
             EasyMock.replay(request);
             AbstractUserAction action = UserActionFactory.httpPostFactory().createAction(request);
             EasyMock.verify(request);
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/User.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/User.java
index 97a63b6b..70ec4238 100644
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/User.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/User.java
@@ -152,6 +152,18 @@ public class User<T extends Principal>
         return getClass().getSimpleName() + "[" + userID.getName() + "]";
     }
 
+    public <S extends UserDetails>S getUserDetail(final Class<S> userDetailsClass)
+    {
+        for (final UserDetails ud : details)
+        {
+            if (ud.getClass() == userDetailsClass)
+            {
+                return (S) ud;
+            }
+        }
+        return null;
+    }
+
     public <S extends UserDetails> Set<S> getDetails(
             final Class<S> userDetailsClass)
     {
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java
index be5ce05a..3f0a4f95 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java
@@ -83,7 +83,6 @@ import java.security.AccessControlException;
 import java.security.AccessController;
 import java.security.Principal;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -239,7 +238,7 @@ public class GMSClient implements TransferListener
     {
         final URL usersListURL = new URL(this.baseURL + "/users");
         return new HttpDownload(usersListURL,
-                                new JSONUserListInputStreamWrapper(webUsers));
+                                new JsonUserListInputStreamWrapper(webUsers));
     }
 
     /**
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/JSONUserListInputStreamWrapper.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/JSONUserListInputStreamWrapper.java
deleted file mode 100644
index 8e3a4744..00000000
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/JSONUserListInputStreamWrapper.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- ************************************************************************
- *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
- **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
- *
- *  (c) 2015.                            (c) 2015.
- *  Government of Canada                 Gouvernement du Canada
- *  National Research Council            Conseil national de recherches
- *  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
- *  All rights reserved                  Tous droits réservés
- *
- *  NRC disclaims any warranties,        Le CNRC dénie toute garantie
- *  expressed, implied, or               énoncée, implicite ou légale,
- *  statutory, of any kind with          de quelque nature que ce
- *  respect to the software,             soit, concernant le logiciel,
- *  including without limitation         y compris sans restriction
- *  any warranty of merchantability      toute garantie de valeur
- *  or fitness for a particular          marchande ou de pertinence
- *  purpose. NRC shall not be            pour un usage particulier.
- *  liable in any event for any          Le CNRC ne pourra en aucun cas
- *  damages, whether direct or           être tenu responsable de tout
- *  indirect, special or general,        dommage, direct ou indirect,
- *  consequential or incidental,         particulier ou général,
- *  arising from the use of the          accessoire ou fortuit, résultant
- *  software.  Neither the name          de l'utilisation du logiciel. Ni
- *  of the National Research             le nom du Conseil National de
- *  Council of Canada nor the            Recherches du Canada ni les noms
- *  names of its contributors may        de ses  participants ne peuvent
- *  be used to endorse or promote        être utilisés pour approuver ou
- *  products derived from this           promouvoir les produits dérivés
- *  software without specific prior      de ce logiciel sans autorisation
- *  written permission.                  préalable et particulière
- *                                       par écrit.
- *
- *  This file is part of the             Ce fichier fait partie du projet
- *  OpenCADC project.                    OpenCADC.
- *
- *  OpenCADC is free software:           OpenCADC est un logiciel libre ;
- *  you can redistribute it and/or       vous pouvez le redistribuer ou le
- *  modify it under the terms of         modifier suivant les termes de
- *  the GNU Affero General Public        la “GNU Affero General Public
- *  License as published by the          License” telle que publiée
- *  Free Software Foundation,            par la Free Software Foundation
- *  either version 3 of the              : soit la version 3 de cette
- *  License, or (at your option)         licence, soit (à votre gré)
- *  any later version.                   toute version ultérieure.
- *
- *  OpenCADC is distributed in the       OpenCADC est distribué
- *  hope that it will be useful,         dans l’espoir qu’il vous
- *  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
- *  without even the implied             GARANTIE : sans même la garantie
- *  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
- *  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
- *  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
- *  General Public License for           Générale Publique GNU Affero
- *  more details.                        pour plus de détails.
- *
- *  You should have received             Vous devriez avoir reçu une
- *  a copy of the GNU Affero             copie de la Licence Générale
- *  General Public License along         Publique GNU Affero avec
- *  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
- *  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
- *                                       <http://www.gnu.org/licenses/>.
- *
- *
- ************************************************************************
- */
-
-package ca.nrc.cadc.ac.client;
-
-import ca.nrc.cadc.ac.PersonalDetails;
-import ca.nrc.cadc.ac.User;
-import ca.nrc.cadc.auth.HttpPrincipal;
-import ca.nrc.cadc.net.InputStreamWrapper;
-import ca.nrc.cadc.util.StringUtil;
-import org.apache.log4j.Logger;
-import org.json.JSONObject;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.List;
-
-public class JSONUserListInputStreamWrapper implements InputStreamWrapper
-{
-    private static final Logger LOGGER = Logger
-            .getLogger(JSONUserListInputStreamWrapper.class);
-    private final List<User<HttpPrincipal>> output;
-
-
-    public JSONUserListInputStreamWrapper(
-            final List<User<HttpPrincipal>> output)
-    {
-        this.output = output;
-    }
-
-
-    /**
-     * Read the stream in.
-     *
-     * @param inputStream The stream to read from.
-     * @throws IOException Any reading exceptions.
-     */
-    @Override
-    public void read(final InputStream inputStream) throws IOException
-    {
-        String line = null;
-
-        try
-        {
-            final InputStreamReader inReader =
-                    new InputStreamReader(inputStream);
-            final BufferedReader reader = new BufferedReader(inReader);
-
-            while (StringUtil.hasText(line = reader.readLine()))
-            {
-                // Deal with arrays stuff.
-                while (line.startsWith("[") || line.startsWith(","))
-                {
-                    line = line.substring(1);
-                }
-
-                while (line.endsWith("]") || line.endsWith(","))
-                {
-                    line = line.substring(0, (line.length() - 1));
-                }
-
-                if (StringUtil.hasText(line))
-                {
-                    LOGGER.debug(String.format("Reading: %s", line));
-
-                    final JSONObject jsonObject = new JSONObject(line);
-                    final User<HttpPrincipal> webUser =
-                            new User<HttpPrincipal>(
-                                    new HttpPrincipal(jsonObject
-                                                              .getString("id")));
-                    final String firstName = jsonObject.getString("firstName");
-                    final String lastName = jsonObject.getString("lastName");
-
-                    webUser.details
-                            .add(new PersonalDetails(firstName, lastName));
-
-                    output.add(webUser);
-                }
-            }
-        }
-        catch (Exception bug)
-        {
-            throw new IOException(bug + (StringUtil.hasText(line)
-                                         ? "Error line is " + line : ""));
-        }
-    }
-}
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/JsonUserListInputStreamWrapper.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/JsonUserListInputStreamWrapper.java
index 8e3a4744..f00b0aee 100644
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/JsonUserListInputStreamWrapper.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/JsonUserListInputStreamWrapper.java
@@ -82,15 +82,15 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.List;
 
-public class JSONUserListInputStreamWrapper implements InputStreamWrapper
+public class JsonUserListInputStreamWrapper implements InputStreamWrapper
 {
     private static final Logger LOGGER = Logger
-            .getLogger(JSONUserListInputStreamWrapper.class);
+            .getLogger(JsonUserListInputStreamWrapper.class);
     private final List<User<HttpPrincipal>> output;
 
 
-    public JSONUserListInputStreamWrapper(
-            final List<User<HttpPrincipal>> output)
+    public JsonUserListInputStreamWrapper(
+        final List<User<HttpPrincipal>> output)
     {
         this.output = output;
     }
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/UserClient.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/UserClient.java
index c108f030..a22a7519 100644
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/UserClient.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/UserClient.java
@@ -264,7 +264,7 @@ public class UserClient
             }
             else if (principal instanceof NumericPrincipal)
             {
-            	idTypeStr = IdentityType.UID.getValue();
+            	idTypeStr = IdentityType.CADC.getValue();
             }
             else if (principal instanceof CookiePrincipal)
             {
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/AbstractXML.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/AbstractReaderWriter.java
similarity index 99%
rename from projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/AbstractXML.java
rename to projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/AbstractReaderWriter.java
index db221a9d..7401185b 100644
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/AbstractXML.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/AbstractReaderWriter.java
@@ -100,9 +100,9 @@ import java.util.List;
 import java.util.Set;
 
 /**
- * AbstractXML TODO describe class
+ * AbstractReaderWriter TODO describe class
  */
-public abstract class AbstractXML
+public abstract class AbstractReaderWriter
 {
     /**
      * Write to root Element to a writer.
@@ -120,244 +120,58 @@ public abstract class AbstractXML
     }
 
     /**
-     * Get a UserRequest object from a JDOM element.
+     * Get a User object from a JDOM element.
      *
-     * @param element The UserRequest JDOM element.
-     * @return A UserRequest object.
+     * @param element The User JDOM element.
+     * @return A User object.
      * @throws ReaderException
      */
-    protected final Group getGroup(Element element)
+    protected final User<Principal> getUser(Element element)
         throws ReaderException
     {
-        String uri = element.getAttributeValue("uri");
-        if (uri == null)
+        // userID element of the User element
+        Element userIDElement = element.getChild("userID");
+        if (userIDElement == null)
         {
-            String error = "group missing required uri attribute";
+            String error = "userID element not found in user element";
             throw new ReaderException(error);
         }
 
-        // Group groupID
-        int index = uri.indexOf(AC.GROUP_URI);
-        if (index == -1)
+        // identity element of the userID element
+        Element userIDIdentityElement = userIDElement.getChild("identity");
+        if (userIDIdentityElement == null)
         {
-            String error = "group uri attribute malformed: " + uri;
+            String error = "identity element not found in userID element";
             throw new ReaderException(error);
         }
-        String groupID = uri.substring(AC.GROUP_URI.length());
-
-        // Group owner
-        User<? extends Principal> user = null;
-        Element ownerElement = element.getChild("owner");
-        if (ownerElement != null)
-        {
-            // Owner user
-            Element userElement = ownerElement.getChild("user");
-            if (userElement == null)
-            {
-                String error = "owner missing required user element";
-                throw new ReaderException(error);
-            }
-            user = getUser(userElement);
-        }
-
-        Group group = new Group(groupID, user);
-
-        // description
-        Element descriptionElement = element.getChild("description");
-        if (descriptionElement != null)
-        {
-            group.description = descriptionElement.getText();
-        }
-
-        // lastModified
-        Element lastModifiedElement = element.getChild("lastModified");
-        if (lastModifiedElement != null)
-        {
-            try
-            {
-                DateFormat df = DateUtil.getDateFormat(DateUtil.IVOA_DATE_FORMAT, DateUtil.UTC);
-                group.lastModified = df.parse(lastModifiedElement.getText());
-            }
-            catch (ParseException e)
-            {
-                String error = "Unable to parse group lastModified because " + e.getMessage();
-
-                throw new ReaderException(error);
-            }
-        }
-
-        // properties
-        Element propertiesElement = element.getChild("properties");
-        if (propertiesElement != null)
-        {
-            List<Element> propertyElements = propertiesElement.getChildren("property");
-            for (Element propertyElement : propertyElements)
-            {
-                group.getProperties().add(getGroupProperty(propertyElement));
-            }
-        }
-
-        // groupMembers
-        Element groupMembersElement = element.getChild("groupMembers");
-        if (groupMembersElement != null)
-        {
-            List<Element> groupElements = groupMembersElement.getChildren("group");
-            for (Element groupMember : groupElements)
-            {
-                group.getGroupMembers().add(getGroup(groupMember));
-            }
-        }
-
-        // userMembers
-        Element userMembersElement = element.getChild("userMembers");
-        if (userMembersElement != null)
-        {
-            List<Element> userElements = userMembersElement.getChildren("user");
-            for (Element userMember : userElements)
-            {
-                group.getUserMembers().add(getUser(userMember));
-            }
-        }
 
-        // groupAdmins
-        Element groupAdminsElement = element.getChild("groupAdmins");
-        if (groupAdminsElement != null)
-        {
-            List<Element> groupElements = groupAdminsElement.getChildren("group");
-            for (Element groupMember : groupElements)
-            {
-                group.getGroupAdmins().add(getGroup(groupMember));
-            }
-        }
+        Principal userID = getPrincipal(userIDIdentityElement);
+        User<Principal> user = new User<Principal>(userID);
 
-        // userAdmins
-        Element userAdminsElement = element.getChild("userAdmins");
-        if (userAdminsElement != null)
+        // identities
+        Element identitiesElement = element.getChild("identities");
+        if (identitiesElement != null)
         {
-            List<Element> userElements = userAdminsElement.getChildren("user");
-            for (Element userMember : userElements)
+            List<Element> identityElements = identitiesElement.getChildren("identity");
+            for (Element identityElement : identityElements)
             {
-                group.getUserAdmins().add(getUser(userMember));
+                user.getIdentities().add(getPrincipal(identityElement));
             }
-        }
-
-        return group;
-    }
-
-    /**
-     * Get a JDOM element from a Group object.
-     *
-     * @param group The UserRequest.
-     * @return A JDOM Group representation.
-     * @throws WriterException
-     */
-    protected final Element getElement(Group group)
-        throws WriterException
-    {
-        return getElement(group, true);
-    }
-
-    /**
-     * Get a JDOM element from a Group object.
-     *
-     * @param group The UserRequest.
-     * @param deepCopy Return all Group elements.
-     * @return A JDOM Group representation.
-     * @throws WriterException
-     */
-    protected final Element getElement(Group group, boolean deepCopy)
-        throws WriterException
-    {
-        // Create the root group element.
-        Element groupElement = new Element("group");
-        String groupURI = AC.GROUP_URI + group.getID();
-        groupElement.setAttribute(new Attribute("uri", groupURI));
 
-        // Group owner
-        if (group.getOwner() != null)
-        {
-            Element ownerElement = new Element("owner");
-            Element userElement = getElement(group.getOwner());
-            ownerElement.addContent(userElement);
-            groupElement.addContent(ownerElement);
         }
 
-        if (deepCopy)
+        // details
+        Element detailsElement = element.getChild("details");
+        if (detailsElement != null)
         {
-            // Group description
-            if (group.description != null)
-            {
-                Element descriptionElement = new Element("description");
-                descriptionElement.setText(group.description);
-                groupElement.addContent(descriptionElement);
-            }
-
-            // lastModified
-            if (group.lastModified != null)
-            {
-                Element lastModifiedElement = new Element("lastModified");
-                DateFormat df = DateUtil.getDateFormat(DateUtil.IVOA_DATE_FORMAT, DateUtil.UTC);
-                lastModifiedElement.setText(df.format(group.lastModified));
-                groupElement.addContent(lastModifiedElement);
-            }
-
-            // Group properties
-            if (!group.getProperties().isEmpty())
-            {
-                Element propertiesElement = new Element("properties");
-                for (GroupProperty property : group.getProperties())
-                {
-                    propertiesElement.addContent(getElement(property));
-                }
-                groupElement.addContent(propertiesElement);
-            }
-
-            // Group groupMembers.
-            if ((group.getGroupMembers() != null) && (!group.getGroupMembers().isEmpty()))
-            {
-                Element groupMembersElement = new Element("groupMembers");
-                for (Group groupMember : group.getGroupMembers())
-                {
-                    groupMembersElement.addContent(getElement(groupMember, false));
-                }
-                groupElement.addContent(groupMembersElement);
-            }
-
-            // Group userMembers
-            if ((group.getUserMembers() != null) && (!group.getUserMembers().isEmpty()))
-            {
-                Element userMembersElement = new Element("userMembers");
-                for (User<? extends Principal> userMember : group.getUserMembers())
-                {
-                    userMembersElement.addContent(getElement(userMember));
-                }
-                groupElement.addContent(userMembersElement);
-            }
-
-            // Group groupAdmins.
-            if ((group.getGroupAdmins() != null) && (!group.getGroupAdmins().isEmpty()))
-            {
-                Element groupAdminsElement = new Element("groupAdmins");
-                for (Group groupMember : group.getGroupAdmins())
-                {
-                    groupAdminsElement.addContent(getElement(groupMember, false));
-                }
-                groupElement.addContent(groupAdminsElement);
-            }
-
-            // Group userAdmins
-            if ((group.getUserAdmins() != null) && (!group.getUserAdmins().isEmpty()))
+            List<Element> userDetailsElements = detailsElement.getChildren("userDetails");
+            for (Element userDetailsElement : userDetailsElements)
             {
-                Element userAdminsElement = new Element("userAdmins");
-                for (User<? extends Principal> userMember : group.getUserAdmins())
-                {
-                    userAdminsElement.addContent(getElement(userMember));
-                }
-                groupElement.addContent(userAdminsElement);
+                user.details.add(getUserDetails(userDetailsElement));
             }
         }
 
-        return groupElement;
+        return user;
     }
 
     /**
@@ -392,128 +206,364 @@ public abstract class AbstractXML
     }
 
     /**
-     * Get a JDOM element from a UserRequest object.
+     * Get a Principal object from a JDOM element.
      *
-     * @param userRequest The UserRequest.
-     * @return A JDOM UserRequest representation.
-     * @throws WriterException
+     * @param element The Principal JDOM element.
+     * @return A Principal object.
+     * @throws ReaderException
      */
-    protected final Element getElement(UserRequest<? extends Principal> userRequest)
-        throws WriterException
+    protected final Principal getPrincipal(Element element)
+        throws ReaderException
     {
-        // Create the userRequest Element.
-        Element userRequestElement = new Element("userRequest");
-
-        // user element
-        Element userElement = getElement(userRequest.getUser());
-        userRequestElement.addContent(userElement);
+        if (element == null)
+        {
+            String error = "null identity element";
+            throw new ReaderException(error);
+        }
 
-        // password element
-        Element passwordElement = new Element("password");
-        passwordElement.setText(String.valueOf(userRequest.getPassword()));
-        userRequestElement.addContent(passwordElement);
+        if (!element.getName().equals("identity"))
+        {
+            String error = "expected identity element name, found " +
+                element.getName();
+            throw new ReaderException(error);
+        }
 
-        return userRequestElement;
+        String type = element.getAttributeValue("type");
+        if (type == null)
+        {
+            String error = "type attribute not found in identity element" +
+                element.getName();
+            throw new ReaderException(error);
+        }
+
+        String identity = element.getText();
+        Principal principal;
+        if (type.equals(IdentityType.OPENID.getValue()))
+        {
+            principal = new OpenIdPrincipal(identity);
+        }
+        else if (type.equals(IdentityType.CADC.getValue()))
+        {
+            Integer cadcID;
+            try
+            {
+                cadcID = Integer.valueOf(identity);
+            }
+            catch (NumberFormatException e)
+            {
+                String error = "Non-integer cadcID: " + identity;
+                throw new ReaderException(error);
+            }
+            principal = new NumericPrincipal(cadcID);
+        }
+        else if (type.equals(IdentityType.USERNAME.getValue()))
+        {
+            principal = new HttpPrincipal(identity);
+        }
+        else if (type.equals(IdentityType.X500.getValue()))
+        {
+            principal = new X500Principal(identity);
+        }
+        else
+        {
+            String error = "Unknown type attribute: " + type;
+            throw new ReaderException(error);
+        }
+
+        return principal;
     }
 
     /**
-     * Get a User object from a JDOM element.
+     * Get a UserDetails object from a JDOM element.
      *
-     * @param element The User JDOM element.
-     * @return A User object.
+     * @param element The UserDetails JDOM element.
+     * @return A UserDetails object.
      * @throws ReaderException
      */
-    protected final User<Principal> getUser(Element element)
+    protected final UserDetails getUserDetails(Element element)
         throws ReaderException
     {
-        // userID element of the User element
-        Element userIDElement = element.getChild("userID");
-        if (userIDElement == null)
+        if (element == null)
         {
-            String error = "userID element not found in user element";
+            throw new ReaderException("null UserDetails");
+        }
+
+        if (!element.getName().equals(UserDetails.NAME))
+        {
+            String error = "expected element name userDetails, found " +
+                element.getName();
             throw new ReaderException(error);
         }
 
-        // identity element of the userID element
-        Element userIDIdentityElement = userIDElement.getChild("identity");
-        if (userIDIdentityElement == null)
+        String type = element.getAttributeValue(UserDetails.TYPE_ATTRIBUTE);
+        if (type == null)
         {
-            String error = "identity element not found in userID element";
+            String error = "userDetails missing required attribute type";
             throw new ReaderException(error);
         }
 
-        Principal userID = getPrincipal(userIDIdentityElement);
-        User<Principal> user = new User<Principal>(userID);
+        if (type.equals(PosixDetails.NAME))
+        {
+            return getPosixDetails(element);
+        }
+        if (type.equals(PersonalDetails.NAME))
+        {
+            return getPersonalDetails(element);
+        }
 
-        // identities
-        Element identitiesElement = element.getChild("identities");
-        if (identitiesElement != null)
+        String error = "Unknown UserDetails attribute type " + type;
+        throw new ReaderException(error);
+    }
+
+    /**
+     * Get a PosixDetails object from a JDOM element.
+     *
+     * @param element The PosixDetails JDOM element.
+     * @return A PosixDetails object.
+     * @throws ReaderException
+     */
+    protected final PosixDetails getPosixDetails(Element element)
+        throws ReaderException
+    {
+        // uid
+        Element uidElement = element.getChild(PosixDetails.UID);
+        if (uidElement == null)
         {
-            List<Element> identityElements = identitiesElement.getChildren("identity");
-            for (Element identityElement : identityElements)
-            {
-                user.getIdentities().add(getPrincipal(identityElement));
-            }
+            String error = "posixDetails missing required element uid";
+            throw new ReaderException(error);
+        }
+        long uid;
+        try
+        {
+            uid = Long.valueOf(uidElement.getText());
+        }
+        catch (NumberFormatException e)
+        {
+            String error = "Cannot parse posixDetails uid to a long";
+            throw new ReaderException(error);
+        }
 
+        // gid
+        Element gidElement = element.getChild(PosixDetails.GID);
+        if (gidElement == null)
+        {
+            String error = "posixDetails missing required element gid";
+            throw new ReaderException(error);
+        }
+        long gid;
+        try
+        {
+            gid = Long.valueOf(gidElement.getText());
+        }
+        catch (NumberFormatException e)
+        {
+            String error = "Cannot parse posixDetails gid to a long";
+            throw new ReaderException(error);
         }
 
-        // details
-        Element detailsElement = element.getChild("details");
-        if (detailsElement != null)
+        // homeDirectory
+        Element homeDirElement = element.getChild(PosixDetails.HOME_DIRECTORY);
+        if (homeDirElement == null)
         {
-            List<Element> userDetailsElements = detailsElement.getChildren("userDetails");
-            for (Element userDetailsElement : userDetailsElements)
-            {
-                user.details.add(getUserDetails(userDetailsElement));
-            }
+            String error = "posixDetails missing required element homeDirectory";
+            throw new ReaderException(error);
         }
+        String homeDirectory = homeDirElement.getText();
 
-        return user;
+        return new PosixDetails(uid, gid, homeDirectory);
     }
 
     /**
-     * Get a JDOM element from a User object.
+     * Get a PersonalDetails object from a JDOM element.
      *
-     * @param user The User.
-     * @return A JDOM User representation.
-     * @throws WriterException
+     * @param element The PersonalDetails JDOM element.
+     * @return A PersonalDetails object.
+     * @throws ReaderException
      */
-    protected final Element getElement(User<? extends Principal> user)
-        throws WriterException
+    protected final PersonalDetails getPersonalDetails(Element element)
+        throws ReaderException
     {
-        // Create the user Element.
-        Element userElement = new Element("user");
+        // firstName
+        Element firstNameElement = element.getChild(PersonalDetails.FIRSTNAME);
+        if (firstNameElement == null)
+        {
+            String error = "personalDetails missing required element firstName";
+            throw new ReaderException(error);
+        }
+        String firstName = firstNameElement.getText();
 
-        // userID element
-        Element userIDElement = new Element("userID");
-        userIDElement.addContent(getElement(user.getUserID()));
-        userElement.addContent(userIDElement);
+        // lastName
+        Element lastNameElement = element.getChild(PersonalDetails.LASTNAME);
+        if (lastNameElement == null)
+        {
+            String error = "personalDetails missing required element lastName";
+            throw new ReaderException(error);
+        }
+        String lastName = lastNameElement.getText();
 
-        // identities
-        Set<Principal> identities = user.getIdentities();
-        if (!identities.isEmpty())
+        PersonalDetails details = new PersonalDetails(firstName, lastName);
+
+        // email
+        Element emailElement = element.getChild(PersonalDetails.EMAIL);
+        if (emailElement != null)
         {
-            Element identitiesElement = new Element("identities");
-            for (Principal identity : identities)
+            details.email = emailElement.getText();
+        }
+
+        // address
+        Element addressElement = element.getChild(PersonalDetails.ADDRESS);
+        if (addressElement != null)
+        {
+            details.address = addressElement.getText();
+        }
+
+        // institute
+        Element instituteElement = element.getChild(PersonalDetails.INSTITUTE);
+        if (instituteElement != null)
+        {
+            details.institute = instituteElement.getText();
+        }
+
+        // city
+        Element cityElement = element.getChild(PersonalDetails.CITY);
+        if (cityElement != null)
+        {
+            details.city = cityElement.getText();
+        }
+
+        // country
+        Element countryElement = element.getChild(PersonalDetails.COUNTRY);
+        if (countryElement != null)
+        {
+            details.country = countryElement.getText();
+        }
+
+        return details;
+    }
+
+    /**
+     * Get a UserRequest object from a JDOM element.
+     *
+     * @param element The UserRequest JDOM element.
+     * @return A UserRequest object.
+     * @throws ReaderException
+     */
+    protected final Group getGroup(Element element)
+        throws ReaderException
+    {
+        String uri = element.getAttributeValue("uri");
+        if (uri == null)
+        {
+            String error = "group missing required uri attribute";
+            throw new ReaderException(error);
+        }
+
+        // Group groupID
+        int index = uri.indexOf(AC.GROUP_URI);
+        if (index == -1)
+        {
+            String error = "group uri attribute malformed: " + uri;
+            throw new ReaderException(error);
+        }
+        String groupID = uri.substring(AC.GROUP_URI.length());
+
+        // Group owner
+        User<? extends Principal> user = null;
+        Element ownerElement = element.getChild("owner");
+        if (ownerElement != null)
+        {
+            // Owner user
+            Element userElement = ownerElement.getChild("user");
+            if (userElement == null)
             {
-                identitiesElement.addContent(getElement(identity));
+                String error = "owner missing required user element";
+                throw new ReaderException(error);
             }
-            userElement.addContent(identitiesElement);
+            user = getUser(userElement);
         }
 
-        // details
-        if (!user.details.isEmpty())
+        Group group = new Group(groupID, user);
+
+        // description
+        Element descriptionElement = element.getChild("description");
+        if (descriptionElement != null)
         {
-            Element detailsElement = new Element("details");
-            Set<UserDetails> userDetails = user.details;
-            for (UserDetails userDetail : userDetails)
+            group.description = descriptionElement.getText();
+        }
+
+        // lastModified
+        Element lastModifiedElement = element.getChild("lastModified");
+        if (lastModifiedElement != null)
+        {
+            try
             {
-                detailsElement.addContent(getElement(userDetail));
+                DateFormat df = DateUtil.getDateFormat(DateUtil.IVOA_DATE_FORMAT, DateUtil.UTC);
+                group.lastModified = df.parse(lastModifiedElement.getText());
+            }
+            catch (ParseException e)
+            {
+                String error = "Unable to parse group lastModified because " + e.getMessage();
+
+                throw new ReaderException(error);
+            }
+        }
+
+        // properties
+        Element propertiesElement = element.getChild("properties");
+        if (propertiesElement != null)
+        {
+            List<Element> propertyElements = propertiesElement.getChildren("property");
+            for (Element propertyElement : propertyElements)
+            {
+                group.getProperties().add(getGroupProperty(propertyElement));
+            }
+        }
+
+        // groupMembers
+        Element groupMembersElement = element.getChild("groupMembers");
+        if (groupMembersElement != null)
+        {
+            List<Element> groupElements = groupMembersElement.getChildren("group");
+            for (Element groupMember : groupElements)
+            {
+                group.getGroupMembers().add(getGroup(groupMember));
+            }
+        }
+
+        // userMembers
+        Element userMembersElement = element.getChild("userMembers");
+        if (userMembersElement != null)
+        {
+            List<Element> userElements = userMembersElement.getChildren("user");
+            for (Element userMember : userElements)
+            {
+                group.getUserMembers().add(getUser(userMember));
+            }
+        }
+
+        // groupAdmins
+        Element groupAdminsElement = element.getChild("groupAdmins");
+        if (groupAdminsElement != null)
+        {
+            List<Element> groupElements = groupAdminsElement.getChildren("group");
+            for (Element groupMember : groupElements)
+            {
+                group.getGroupAdmins().add(getGroup(groupMember));
+            }
+        }
+
+        // userAdmins
+        Element userAdminsElement = element.getChild("userAdmins");
+        if (userAdminsElement != null)
+        {
+            List<Element> userElements = userAdminsElement.getChildren("user");
+            for (Element userMember : userElements)
+            {
+                group.getUserAdmins().add(getUser(userMember));
             }
-            userElement.addContent(detailsElement);
         }
 
-        return userElement;
+        return group;
     }
 
     /**
@@ -575,329 +625,117 @@ public abstract class AbstractXML
     }
 
     /**
-     * Get a JDOM element from a GroupProperty object.
+     * Get a JDOM element from a User object.
      *
-     * @param property The GroupProperty.
-     * @return A JDOM GroupProperty representation.
+     * @param user The User.
+     * @return A JDOM User representation.
      * @throws WriterException
      */
-    protected final Element getElement(GroupProperty property)
+    protected final Element getElement(User<? extends Principal> user)
         throws WriterException
     {
-        if (property == null)
-        {
-            throw new WriterException("null GroupProperty");
-        }
-
-        Element propertyElement = new Element(GroupProperty.NAME);
-        propertyElement.setAttribute(GroupProperty.KEY_ATTRIBUTE,
-            property.getKey());
-        if (property.isReadOnly())
-        {
-            propertyElement.setAttribute(GroupProperty.READONLY_ATTRIBUTE,
-                "true");
-        }
-
-        Object value = property.getValue();
-        if ((value instanceof String))
-        {
-            propertyElement.setAttribute(GroupProperty.TYPE_ATTRIBUTE,
-                GroupProperty.STRING_TYPE);
-        }
-        else if ((value instanceof Integer))
-        {
-            propertyElement.setAttribute(GroupProperty.TYPE_ATTRIBUTE,
-                GroupProperty.INTEGER_TYPE);
-        }
-        else
-        {
-            String error = "Unsupported value type: " +
-                value.getClass().getSimpleName();
-            throw new IllegalArgumentException(error);
-        }
-        propertyElement.setText(String.valueOf(property.getValue()));
-
-        return propertyElement;
-    }
-
-    /**
-     * Get a Principal object from a JDOM element.
-     *
-     * @param element The Principal JDOM element.
-     * @return A Principal object.
-     * @throws ReaderException
-     */
-    protected final Principal getPrincipal(Element element)
-        throws ReaderException
-    {
-        if (element == null)
-        {
-            String error = "null identity element";
-            throw new ReaderException(error);
-        }
+        // Create the user Element.
+        Element userElement = new Element("user");
 
-        if (!element.getName().equals("identity"))
-        {
-            String error = "expected identity element name, found " +
-                element.getName();
-            throw new ReaderException(error);
-        }
+        // userID element
+        Element userIDElement = new Element("userID");
+        userIDElement.addContent(getElement(user.getUserID()));
+        userElement.addContent(userIDElement);
 
-        String type = element.getAttributeValue("type");
-        if (type == null)
+        // identities
+        Set<Principal> identities = user.getIdentities();
+        if (!identities.isEmpty())
         {
-            String error = "type attribute not found in identity element" +
-                element.getName();
-            throw new ReaderException(error);
+            Element identitiesElement = new Element("identities");
+            for (Principal identity : identities)
+            {
+                identitiesElement.addContent(getElement(identity));
+            }
+            userElement.addContent(identitiesElement);
         }
 
-        String identity = element.getText();
-        Principal principal;
-        if (type.equals(IdentityType.OPENID.getValue()))
-        {
-            principal = new OpenIdPrincipal(identity);
-        }
-        else if (type.equals(IdentityType.UID.getValue()))
+        // details
+        if (!user.details.isEmpty())
         {
-            Integer cadcID;
-            try
-            {
-                cadcID = Integer.valueOf(identity);
-            }
-            catch (NumberFormatException e)
+            Element detailsElement = new Element("details");
+            Set<UserDetails> userDetails = user.details;
+            for (UserDetails userDetail : userDetails)
             {
-                String error = "Non-integer cadcID: " + identity;
-                throw new ReaderException(error);
+                detailsElement.addContent(getElement(userDetail));
             }
-            principal = new NumericPrincipal(cadcID);
-        }
-        else if (type.equals(IdentityType.USERNAME.getValue()))
-        {
-            principal = new HttpPrincipal(identity);
-        }
-        else if (type.equals(IdentityType.X500.getValue()))
-        {
-            principal = new X500Principal(identity);
-        }
-        else
-        {
-            String error = "Unknown type attribute: " + type;
-            throw new ReaderException(error);
+            userElement.addContent(detailsElement);
         }
 
-        return principal;
+        return userElement;
     }
 
     /**
-     * Get a JDOM element from a Principal object.
+     * Get a JDOM element from a UserRequest object.
      *
-     * @param identity The Principal.
-     * @return A JDOM UserDetails representation.
+     * @param userRequest The UserRequest.
+     * @return A JDOM UserRequest representation.
      * @throws WriterException
-     */
-    protected final Element getElement(Principal identity)
-        throws WriterException
-    {
-        if (identity == null)
-        {
-            String error = "null identity";
-            throw new WriterException(error);
-        }
-
-        Element identityElement = new Element("identity");
-        if ((identity instanceof HttpPrincipal))
-        {
-            identityElement.setAttribute("type", IdentityType.USERNAME.getValue());
-        }
-        else if ((identity instanceof NumericPrincipal))
-        {
-            identityElement.setAttribute("type", IdentityType.UID.getValue());
-        }
-        else if ((identity instanceof OpenIdPrincipal))
-        {
-            identityElement.setAttribute("type", IdentityType.OPENID.getValue());
-        }
-        else if ((identity instanceof X500Principal))
-        {
-            identityElement.setAttribute("type", IdentityType.X500.getValue());
-        }
-        else
-        {
-            String error = "Unsupported Principal type " +
-                identity.getClass().getSimpleName();
-            throw new IllegalArgumentException(error);
-        }
-        identityElement.setText(identity.getName());
-
-        return identityElement;
-    }
-
-    /**
-     * Get a UserDetails object from a JDOM element.
-     *
-     * @param element The UserDetails JDOM element.
-     * @return A UserDetails object.
-     * @throws ReaderException
-     */
-    protected final UserDetails getUserDetails(Element element)
-        throws ReaderException
-    {
-        if (element == null)
-        {
-            throw new ReaderException("null UserDetails");
-        }
-
-        if (!element.getName().equals(UserDetails.NAME))
-        {
-            String error = "expected element name userDetails, found " +
-                element.getName();
-            throw new ReaderException(error);
-        }
-
-        String type = element.getAttributeValue(UserDetails.TYPE_ATTRIBUTE);
-        if (type == null)
-        {
-            String error = "userDetails missing required attribute type";
-            throw new ReaderException(error);
-        }
-
-        if (type.equals(PosixDetails.NAME))
-        {
-            return getPosixDetails(element);
-        }
-        if (type.equals(PersonalDetails.NAME))
-        {
-            return getPersonalDetails(element);
-        }
-
-        String error = "Unknown UserDetails attribute type " + type;
-        throw new ReaderException(error);
-    }
-
-    /**
-     * Get a PosixDetails object from a JDOM element.
-     *
-     * @param element The PosixDetails JDOM element.
-     * @return A PosixDetails object.
-     * @throws ReaderException
-     */
-    protected final PosixDetails getPosixDetails(Element element)
-        throws ReaderException
-    {
-        // uid
-        Element uidElement = element.getChild(PosixDetails.UID);
-        if (uidElement == null)
-        {
-            String error = "posixDetails missing required element uid";
-            throw new ReaderException(error);
-        }
-        long uid;
-        try
-        {
-            uid = Long.valueOf(uidElement.getText());
-        }
-        catch (NumberFormatException e)
-        {
-            String error = "Cannot parse posixDetails uid to a long";
-            throw new ReaderException(error);
-        }
-
-        // gid
-        Element gidElement = element.getChild(PosixDetails.GID);
-        if (gidElement == null)
-        {
-            String error = "posixDetails missing required element gid";
-            throw new ReaderException(error);
-        }
-        long gid;
-        try
-        {
-            gid = Long.valueOf(gidElement.getText());
-        }
-        catch (NumberFormatException e)
-        {
-            String error = "Cannot parse posixDetails gid to a long";
-            throw new ReaderException(error);
-        }
+     */
+    protected final Element getElement(UserRequest<? extends Principal> userRequest)
+        throws WriterException
+    {
+        // Create the userRequest Element.
+        Element userRequestElement = new Element("userRequest");
 
-        // homeDirectory
-        Element homeDirElement = element.getChild(PosixDetails.HOME_DIRECTORY);
-        if (homeDirElement == null)
-        {
-            String error = "posixDetails missing required element homeDirectory";
-            throw new ReaderException(error);
-        }
-        String homeDirectory = homeDirElement.getText();
+        // user element
+        Element userElement = getElement(userRequest.getUser());
+        userRequestElement.addContent(userElement);
 
-        return new PosixDetails(uid, gid, homeDirectory);
+        // password element
+        Element passwordElement = new Element("password");
+        passwordElement.setText(String.valueOf(userRequest.getPassword()));
+        userRequestElement.addContent(passwordElement);
+
+        return userRequestElement;
     }
 
     /**
-     * Get a PersonalDetails object from a JDOM element.
+     * Get a JDOM element from a Principal object.
      *
-     * @param element The PersonalDetails JDOM element.
-     * @return A PersonalDetails object.
-     * @throws ReaderException
+     * @param identity The Principal.
+     * @return A JDOM UserDetails representation.
+     * @throws WriterException
      */
-    protected final PersonalDetails getPersonalDetails(Element element)
-        throws ReaderException
+    protected final Element getElement(Principal identity)
+        throws WriterException
     {
-        // firstName
-        Element firstNameElement = element.getChild(PersonalDetails.FIRSTNAME);
-        if (firstNameElement == null)
-        {
-            String error = "personalDetails missing required element firstName";
-            throw new ReaderException(error);
-        }
-        String firstName = firstNameElement.getText();
-
-        // lastName
-        Element lastNameElement = element.getChild(PersonalDetails.LASTNAME);
-        if (lastNameElement == null)
+        if (identity == null)
         {
-            String error = "personalDetails missing required element lastName";
-            throw new ReaderException(error);
+            String error = "null identity";
+            throw new WriterException(error);
         }
-        String lastName = lastNameElement.getText();
-
-        PersonalDetails details = new PersonalDetails(firstName, lastName);
 
-        // email
-        Element emailElement = element.getChild(PersonalDetails.EMAIL);
-        if (emailElement != null)
+        Element identityElement = new Element("identity");
+        if ((identity instanceof HttpPrincipal))
         {
-            details.email = emailElement.getText();
+            identityElement.setAttribute("type", IdentityType.USERNAME.getValue());
         }
-
-        // address
-        Element addressElement = element.getChild(PersonalDetails.ADDRESS);
-        if (addressElement != null)
+        else if ((identity instanceof NumericPrincipal))
         {
-            details.address = addressElement.getText();
+            identityElement.setAttribute("type", IdentityType.CADC.getValue());
         }
-
-        // institute
-        Element instituteElement = element.getChild(PersonalDetails.INSTITUTE);
-        if (instituteElement != null)
+        else if ((identity instanceof OpenIdPrincipal))
         {
-            details.institute = instituteElement.getText();
+            identityElement.setAttribute("type", IdentityType.OPENID.getValue());
         }
-
-        // city
-        Element cityElement = element.getChild(PersonalDetails.CITY);
-        if (cityElement != null)
+        else if ((identity instanceof X500Principal))
         {
-            details.city = cityElement.getText();
+            identityElement.setAttribute("type", IdentityType.X500.getValue());
         }
-
-        // country
-        Element countryElement = element.getChild(PersonalDetails.COUNTRY);
-        if (countryElement != null)
+        else
         {
-            details.country = countryElement.getText();
+            String error = "Unsupported Principal type " +
+                identity.getClass().getSimpleName();
+            throw new IllegalArgumentException(error);
         }
+        identityElement.setText(identity.getName());
 
-        return details;
+        return identityElement;
     }
 
     /**
@@ -1014,5 +852,166 @@ public abstract class AbstractXML
         return detailsElement;
     }
 
+    /**
+     * Get a JDOM element from a Group object.
+     *
+     * @param group The UserRequest.
+     * @return A JDOM Group representation.
+     * @throws WriterException
+     */
+    protected final Element getElement(Group group)
+        throws WriterException
+    {
+        return getElement(group, true);
+    }
+
+    /**
+     * Get a JDOM element from a Group object.
+     *
+     * @param group The UserRequest.
+     * @param deepCopy Return all Group elements.
+     * @return A JDOM Group representation.
+     * @throws WriterException
+     */
+    protected final Element getElement(Group group, boolean deepCopy)
+        throws WriterException
+    {
+        // Create the root group element.
+        Element groupElement = new Element("group");
+        String groupURI = AC.GROUP_URI + group.getID();
+        groupElement.setAttribute(new Attribute("uri", groupURI));
+
+        // Group owner
+        if (group.getOwner() != null)
+        {
+            Element ownerElement = new Element("owner");
+            Element userElement = getElement(group.getOwner());
+            ownerElement.addContent(userElement);
+            groupElement.addContent(ownerElement);
+        }
+
+        if (deepCopy)
+        {
+            // Group description
+            if (group.description != null)
+            {
+                Element descriptionElement = new Element("description");
+                descriptionElement.setText(group.description);
+                groupElement.addContent(descriptionElement);
+            }
+
+            // lastModified
+            if (group.lastModified != null)
+            {
+                Element lastModifiedElement = new Element("lastModified");
+                DateFormat df = DateUtil.getDateFormat(DateUtil.IVOA_DATE_FORMAT, DateUtil.UTC);
+                lastModifiedElement.setText(df.format(group.lastModified));
+                groupElement.addContent(lastModifiedElement);
+            }
+
+            // Group properties
+            if (!group.getProperties().isEmpty())
+            {
+                Element propertiesElement = new Element("properties");
+                for (GroupProperty property : group.getProperties())
+                {
+                    propertiesElement.addContent(getElement(property));
+                }
+                groupElement.addContent(propertiesElement);
+            }
+
+            // Group groupMembers.
+            if ((group.getGroupMembers() != null) && (!group.getGroupMembers().isEmpty()))
+            {
+                Element groupMembersElement = new Element("groupMembers");
+                for (Group groupMember : group.getGroupMembers())
+                {
+                    groupMembersElement.addContent(getElement(groupMember, false));
+                }
+                groupElement.addContent(groupMembersElement);
+            }
+
+            // Group userMembers
+            if ((group.getUserMembers() != null) && (!group.getUserMembers().isEmpty()))
+            {
+                Element userMembersElement = new Element("userMembers");
+                for (User<? extends Principal> userMember : group.getUserMembers())
+                {
+                    userMembersElement.addContent(getElement(userMember));
+                }
+                groupElement.addContent(userMembersElement);
+            }
+
+            // Group groupAdmins.
+            if ((group.getGroupAdmins() != null) && (!group.getGroupAdmins().isEmpty()))
+            {
+                Element groupAdminsElement = new Element("groupAdmins");
+                for (Group groupMember : group.getGroupAdmins())
+                {
+                    groupAdminsElement.addContent(getElement(groupMember, false));
+                }
+                groupElement.addContent(groupAdminsElement);
+            }
+
+            // Group userAdmins
+            if ((group.getUserAdmins() != null) && (!group.getUserAdmins().isEmpty()))
+            {
+                Element userAdminsElement = new Element("userAdmins");
+                for (User<? extends Principal> userMember : group.getUserAdmins())
+                {
+                    userAdminsElement.addContent(getElement(userMember));
+                }
+                groupElement.addContent(userAdminsElement);
+            }
+        }
+
+        return groupElement;
+    }
+
+    /**
+     * Get a JDOM element from a GroupProperty object.
+     *
+     * @param property The GroupProperty.
+     * @return A JDOM GroupProperty representation.
+     * @throws WriterException
+     */
+    protected final Element getElement(GroupProperty property)
+        throws WriterException
+    {
+        if (property == null)
+        {
+            throw new WriterException("null GroupProperty");
+        }
+
+        Element propertyElement = new Element(GroupProperty.NAME);
+        propertyElement.setAttribute(GroupProperty.KEY_ATTRIBUTE,
+            property.getKey());
+        if (property.isReadOnly())
+        {
+            propertyElement.setAttribute(GroupProperty.READONLY_ATTRIBUTE,
+                "true");
+        }
+
+        Object value = property.getValue();
+        if ((value instanceof String))
+        {
+            propertyElement.setAttribute(GroupProperty.TYPE_ATTRIBUTE,
+                GroupProperty.STRING_TYPE);
+        }
+        else if ((value instanceof Integer))
+        {
+            propertyElement.setAttribute(GroupProperty.TYPE_ATTRIBUTE,
+                GroupProperty.INTEGER_TYPE);
+        }
+        else
+        {
+            String error = "Unsupported value type: " +
+                value.getClass().getSimpleName();
+            throw new IllegalArgumentException(error);
+        }
+        propertyElement.setText(String.valueOf(property.getValue()));
+
+        return propertyElement;
+    }
 
 }
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupListReader.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupListReader.java
index 10d46bbe..da9f16c8 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupListReader.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupListReader.java
@@ -89,7 +89,7 @@ import java.util.List;
  * Class to read an XML representation of a List of Groups
  * into a Collection of Group objects.
  */
-public class GroupListReader extends AbstractXML
+public class GroupListReader extends AbstractReaderWriter
 {
     /**
      * Construct a list of Group's from an XML String source.
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupListWriter.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupListWriter.java
index e406a4cd..a42cff8b 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupListWriter.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupListWriter.java
@@ -85,7 +85,7 @@ import java.util.Collection;
 /**
  * Class to write a XML representation from a Collection of Groups objects.
  */
-public class GroupListWriter extends AbstractXML
+public class GroupListWriter extends AbstractReaderWriter
 {
     /**
      * Write a Collection of Group's to a StringBuilder.
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupReader.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupReader.java
index 31de44fc..b9c64d1b 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupReader.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupReader.java
@@ -86,7 +86,7 @@ import java.net.URISyntaxException;
 /**
  * Class to read a XML representation of a Group to a Group object.
  */
-public class GroupReader extends AbstractXML
+public class GroupReader extends AbstractReaderWriter
 {
 
     /**
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupWriter.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupWriter.java
index 54eba4f2..67687c53 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupWriter.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/GroupWriter.java
@@ -82,7 +82,7 @@ import java.io.Writer;
 /**
  * Class to write a XML representation of a Group object.
  */
-public class GroupWriter extends AbstractXML
+public class GroupWriter extends AbstractReaderWriter
 {
     /**
      * Write a Group to a StringBuilder.
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserListReader.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserListReader.java
index 4877e7c2..ae454fcb 100644
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserListReader.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserListReader.java
@@ -90,7 +90,7 @@ import java.util.List;
  * Class to read an XML representation of a List of Users
  * into a List of User objects.
  */
-public class UserListReader extends AbstractXML
+public class UserListReader extends AbstractReaderWriter
 {
     /**
      * Construct a List of Users from an XML String source.
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserListWriter.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserListWriter.java
index 92697e1f..1674324d 100644
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserListWriter.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserListWriter.java
@@ -81,12 +81,11 @@ import java.io.UnsupportedEncodingException;
 import java.io.Writer;
 import java.security.Principal;
 import java.util.Collection;
-import java.util.List;
 
 /**
  * Class to write a XML representation of a List of User's.
  */
-public class UserListWriter extends AbstractXML
+public class UserListWriter extends AbstractReaderWriter
 {
     /**
      * Write a Collection of User's to a StringBuilder.
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserReader.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserReader.java
index b4181956..c3cbae20 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserReader.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserReader.java
@@ -87,7 +87,7 @@ import java.security.Principal;
 /**
  * Class to read a XML representation of a User to a User object.
  */
-public class UserReader extends AbstractXML
+public class UserReader extends AbstractReaderWriter
 {
     /**
      * Construct a User from an XML String source.
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserRequestReader.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserRequestReader.java
index 317d753c..dffef3e5 100644
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserRequestReader.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserRequestReader.java
@@ -86,7 +86,7 @@ import java.security.Principal;
 /**
  * Class to read a XML representation of a UserRequest to a UserRequest object.
  */
-public class UserRequestReader extends AbstractXML
+public class UserRequestReader extends AbstractReaderWriter
 {
     /**
      * Construct a UserRequest from an XML String source.
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserRequestWriter.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserRequestWriter.java
index ef100682..e87840d1 100644
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserRequestWriter.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserRequestWriter.java
@@ -80,7 +80,7 @@ import java.security.Principal;
 /**
  * Class to write a XML representation of a UserRequest object.
  */
-public class UserRequestWriter extends AbstractXML
+public class UserRequestWriter extends AbstractReaderWriter
 {
     /**
      * Write a UserRequest to a StringBuilder.
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserWriter.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserWriter.java
index e2fe3bf8..7df99cbb 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserWriter.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/xml/UserWriter.java
@@ -83,7 +83,7 @@ import java.security.Principal;
 /**
  * Class to write a XML representation of a User object.
  */
-public class UserWriter extends AbstractXML
+public class UserWriter extends AbstractReaderWriter
 {
     /**
      * Write a User to a StringBuilder.
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/client/JsonUserListInputStreamWrapperTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/client/JsonUserListInputStreamWrapperTest.java
index fe977e4e..d8bd34ea 100644
--- a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/client/JsonUserListInputStreamWrapperTest.java
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/client/JsonUserListInputStreamWrapperTest.java
@@ -80,15 +80,15 @@ import org.junit.Test;
 import static org.junit.Assert.*;
 
 
-public class JSONUserListInputStreamWrapperTest
+public class JsonUserListInputStreamWrapperTest
 {
     @Test
     public void readInputStream() throws Exception
     {
         final List<User<HttpPrincipal>> output =
                 new ArrayList<User<HttpPrincipal>>();
-        final JSONUserListInputStreamWrapper testSubject =
-                new JSONUserListInputStreamWrapper(output);
+        final JsonUserListInputStreamWrapper testSubject =
+                new JsonUserListInputStreamWrapper(output);
         final InputStream inputStream =
                 new ByteArrayInputStream("[{\"id\":\"CADCTest\",\"firstName\":\"CADCtest\",\"lastName\":\"USER\"}\n,{\"id\":\"User_2\",\"firstName\":\"User\",\"lastName\":\"2\"}]".getBytes());
 
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupPropertyReaderWriterTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupPropertyReaderWriterTest.java
index 172d211e..0a259847 100644
--- a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupPropertyReaderWriterTest.java
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupPropertyReaderWriterTest.java
@@ -83,7 +83,7 @@ import static org.junit.Assert.*;
  *
  * @author jburke
  */
-public class GroupPropertyReaderWriterTest extends AbstractXML
+public class GroupPropertyReaderWriterTest extends AbstractReaderWriter
 {
     private static Logger log = Logger.getLogger(GroupPropertyReaderWriterTest.class);
 
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/IdentityReaderWriterTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/IdentityReaderWriterTest.java
index 7ff46919..8bb1d674 100644
--- a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/IdentityReaderWriterTest.java
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/IdentityReaderWriterTest.java
@@ -89,7 +89,7 @@ import static org.junit.Assert.fail;
  *
  * @author jburke
  */
-public class IdentityReaderWriterTest extends AbstractXML
+public class IdentityReaderWriterTest extends AbstractReaderWriter
 {
     private static Logger log = Logger.getLogger(IdentityReaderWriterTest.class);
 
@@ -165,7 +165,7 @@ public class IdentityReaderWriterTest extends AbstractXML
          
         assertEquals(expected, actual);
 
-        // UID
+        // CADC
         expected = new NumericPrincipal(123l);
         element = getElement(expected);
         assertNotNull(element);
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/UserDetailsReaderWriterTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/UserDetailsReaderWriterTest.java
index 3a1baaa1..4a6304dd 100644
--- a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/UserDetailsReaderWriterTest.java
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/UserDetailsReaderWriterTest.java
@@ -85,7 +85,7 @@ import static org.junit.Assert.fail;
  *
  * @author jburke
  */
-public class UserDetailsReaderWriterTest extends AbstractXML
+public class UserDetailsReaderWriterTest extends AbstractReaderWriter
 {
     private static Logger log = Logger.getLogger(UserDetailsReaderWriterTest.class);
 
-- 
GitLab