diff --git a/projects/cadcAccessControl-Server/build.xml b/projects/cadcAccessControl-Server/build.xml
index 9f319e5247c77ef09e92925b8bc55aa9f3ba2153..61e94bf0246cb0ab060277064d6719988a357ca0 100644
--- a/projects/cadcAccessControl-Server/build.xml
+++ b/projects/cadcAccessControl-Server/build.xml
@@ -145,6 +145,7 @@
             <!--<test name="ca.nrc.cadc.ac.server.ldap.LdapDAOTestImpl" />-->
             <test name="ca.nrc.cadc.ac.server.ldap.LdapGroupDAOTest" />
             <!--<test name="ca.nrc.cadc.ac.server.web.GroupActionFactoryTest" />-->
+            <!--<test name="ca.nrc.cadc.ac.server.ldap.LdapUserDAOTest" />-->
             <formatter type="plain" usefile="false" />
         </junit>
     </target>
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/GroupPersistence.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/GroupPersistence.java
index 70556c684c7075afd4aafe4ddbc1191e67b4f2df..970749fdc63d7b5b19d1fa17861582ff6631c331 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/GroupPersistence.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/GroupPersistence.java
@@ -71,8 +71,8 @@ package ca.nrc.cadc.ac.server;
 import ca.nrc.cadc.ac.Group;
 import ca.nrc.cadc.ac.GroupAlreadyExistsException;
 import ca.nrc.cadc.ac.GroupNotFoundException;
+import ca.nrc.cadc.ac.IdentityType;
 import ca.nrc.cadc.ac.Role;
-import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.UserNotFoundException;
 import ca.nrc.cadc.net.TransientException;
 import java.security.AccessControlException;
@@ -145,35 +145,21 @@ public abstract interface GroupPersistence<T extends Principal>
     /**
      * Obtain a Collection of Groups that fit the given query.
      *
-     * @param user user
+     * @param userID The userID.
      * @param role Role of the user, either owner, member, or read/write.
+     * @param groupID The Group ID.
      * 
      * @return Collection of Groups matching the query, or empty Collection.
      *         Never null.
      *
      * @throws UserNotFoundException If owner or group members not valid users.
+     * @throws ca.nrc.cadc.ac.GroupNotFoundException
      * @throws TransientException If an temporary, unexpected problem occurred.
      * @throws AccessControlException If the operation is not permitted.
      */
-    public abstract Collection<Group> getGroups(User<T> user, Role role)
-        throws UserNotFoundException, TransientException,
-               AccessControlException;
-
-    /**
-     * Check whether the user is a member of the group.
-     *
-     * @param user user
-     * @param groupID ID of group
-     *
-     * @return true or false
-     *
-     * @throws GroupNotFoundException If the group was not found.
-     * @throws TransientException If an temporary, unexpected problem occurred.
-     * @throws AccessControlException If the operation is not permitted.
-     * @throws ca.nrc.cadc.ac.UserNotFoundException
-     */
-    public abstract boolean isMember(User<T> user, String groupID)
-        throws GroupNotFoundException, TransientException,
-               AccessControlException, UserNotFoundException;
+    public abstract Collection<Group> searchGroups(T userID, Role role, 
+                                                   String groupID)
+        throws UserNotFoundException, GroupNotFoundException,
+               TransientException, AccessControlException;
 
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/RequestValidator.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/RequestValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf0314829fde491147778ba8d6cc03fba197f7ad
--- /dev/null
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/RequestValidator.java
@@ -0,0 +1,179 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  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/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac.server;
+
+import ca.nrc.cadc.ac.IdentityType;
+import ca.nrc.cadc.ac.Role;
+import ca.nrc.cadc.uws.Parameter;
+import ca.nrc.cadc.uws.ParameterUtil;
+import java.util.List;
+import org.apache.log4j.Logger;
+
+/**
+ * Request Validator. This class extracts and validates the ID, TYPE, ROLE
+ * and GURI parameters.
+ *
+ */
+public class RequestValidator
+{
+    private static final Logger log = Logger.getLogger(RequestValidator.class);
+    
+    private String id;
+    private IdentityType type;
+    private Role role;
+    private String guri;
+    
+    public RequestValidator() { }
+
+    private void clear()
+    {
+        this.id = null;
+        this.type = null;
+        this.role = null;
+        this.guri = null;
+    }
+    
+    public void validate(List<Parameter> paramList)
+    {
+        clear();
+        if (paramList == null || paramList.isEmpty())
+        {
+            throw new IllegalArgumentException(
+                    "Missing required parameters: ID and TYPE");
+        }
+
+        //  ID
+        String param = ParameterUtil.findParameterValue("ID", paramList);
+        if (param == null || param.trim().isEmpty())
+        {
+            throw new IllegalArgumentException(
+                    "ID parameter required but not found");
+        }
+        this.id = param.trim();
+        log.debug("ID: " + id);
+
+        //  TYPE
+        param = ParameterUtil.findParameterValue("TYPE", paramList);
+        if (param == null || param.trim().isEmpty())
+        {
+            throw new IllegalArgumentException(
+                    "TYPE parameter required but not found");
+        }
+        this.type = IdentityType.toValue(param);
+        log.debug("TYPE: " + type);
+        
+        //  ROLE
+        param = ParameterUtil.findParameterValue("ROLE", paramList);
+        if (param == null || param.trim().isEmpty())
+        {
+            throw new IllegalArgumentException(
+                    "ROLE parameter required but not found");
+        }
+        this.role = Role.toValue(param);
+        log.debug("ROLE: " + role);
+        
+        //  GURI
+        param = ParameterUtil.findParameterValue("GURI", paramList);
+        if (param != null)
+        {
+            if (param.isEmpty())
+                throw new IllegalArgumentException(
+                        "GURI parameter specified without a value");
+            this.guri = param.trim();
+        }
+        log.debug("GURI: " + guri);
+        
+        if (role != null && guri != null)
+        {
+            throw new IllegalArgumentException(
+                    "ROLE and GURI cannot be used in the same search");
+        }
+    }
+
+    public String getId()
+    {
+        return id;
+    }
+    
+    public IdentityType getType()
+    {
+        return type;
+    }
+
+    public Role getRole()
+    {
+        return role;
+    }
+    
+    public String getGUri()
+    {
+        return guri;
+    }
+
+}
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/UserPersistence.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/UserPersistence.java
index 9409fc26591fe96fc054ecee6d753e27c6fc8e43..8db06efab2dc59e6f990b921b89c5fdd34157368 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/UserPersistence.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/UserPersistence.java
@@ -68,11 +68,13 @@
  */
 package ca.nrc.cadc.ac.server;
 
+import ca.nrc.cadc.ac.Group;
 import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.UserNotFoundException;
 import ca.nrc.cadc.net.TransientException;
 import java.security.AccessControlException;
 import java.security.Principal;
+import java.util.Collection;
 
 public abstract interface UserPersistence<T extends Principal>
 {
@@ -82,11 +84,41 @@ public abstract interface UserPersistence<T extends Principal>
      * @param userID The userID.
      *
      * @return User instance.
-     * @throws UserNotFoundException when the member is not found.
+     * 
+     * @throws UserNotFoundException when the user is not found.
      * @throws TransientException If an temporary, unexpected problem occurred.
      * @throws AccessControlException If the operation is not permitted.
      */
     public abstract User<T> getUser(T userID)
         throws UserNotFoundException, TransientException, AccessControlException;
 
+    /**
+     * Get all groups the user specified by userID belongs to.
+     * 
+     * @param userID The userID.
+     * 
+     * @return Collection of Group instances.
+     * 
+     * @throws UserNotFoundException  when the user is not found.
+     * @throws TransientException If an temporary, unexpected problem occurred.
+     * @throws AccessControlException If the operation is not permitted.
+     */
+    public abstract Collection<Group> getUserGroups(T userID)
+        throws UserNotFoundException, TransientException, AccessControlException;
+    
+    /**
+     * Check whether the user is a member of the group.
+     *
+     * @param userID The userID.
+     * @param groupID The groupID.
+     *
+     * @return true or false
+     *
+     * @throws UserNotFoundException If the user is not found.
+     * @throws TransientException If an temporary, unexpected problem occurred.
+     * @throws AccessControlException If the operation is not permitted.
+     */
+    public abstract boolean isMember(T userID, String groupID)
+        throws UserNotFoundException, TransientException,
+               AccessControlException;
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAO.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAO.java
index e6eb7d66f9b5869c5c4eb532e8ce4f15b4b2019e..78382666d69097bc1fa587bdcb716fd44b5b76ed 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAO.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAO.java
@@ -68,6 +68,7 @@
  */
 package ca.nrc.cadc.ac.server.ldap;
 
+import ca.nrc.cadc.ac.ActivatedGroup;
 import ca.nrc.cadc.ac.Group;
 import ca.nrc.cadc.ac.GroupAlreadyExistsException;
 import ca.nrc.cadc.ac.GroupNotFoundException;
@@ -83,7 +84,6 @@ import com.unboundid.ldap.sdk.LDAPException;
 import com.unboundid.ldap.sdk.LDAPResult;
 import com.unboundid.ldap.sdk.Modification;
 import com.unboundid.ldap.sdk.ModificationType;
-import com.unboundid.ldap.sdk.ModifyDNRequest;
 import com.unboundid.ldap.sdk.ModifyRequest;
 import com.unboundid.ldap.sdk.SearchRequest;
 import com.unboundid.ldap.sdk.SearchResult;
@@ -169,7 +169,33 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
         catch (GroupNotFoundException ex)
         {
             try
-            {
+            {             
+                try
+                {
+                    Group inactiveGroup = getInactiveGroup(group.getID());
+
+                    // Check requestor owns the group.
+                    DN ownerDN = userPersist.getUserDN(group.getOwner());
+                    if (!ownerDN.equals(getSubjectDN()))
+                    {
+                       throw new AccessControlException(
+                           "Unable to activate group " + group.getID() + 
+                           " because " + group.getOwner().getUserID().getName() 
+                           + " is not the owner"); 
+                    }
+                    
+                    List<Modification> mods = new ArrayList<Modification>();
+                    Modification mod = 
+                        new Modification(ModificationType.DELETE, 
+                                         "nsaccountlock");
+                    mods.add(mod);
+                    Group activatedGroup = 
+                        modifyGroup(group, inactiveGroup, mods);
+                    return new ActivatedGroup(activatedGroup.getID(),
+                                              activatedGroup.getOwner());
+                }
+                catch (GroupNotFoundException ignore) {}
+                
                 if (!group.getProperties().isEmpty())
                 {
                     throw new UnsupportedOperationException(
@@ -285,19 +311,36 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
         throws GroupNotFoundException, TransientException,
                AccessControlException
     {
-        Group group = getGroup(groupID, false);
-        DN groupDN = getGroupDN(group.getID());
-        try
+        Group group = getGroup(groupID);
+        List<Modification> modifs = new ArrayList<Modification>();
+        modifs.add(new Modification(ModificationType.ADD, "nsaccountlock", "true"));
+        
+        if (group.description != null)
         {
-            ModifyDNRequest modifyDNRequest = new ModifyDNRequest(
-                    group.getID(), group.getID(), 
-                    true, config.getDeletedGroupsDN());
-
-            modifyDNRequest.addControl(
-                    new ProxiedAuthorizationV2RequestControl("dn:" + 
-                            getSubjectDN().toNormalizedString()));
+            modifs.add(new Modification(ModificationType.DELETE, "description"));
+        }
+        
+        if (group.groupRead != null || 
+            group.groupWrite != null || 
+            group.publicRead)
+        {
+            modifs.add(new Modification(ModificationType.DELETE, "aci"));
+        }
+        
+        if (!group.getGroupMembers().isEmpty() || 
+            !group.getUserMembers().isEmpty())
+        {
+            modifs.add(new Modification(ModificationType.DELETE, "uniquemember"));
+        }
 
-            LDAPResult result = getConnection().modifyDN(modifyDNRequest);
+        ModifyRequest modifyRequest = 
+                new ModifyRequest(getGroupDN(group.getID()), modifs);
+        try
+        {
+            modifyRequest.addControl(
+                    new ProxiedAuthorizationV2RequestControl(
+                            "dn:" + getSubjectDN().toNormalizedString()));
+            LDAPResult result = getConnection().modify(modifyRequest);
         }
         catch (LDAPException e1)
         {
@@ -312,12 +355,13 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
         }
         catch (GroupNotFoundException ignore) {}
     }
-
+    
     /**
      * Obtain a Collection of Groups that fit the given query.
      * 
-     * @param user<T> ID of user
+     * @param userID The userID.
      * @param role Role of the user, either owner, member, or read/write.
+     * @param groupID The Group ID.
      * 
      * @return Collection of Groups
      *         matching GROUP_READ_ACI.replace(ACTUAL_GROUP_TOKEN,
@@ -325,88 +369,39 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
      *         Collection. Never null.
      * @throws TransientException  If an temporary, unexpected problem occurred.
      * @throws UserNotFoundException
+     * @throws GroupNotFoundException
      */
-    public Collection<Group> getGroups(User<T> user, Role role)
+    public Collection<Group> searchGroups(T userID, Role role, String groupID)
         throws TransientException, AccessControlException,
-               UserNotFoundException
+               GroupNotFoundException, UserNotFoundException
     {
+        User<T> user = new User<T>(userID);
+        DN userDN;
         try
         {   
-            DN userDN = userPersist.getUserDN(user);
-            Filter filter = null;
-            if (role == Role.OWNER)
-            {
-                filter = Filter.createEqualityFilter("owner", userDN.toString());
-            }
-            else if (role == Role.MEMBER)
-            {
-                throw new IllegalArgumentException("Member role not implemented");
-            }
-            else if (role == Role.RW)
-            {
-                throw new IllegalArgumentException("RW role not implemented");
-            }
-            
-            SearchRequest searchRequest =  new SearchRequest(
-                    config.getGroupsDN(), SearchScope.SUB, filter, 
-                    new String[] {"cn", "description",
-                                  "owner", "modifytimestamp"});
-            
-            searchRequest.addControl(
-                    new ProxiedAuthorizationV2RequestControl("dn:" + 
-                            getSubjectDN().toNormalizedString()));
-            
-            Collection<Group> groups = new ArrayList<Group>();
-            SearchResult results = getConnection().search(searchRequest);
-            for (SearchResultEntry result : results.getSearchEntries())
-            {
-                String groupName = result.getAttributeValue("cn");
-                DN groupOwner = result.getAttributeValueAsDN("owner");
-                
-                User<X500Principal> owner;
-                try
-                {
-                    owner = userPersist.getMember(groupOwner);
-                }
-                catch (UserNotFoundException e)
-                {
-                    throw new RuntimeException("BUG: group owner not found");
-                }
-                
-                Group group = new Group(groupName, owner);
-                group.description = result.getAttributeValue("description");
-                group.lastModified = result.getAttributeValueAsDate("modifytimestamp");
-                groups.add(group);
-            }
-            
-            return groups; 
+            userDN = userPersist.getUserDN(user);
         }
-        catch (LDAPException e1)
+        catch (LDAPException e)
         {
             // TODO check which LDAP exceptions are transient and which
             // ones are
             // access control
-            throw new TransientException("Error getting groups", e1);
+            throw new TransientException("Error getting user", e);
         }
-    }
-
-    public boolean isMember(User<T> user, String groupID)
-        throws UserNotFoundException, TransientException,
-               AccessControlException
-    {
-        return false;
-//        try
-//        {
-//            
-//           
-//        }
-//        catch (LDAPException e1)
-//        {
-//            // TODO check which LDAP exceptions are transient and which
-//            // ones are
-//            // access control
-//            throw new TransientException("Error getting the group", e1);
-//        }
+        
+        if (role == Role.OWNER)
+        {
+            return getOwnerGroups(user, userDN, groupID);
+        }
+        else if (role == Role.MEMBER)
+        {
+            return getMemberGroups(user, userDN, groupID);
+        }
+        else if (role == Role.RW)
+        {
+            return getRWGroups(user, userDN, groupID);
+        }
+        throw new IllegalArgumentException("Unknown role " + role);
     }
     
     private Group getGroup(String groupID, boolean withMembers)
@@ -415,11 +410,16 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
     {
         try
         {
+            Filter filter = Filter.createANDFilter(
+                    Filter.createEqualityFilter("cn", groupID),
+                    Filter.createNOTFilter(
+                        Filter.createEqualityFilter("nsaccountlock", "true")));
+            
             SearchRequest searchRequest =  new SearchRequest(
                     config.getGroupsDN(), SearchScope.SUB, 
-                    "(cn=" + groupID + ")", new String[] {"entrydn", "entryid", 
-                    "cn", "description", "owner", "uniquemember", "aci", 
-                    "modifytimestamp"});
+                    filter, new String[] {"entrydn", "cn", "description", 
+                                          "owner", "uniquemember", "aci", 
+                                          "modifytimestamp"});
 
             searchRequest.addControl(
                     new ProxiedAuthorizationV2RequestControl("dn:" + 
@@ -435,7 +435,6 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
             }
             String groupCN = group.getAttributeValue("cn");
             DN groupOwner = group.getAttributeValueAsDN("owner");
-            Long grID = group.getAttributeValueAsLong("entryid");
             Date lastModified = 
                 group.getAttributeValueAsDate("modifytimestamp");
             
@@ -523,7 +522,7 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
                     }
                 }
             }
-
+            
             return ldapGroup;
         }
         catch (LDAPException e1)
@@ -539,58 +538,84 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
         }
     }
 
+    /**
+     * Modify the given group.
+     *
+     * @param group The group to update.
+     * 
+     * @return The newly updated group.
+     * 
+     * @throws GroupNotFoundException If the group was not found.
+     * @throws TransientException If an temporary, unexpected problem occurred.
+     * @throws AccessControlException If the operation is not permitted.
+     * @throws UserNotFoundException If owner or group members not valid users.
+     */
     public Group modifyGroup(Group group)
         throws GroupNotFoundException, TransientException,
                AccessControlException, UserNotFoundException
     {
         // check if group exists
         Group oldGroup = getGroup(group.getID());
-        if (!group.getProperties().isEmpty())
+        
+        return modifyGroup(group, oldGroup, null);
+    }
+    
+    private Group modifyGroup(Group newGroup, Group oldGroup,
+                             List<Modification> modifications)
+        throws UserNotFoundException, TransientException,
+               AccessControlException
+    {
+        if (!newGroup.getProperties().isEmpty())
         {
             throw new UnsupportedOperationException(
                     "Support for groups properties not available");
         }
 
         List<Modification> modifs = new ArrayList<Modification>();
-        if (group.description == null)
+        if (modifications != null)
+        {
+            modifs.addAll(modifications);
+        }
+
+        if (newGroup.description == null && oldGroup.description != null)
         {
             modifs.add(new Modification(ModificationType.DELETE, 
                                         "description"));
         }
-        else
+        else if (newGroup.description != null)
         {
             modifs.add(new Modification(ModificationType.REPLACE, "description", 
-                                        group.description));
+                                        newGroup.description));
         }
 
         List<String> acis = new ArrayList<String>();
-        if (group.groupRead != null)
+        if (newGroup.groupRead != null)
         {
-            if (group.groupRead.equals(group))
+            if (newGroup.groupRead.equals(newGroup))
             {
                 throw new IllegalArgumentException(
                         "cyclical reference from groupRead to group");
             }
 
-            DN readGrDN = getGroupDN(group.groupRead.getID());
+            DN readGrDN = getGroupDN(newGroup.groupRead.getID());
             acis.add(GROUP_READ_ACI.replace(ACTUAL_GROUP_TOKEN, 
                                             readGrDN.toNormalizedString()));
         }
 
-        if (group.groupWrite != null)
+        if (newGroup.groupWrite != null)
         {
-            if (group.groupWrite.equals(group))
+            if (newGroup.groupWrite.equals(newGroup))
             {
                 throw new IllegalArgumentException(
                         "cyclical reference from groupWrite to group");
             }
 
-            DN writeGrDN = getGroupDN(group.groupWrite.getID());
+            DN writeGrDN = getGroupDN(newGroup.groupWrite.getID());
             acis.add(GROUP_WRITE_ACI.replace(ACTUAL_GROUP_TOKEN, 
                                              writeGrDN.toNormalizedString()));
         }
 
-        if (group.publicRead)
+        if (newGroup.publicRead)
         {
             acis.add(PUB_GROUP_ACI);
         }
@@ -598,7 +623,7 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
                                     acis.toArray(new String[acis.size()])));
 
         List<String> newMembers = new ArrayList<String>();
-        for (User<?> member : group.getUserMembers())
+        for (User<?> member : newGroup.getUserMembers())
         {
             if (!oldGroup.getUserMembers().remove(member))
             {
@@ -615,9 +640,9 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
                 newMembers.add(memberDN.toNormalizedString());
             }
         }
-        for (Group gr : group.getGroupMembers())
+        for (Group gr : newGroup.getGroupMembers())
         {
-            if (gr.equals(group))
+            if (gr.equals(newGroup))
             {
                 throw new IllegalArgumentException(
                         "cyclical reference from group member to group");
@@ -662,7 +687,7 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
         }
 
         ModifyRequest modifyRequest = 
-                new ModifyRequest(getGroupDN(group.getID()), modifs);
+                new ModifyRequest(getGroupDN(newGroup.getID()), modifs);
         try
         {
             modifyRequest.addControl(
@@ -676,7 +701,7 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
         }
         try
         {
-            return getGroup(group.getID());
+            return getGroup(newGroup.getID());
         }
         catch (GroupNotFoundException e)
         {
@@ -697,11 +722,10 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
     protected Group getGroup(DN groupDN)
         throws LDAPException, GroupNotFoundException, UserNotFoundException
     {
-        SearchResultEntry searchResult = null;
-
-        searchResult = 
+        SearchResultEntry searchResult = 
                 getConnection().getEntry(groupDN.toNormalizedString(),
-                                new String[] {"cn", "description", "owner"});
+                                new String[] {"cn", "description", "owner", 
+                                              "modifytimestamp"});
 
         if (searchResult == null)
         {
@@ -713,7 +737,9 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
         DN ownerDN = searchResult.getAttributeValueAsDN("owner");
         User<X500Principal> owner = userPersist.getMember(ownerDN);
         Group group = new Group(searchResult.getAttributeValue("cn"), owner);
-
+        group.description = searchResult.getAttributeValue("description");
+        group.lastModified = 
+                searchResult.getAttributeValueAsDate("modifytimestamp");
         return group;
     }
 
@@ -728,5 +754,283 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
         }
         throw new IllegalArgumentException(groupID + " not a valid group ID");
     }
+    
+    protected Collection<Group> getOwnerGroups(User<T> user, DN userDN,
+                                               String groupID)
+        throws TransientException, AccessControlException,
+               GroupNotFoundException, UserNotFoundException
+    {
+        try
+        {                           
+            Filter filter = Filter.createEqualityFilter("owner", 
+                                                        userDN.toString());
+            if (groupID != null)
+            {
+                getGroup(groupID);
+                filter = Filter.createANDFilter(filter, 
+                                Filter.createEqualityFilter("cn", groupID));
+            }
+            
+            SearchRequest searchRequest =  new SearchRequest(
+                    config.getGroupsDN(), SearchScope.SUB, filter, 
+                    new String[] {"cn", "description", "modifytimestamp"});
+            
+            searchRequest.addControl(
+                    new ProxiedAuthorizationV2RequestControl("dn:" + 
+                            getSubjectDN().toNormalizedString()));
+            
+            Collection<Group> groups = new ArrayList<Group>();
+            SearchResult results = getConnection().search(searchRequest);
+            for (SearchResultEntry result : results.getSearchEntries())
+            {
+                String groupName = result.getAttributeValue("cn");
+                // Ignore existing illegal group names.
+                try
+                {
+                    Group group = new Group(groupName, user);
+                    group.description = result.getAttributeValue("description");
+                    group.lastModified = 
+                        result.getAttributeValueAsDate("modifytimestamp");
+                    groups.add(group);
+                }
+                catch (IllegalArgumentException ignore) { }   
+            }
+            
+            return groups; 
+        }
+        catch (LDAPException e1)
+        {
+            // TODO check which LDAP exceptions are transient and which
+            // ones are
+            // access control
+            throw new TransientException("Error getting groups", e1);
+        }
+    }
+    
+    protected Collection<Group> getMemberGroups(User<T> user, DN userDN, 
+                                                String groupID)
+        throws TransientException, AccessControlException,
+               GroupNotFoundException, UserNotFoundException
+    {
+        if (groupID != null)
+        {
+            Collection<Group> groups = new ArrayList<Group>();
+            if (userPersist.isMember(user.getUserID(), groupID))
+            {
+                groups.add(getGroup(groupID));
+            }
+            return groups;
+        }
+        else
+        {
+            return userPersist.getUserGroups(user.getUserID());
+        }
+    }
+    
+    protected Collection<Group> getRWGroups(User<T> user, DN userDN,
+                                            String groupID)
+        throws TransientException, AccessControlException,
+               GroupNotFoundException, UserNotFoundException
+    {
+        try
+        {
+            Collection<Group> queryGroups =  new ArrayList<Group>();
+            if (groupID != null)
+            {
+                queryGroups.add(new Group(groupID, user));
+            }
+            else
+            {
+                // List of Groups the user belongs to.
+                queryGroups.addAll(getMemberGroups(user, userDN, groupID));
+            
+                // List of Groups the user owns;
+                queryGroups.addAll(getOwnerGroups(user, userDN, groupID));
+            }
+            
+            System.out.println("# groups: " + queryGroups.size());
+                    
+            List<Filter> filters = new ArrayList<Filter>();
+            for (Group member : queryGroups)
+            {
+                // Require both groupRead and groupWrite
+                if (member.groupRead != null && member.groupWrite != null)
+                {
+                    DN groupRead = getGroupDN(member.groupRead.getID());
+                    String groupReadAci = 
+                        GROUP_READ_ACI.replace(ACTUAL_GROUP_TOKEN, 
+                                           groupRead.toNormalizedString());
+                    DN groupWrite = getGroupDN(member.groupRead.getID());
+                    String groupWriteAci = 
+                        GROUP_WRITE_ACI.replace(ACTUAL_GROUP_TOKEN, 
+                                            groupWrite.toNormalizedString());
+                    System.out.println(groupReadAci);
+                    System.out.println(groupWriteAci);
+
+                    Filter filter = Filter.createANDFilter(
+                            Filter.createEqualityFilter("aci", groupReadAci),
+                            Filter.createEqualityFilter("aci", groupWriteAci));
+                    filters.add(filter);
+                }
+            }
+
+            Collection<Group> groups = new ArrayList<Group>();
+            if (filters.isEmpty())
+            {
+                return groups;
+            }
+            
+            Filter filter = Filter.createORFilter(filters);
+            SearchRequest searchRequest =  new SearchRequest(
+                        config.getGroupsDN(), SearchScope.SUB, filter, 
+                        new String[] {"cn", "owner", "description", 
+                                      "modifytimestamp"});
+
+            searchRequest.addControl(
+                    new ProxiedAuthorizationV2RequestControl("dn:" + 
+                            getSubjectDN().toNormalizedString()));
+            
+            SearchResult results = getConnection().search(searchRequest);
+            for (SearchResultEntry result : results.getSearchEntries())
+            {
+                String groupName = result.getAttributeValue("cn");
+                DN ownerDN = result.getAttributeValueAsDN("owner");
+                User<X500Principal> owner = userPersist.getMember(ownerDN);
+                
+                // Ignore existing illegal group names.
+                try
+                {
+                    Group group = new Group(groupName, owner);
+                    group.description = result.getAttributeValue("description");
+                    group.lastModified = 
+                            result.getAttributeValueAsDate("modifytimestamp");
+                    groups.add(group);
+                }
+                catch (IllegalArgumentException ignore) { }   
+            }
+            return groups;
+        }
+        catch (LDAPException e)
+        {
+            // TODO check which LDAP exceptions are transient and which
+            // ones are
+            // access control
+            throw new TransientException("Error getting groups", e);
+        }
+    }
+    
+    protected Collection<Group> getRWGroups2(User<T> user, DN userDN, 
+                                             String groupID)
+        throws TransientException, AccessControlException,
+               GroupNotFoundException, UserNotFoundException
+    {
+        try
+        {
+            Collection<Group> groups = new ArrayList<Group>();
+            
+            Collection<Group> queryGroups =  new ArrayList<Group>();
+            if (groupID != null)
+            {
+                queryGroups.add(new Group(groupID, user));
+            }
+            else
+            {
+                // List of Groups the user belongs to.
+                queryGroups.addAll(getMemberGroups(user, userDN, groupID));
+            
+                // List of Groups the user owns;
+                queryGroups.addAll(getOwnerGroups(user, userDN, groupID));
+            }
+            
+            for (Group member : queryGroups)
+            {
+                // Require both groupRead and groupWrite
+                if (member.groupRead != null && member.groupWrite != null)
+                {
+                    DN groupRead = getGroupDN(member.groupRead.getID());
+                    String groupReadAci = 
+                            GROUP_READ_ACI.replace(ACTUAL_GROUP_TOKEN, 
+                                            groupRead.toNormalizedString());
+                    DN groupWrite = getGroupDN(member.groupWrite.getID());
+                    String groupWriteAci = 
+                            GROUP_WRITE_ACI.replace(ACTUAL_GROUP_TOKEN, 
+                                            groupWrite.toNormalizedString());
+
+                    Filter filter = Filter.createANDFilter(
+                            Filter.createEqualityFilter("aci", groupReadAci),
+                            Filter.createEqualityFilter("aci", groupWriteAci));
+
+                    SearchRequest searchRequest = new SearchRequest(
+                            config.getGroupsDN(), SearchScope.SUB, filter, 
+                            new String[] {"cn", "owner", "description", 
+                                          "modifytimestamp"});
+
+                    searchRequest.addControl(
+                            new ProxiedAuthorizationV2RequestControl("dn:" + 
+                                    getSubjectDN().toNormalizedString()));
+
+                    SearchResult results = getConnection().search(searchRequest);
+                    for (SearchResultEntry result : results.getSearchEntries())
+                    {
+                        String groupName = result.getAttributeValue("cn");
+                        DN ownerDN = result.getAttributeValueAsDN("owner");
+                        User<X500Principal> owner = userPersist.getMember(ownerDN);
+
+                        // Ignore existing illegal group names.
+                        try
+                        {
+                            Group group = new Group(groupName, owner);
+                            group.description = result.getAttributeValue("description");
+                            group.lastModified = 
+                                    result.getAttributeValueAsDate("modifytimestamp");
+                            groups.add(group);
+                        }
+                        catch (IllegalArgumentException ignore) { } 
+                    }
+                }
+            }
+            return groups;
+        }
+        catch (LDAPException e1)
+        {
+            // TODO check which LDAP exceptions are transient and which
+            // ones are
+            // access control
+            throw new TransientException("Error getting groups", e1);
+        }
+    }
+    
+    private Group getInactiveGroup(String groupID)
+        throws UserNotFoundException, GroupNotFoundException, LDAPException
+    {
+        Filter filter = Filter.createANDFilter(
+                Filter.createEqualityFilter("cn", groupID),
+                Filter.createEqualityFilter("nsaccountlock", "true"));
+
+        SearchRequest searchRequest =  new SearchRequest(
+                config.getGroupsDN(), SearchScope.SUB, 
+                filter, new String[] {"cn", "owner"});
+
+        searchRequest.addControl(
+                new ProxiedAuthorizationV2RequestControl("dn:" + 
+                        getSubjectDN().toNormalizedString()));
+
+        SearchResultEntry searchResult = 
+                getConnection().searchForEntry(searchRequest);
+        
+        if (searchResult == null)
+        {
+            String msg = "Group not found " + groupID;
+            logger.debug(msg);
+            throw new GroupNotFoundException(msg);
+        }
+
+        String groupCN = searchResult.getAttributeValue("cn");
+        DN groupOwner = searchResult.getAttributeValueAsDN("owner");
+
+        User<X500Principal> owner = userPersist.getMember(groupOwner);
+
+        return new Group(groupCN, owner);
+    }
 
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupPersistence.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupPersistence.java
index 65386490e3e4148146469e878db5fcd783181864..b45318643f6078e4cfb5d93c2074ae255686752e 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupPersistence.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupPersistence.java
@@ -71,8 +71,8 @@ package ca.nrc.cadc.ac.server.ldap;
 import ca.nrc.cadc.ac.Group;
 import ca.nrc.cadc.ac.GroupAlreadyExistsException;
 import ca.nrc.cadc.ac.GroupNotFoundException;
+import ca.nrc.cadc.ac.IdentityType;
 import ca.nrc.cadc.ac.Role;
-import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.UserNotFoundException;
 import ca.nrc.cadc.ac.server.GroupPersistence;
 import ca.nrc.cadc.net.TransientException;
@@ -172,34 +172,15 @@ public class LdapGroupPersistence<T extends Principal>
         }
     }
 
-    public Collection<Group> getGroups(User<T> user, Role role)
-        throws UserNotFoundException, TransientException, AccessControlException
+    public Collection<Group> searchGroups(T userID, Role role, String groupID)
+        throws UserNotFoundException, GroupNotFoundException,
+               TransientException, AccessControlException
     {
         LdapGroupDAO<T> groupDAO = null;
         try
         {
             groupDAO = new LdapGroupDAO<T>(config, new LdapUserDAO<T>(config));
-            Collection<Group> ret = groupDAO.getGroups(user, role);
-            return ret;
-        }
-        finally
-        {
-            if (groupDAO != null)
-            {
-                groupDAO.close();
-            }
-        }
-    }
-
-    public boolean isMember(User<T> user, String groupID)
-        throws GroupNotFoundException, TransientException,
-               AccessControlException, UserNotFoundException
-    {
-        LdapGroupDAO<T> groupDAO = null;
-        try
-        {
-            groupDAO = new LdapGroupDAO<T>(config, new LdapUserDAO<T>(config));
-            boolean ret = groupDAO.isMember(user, groupID);
+            Collection<Group> ret = groupDAO.searchGroups(userID, role, groupID);
             return ret;
         }
         finally
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 645b5fda9829c9bab423a0c2f2b3b64e9437cc69..92849897aaf4556243beab8491ec3c5dabeda8f2 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
@@ -68,13 +68,16 @@
  */
 package ca.nrc.cadc.ac.server.ldap;
 
+import ca.nrc.cadc.ac.Group;
 import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.UserNotFoundException;
 import ca.nrc.cadc.auth.HttpPrincipal;
 import ca.nrc.cadc.auth.NumericPrincipal;
 import ca.nrc.cadc.net.TransientException;
+import com.unboundid.ldap.sdk.CompareRequest;
+import com.unboundid.ldap.sdk.CompareResult;
 import com.unboundid.ldap.sdk.DN;
-import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.Filter;
 import com.unboundid.ldap.sdk.LDAPException;
 import com.unboundid.ldap.sdk.SearchRequest;
 import com.unboundid.ldap.sdk.SearchResultEntry;
@@ -84,8 +87,8 @@ import java.security.AccessControlException;
 import java.security.Principal;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
 import javax.security.auth.x500.X500Principal;
 import org.apache.log4j.Logger;
 
@@ -106,11 +109,14 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
 
     /**
      * Get the user specified by userID.
-     * 
-     * @param userID The unique userID.
+     *
+     * @param userID The userID.
+     *
      * @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.
      */
     public User<T> getUser(T userID)
         throws UserNotFoundException, TransientException, AccessControlException
@@ -158,6 +164,169 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
         return user;
     }
 
+    /**
+     * Get all groups the user specified by userID belongs to.
+     * 
+     * @param userID The userID.
+     * 
+     * @return Collection of Group instances.
+     * 
+     * @throws UserNotFoundException  when the user is not found.
+     * @throws TransientException If an temporary, unexpected problem occurred.
+     * @throws AccessControlException If the operation is not permitted.
+     */
+    public Collection<Group> getUserGroups(T userID)
+        throws UserNotFoundException, TransientException, AccessControlException
+    {
+        try
+        {
+            String searchField = (String) attribType.get(userID.getClass());
+            if (searchField == null)
+            {
+                throw new IllegalArgumentException(
+                        "Unsupported principal type " + userID.getClass());
+            }
+
+            User user = getUser(userID);
+            Filter filter = Filter.createANDFilter(
+                        Filter.createEqualityFilter(searchField, 
+                                                    user.getUserID().getName()),
+                        Filter.createPresenceFilter("memberOf"));
+
+            SearchRequest searchRequest = 
+                    new SearchRequest(config.getUsersDN(), SearchScope.SUB, 
+                                      filter, new String[] {"memberOf"});
+
+            searchRequest.addControl(
+                    new ProxiedAuthorizationV1RequestControl(getSubjectDN()));
+
+            SearchResultEntry searchResult = 
+                    getConnection().searchForEntry(searchRequest);
+            
+            Collection<Group> groups = new HashSet<Group>();
+            if (searchResult != null)
+            {
+                String[] members = 
+                        searchResult.getAttributeValues("memberOf");
+                if (members != null)
+                {
+                    for (String member : members)
+                    {
+                        String groupCN = DN.getRDNString(member);
+                        int index = groupCN.indexOf("=");
+                        String groupName = groupCN.substring(index + 1);
+                        // Ignore existing illegal group names.
+                        try
+                        {
+                            groups.add(new Group(groupName, user));
+                        }
+                        catch (IllegalArgumentException ignore) { }
+                    }
+                }
+            }
+            return groups;
+        }
+        catch (LDAPException e)
+        {
+            // TODO check which LDAP exceptions are transient and which
+            // ones are
+            // access control
+            throw new TransientException("Error getting user groups", e);
+        }
+    }
+    
+    /**
+     * Check whether the user is a member of the group.
+     *
+     * @param userID The userID.
+     * @param groupID The groupID.
+     *
+     * @return true or false
+     *
+     * @throws UserNotFoundException If the user is not found.
+     * @throws TransientException If an temporary, unexpected problem occurred.
+     * @throws AccessControlException If the operation is not permitted.
+     */
+    public boolean isMemberX(T userID, String groupID)
+        throws UserNotFoundException, TransientException,
+               AccessControlException
+    {
+        try
+        {
+            String searchField = (String) attribType.get(userID.getClass());
+            if (searchField == null)
+            {
+                throw new IllegalArgumentException(
+                        "Unsupported principal type " + userID.getClass());
+            }
+
+            User user = getUser(userID);
+            Filter filter = Filter.createANDFilter(
+                        Filter.createEqualityFilter(searchField, 
+                                                    user.getUserID().getName()),
+                        Filter.createEqualityFilter("memberOf", groupID));
+
+            SearchRequest searchRequest = 
+                    new SearchRequest(config.getUsersDN(), SearchScope.SUB, 
+                                      filter, new String[] {"cn"});
+
+            searchRequest.addControl(
+                    new ProxiedAuthorizationV1RequestControl(getSubjectDN()));
+            
+            SearchResultEntry searchResults = 
+                    getConnection().searchForEntry(searchRequest);
+            
+            if (searchResults == null)
+            {
+                return false;
+            }
+            return true;
+        }
+        catch (LDAPException e1)
+        {
+            // TODO check which LDAP exceptions are transient and which
+            // ones are
+            // access control
+            throw new TransientException("Error getting the user", e1);
+        }
+    }
+    
+    public boolean isMember(T userID, String groupID)
+        throws UserNotFoundException, TransientException,
+               AccessControlException
+    {
+        try
+        {
+            String searchField = (String) attribType.get(userID.getClass());
+            if (searchField == null)
+            {
+                throw new IllegalArgumentException(
+                        "Unsupported principal type " + userID.getClass());
+            }
+
+            User user = getUser(userID);
+            DN userDN = getUserDN(user);
+
+            CompareRequest compareRequest = 
+                    new CompareRequest(userDN.toNormalizedString(), 
+                                      "memberOf", groupID);
+
+            compareRequest.addControl(
+                    new ProxiedAuthorizationV1RequestControl(getSubjectDN()));
+            
+            CompareResult compareResult = 
+                    getConnection().compare(compareRequest);
+            return compareResult.compareMatched();
+        }
+        catch (LDAPException e)
+        {
+            // TODO check which LDAP exceptions are transient and which
+            // ones are
+            // access control
+            throw new TransientException("Error getting the user", e);
+        }
+    }
+    
     /**
      * Returns a member user identified by the X500Principal only.
      * @param userDN
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserPersistence.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserPersistence.java
index 4305fbe95a7613d8ba7bba93c93d027813829f29..ae28f5c12315d393aa0b3a87cc329be79344f598 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserPersistence.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserPersistence.java
@@ -68,12 +68,14 @@
  */
 package ca.nrc.cadc.ac.server.ldap;
 
+import ca.nrc.cadc.ac.Group;
 import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.UserNotFoundException;
 import ca.nrc.cadc.ac.server.UserPersistence;
 import ca.nrc.cadc.net.TransientException;
 import java.security.AccessControlException;
 import java.security.Principal;
+import java.util.Collection;
 import org.apache.log4j.Logger;
 
 public class LdapUserPersistence<T extends Principal>
@@ -94,6 +96,17 @@ public class LdapUserPersistence<T extends Principal>
         }
     }
 
+    /**
+     * Get the user specified by userID.
+     *
+     * @param userID The userID.
+     *
+     * @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.
+     */
     public User<T> getUser(T userID)
         throws UserNotFoundException, TransientException, AccessControlException
     {
@@ -112,5 +125,67 @@ public class LdapUserPersistence<T extends Principal>
             }
         }
     }
+    
+    /**
+     * Get all groups the user specified by userID belongs to.
+     * 
+     * @param userID The userID.
+     * 
+     * @return Collection of Group instances.
+     * 
+     * @throws UserNotFoundException  when the user is not found.
+     * @throws TransientException If an temporary, unexpected problem occurred.
+     * @throws AccessControlException If the operation is not permitted.
+     */
+    public Collection<Group> getUserGroups(T userID)
+        throws UserNotFoundException, TransientException, AccessControlException
+    {
+        LdapUserDAO<T> userDAO = null;
+        try
+        {
+            userDAO = new LdapUserDAO<T>(this.config);
+            Collection<Group> ret = userDAO.getUserGroups(userID);
+            return ret;
+        }
+        finally
+        {
+            if (userDAO != null)
+            {
+                userDAO.close();
+            }
+        }
+    }
+    
+    /**
+     * Check whether the user is a member of the group.
+     *
+     * @param userID The userID.
+     * @param groupID The groupID.
+     *
+     * @return true or false
+     *
+     * @throws UserNotFoundException If the user is not found.
+     * @throws TransientException If an temporary, unexpected problem occurred.
+     * @throws AccessControlException If the operation is not permitted.
+     */
+    public boolean isMember(T userID, String groupID)
+        throws UserNotFoundException, TransientException,
+               AccessControlException
+    {
+        LdapUserDAO<T> userDAO = null;
+        try
+        {
+            userDAO = new LdapUserDAO<T>(this.config);
+            boolean ret = userDAO.isMember(userID, groupID);
+            return ret;
+        }
+        finally
+        {
+            if (userDAO != null)
+            {
+                userDAO.close();
+            }
+        }
+    }
 
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/ACSearchRunner.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/ACSearchRunner.java
index 0c85a4d545adb79824d454fe85d0c7491a238dff..9fac6d11bef93e130d427099ac8e57f4cab7dfa9 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/ACSearchRunner.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/ACSearchRunner.java
@@ -68,17 +68,47 @@
  */
 package ca.nrc.cadc.ac.server.web;
 
+import ca.nrc.cadc.ac.Group;
+import ca.nrc.cadc.ac.GroupNotFoundException;
+import ca.nrc.cadc.ac.GroupsWriter;
+import ca.nrc.cadc.ac.IdentityType;
+import ca.nrc.cadc.ac.UserNotFoundException;
+import ca.nrc.cadc.ac.server.GroupPersistence;
+import ca.nrc.cadc.ac.server.PluginFactory;
+import ca.nrc.cadc.ac.server.RequestValidator;
+import ca.nrc.cadc.ac.server.UserPersistence;
+import ca.nrc.cadc.auth.HttpPrincipal;
+import ca.nrc.cadc.auth.NumericPrincipal;
+import ca.nrc.cadc.auth.OpenIdPrincipal;
+import ca.nrc.cadc.net.TransientException;
+import ca.nrc.cadc.uws.ErrorSummary;
+import ca.nrc.cadc.uws.ErrorType;
+import ca.nrc.cadc.uws.ExecutionPhase;
 import ca.nrc.cadc.uws.Job;
+import ca.nrc.cadc.uws.server.JobNotFoundException;
+import ca.nrc.cadc.uws.server.JobPersistenceException;
 import ca.nrc.cadc.uws.server.JobRunner;
 import ca.nrc.cadc.uws.server.JobUpdater;
 import ca.nrc.cadc.uws.server.SyncOutput;
+import ca.nrc.cadc.uws.util.JobLogInfo;
+import java.io.IOException;
+import java.security.AccessControlException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Date;
+import javax.security.auth.x500.X500Principal;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.log4j.Logger;
 
 public class ACSearchRunner
     implements JobRunner
 {
+    private static Logger log = Logger.getLogger(ACSearchRunner.class);
+    
     private JobUpdater jobUpdater;
     private SyncOutput syncOut;
     private Job job;
+    private JobLogInfo logInfo;
 
     @Override
     public void setJobUpdater(JobUpdater jobUpdater)
@@ -101,7 +131,193 @@ public class ACSearchRunner
     @Override
     public void run()
     {
-        // TODO Run the search query against GroupPersistence
+        log.debug("RUN ACSearchRunner: " + job.ownerSubject);
+        
+        logInfo = new JobLogInfo(job);
+
+        String startMessage = logInfo.start();
+        log.info(startMessage);
+
+        long t1 = System.currentTimeMillis();
+        search();
+        long t2 = System.currentTimeMillis();
+
+        logInfo.setElapsedTime(t2 - t1);
+
+        String endMessage = logInfo.end();
+        log.info(endMessage);
     }
+    
+    private void search()
+    {
+        try
+        {
+            ExecutionPhase ep = 
+                jobUpdater.setPhase(job.getID(), ExecutionPhase.QUEUED, 
+                                    ExecutionPhase.EXECUTING, new Date());
+            if ( !ExecutionPhase.EXECUTING.equals(ep) )
+            {
+                String message = job.getID() + 
+                                 ": QUEUED -> EXECUTING [FAILED] -- DONE";
+                logInfo.setSuccess(false);
+                logInfo.setMessage(message);
+                return;
+            }
+            log.debug(job.getID() + ": QUEUED -> EXECUTING [OK]");
 
+            RequestValidator rv = new RequestValidator();
+            rv.validate(job.getParameterList());
+
+            Principal userID = getUserPrincipal(rv.getId(), rv.getType());
+            
+            PluginFactory factory = new PluginFactory();
+            GroupPersistence dao = factory.getGroupPersistence();
+            Collection<Group> groups = 
+                dao.searchGroups(userID, rv.getRole(), rv.getGUri());
+            syncOut.setResponseCode(HttpServletResponse.SC_OK);
+            GroupsWriter.write(groups, syncOut.getOutputStream());
+            
+            // Mark the Job as completed.
+            jobUpdater.setPhase(job.getID(), ExecutionPhase.EXECUTING, 
+                                ExecutionPhase.COMPLETED, new Date());
+        }
+        catch (TransientException t)
+        {
+            logInfo.setSuccess(false);
+            logInfo.setMessage(t.getMessage());
+            log.debug("FAIL", t);
+            
+            syncOut.setResponseCode(400);
+            
+            ErrorSummary errorSummary =
+                new ErrorSummary(t.getMessage(), ErrorType.FATAL);
+            try
+            {
+                jobUpdater.setPhase(job.getID(), ExecutionPhase.EXECUTING,
+                                    ExecutionPhase.ERROR, errorSummary, 
+                                    new Date());
+            }
+            catch(Throwable oops)
+            {
+                log.debug("failed to set final error status after " + t, oops);
+            }
+        }
+        catch (UserNotFoundException t)
+        {
+            logInfo.setSuccess(false);
+            logInfo.setMessage(t.getMessage());
+            log.debug("FAIL", t);
+            
+            syncOut.setResponseCode(404);
+            
+            ErrorSummary errorSummary =
+                new ErrorSummary(t.getMessage(), ErrorType.FATAL);
+            try
+            {
+                jobUpdater.setPhase(job.getID(), ExecutionPhase.EXECUTING,
+                                    ExecutionPhase.ERROR, errorSummary,
+                                    new Date());
+            }
+            catch(Throwable oops)
+            {
+                log.debug("failed to set final error status after " + t, oops);
+            }
+        }
+        catch (GroupNotFoundException t)
+        {
+            logInfo.setSuccess(false);
+            logInfo.setMessage(t.getMessage());
+            log.debug("FAIL", t);
+            
+            syncOut.setResponseCode(404);
+            
+            ErrorSummary errorSummary =
+                new ErrorSummary(t.getMessage(), ErrorType.FATAL);
+            try
+            {
+                jobUpdater.setPhase(job.getID(), ExecutionPhase.EXECUTING,
+                                    ExecutionPhase.ERROR, errorSummary,
+                                    new Date());
+            }
+            catch(Throwable oops)
+            {
+                log.debug("failed to set final error status after " + t, oops);
+            }
+        }
+        catch (AccessControlException t)
+        {
+            logInfo.setSuccess(false);
+            logInfo.setMessage(t.getMessage());
+            log.debug("FAIL", t);
+            
+            syncOut.setResponseCode(401);
+            
+            ErrorSummary errorSummary =
+                new ErrorSummary(t.getMessage(), ErrorType.FATAL);
+            try
+            {
+                jobUpdater.setPhase(job.getID(), ExecutionPhase.EXECUTING,
+                                    ExecutionPhase.ERROR, errorSummary,
+                                    new Date());
+            }
+            catch(Throwable oops)
+            {
+                log.debug("failed to set final error status after " + t, oops);
+            }
+        }
+        catch (Throwable t)
+        {
+            logInfo.setSuccess(false);
+            logInfo.setMessage(t.getMessage());
+            log.debug("FAIL", t);
+            
+            syncOut.setResponseCode(400);
+            
+            ErrorSummary errorSummary =
+                new ErrorSummary(t.getMessage(), ErrorType.FATAL);
+            try
+            {
+                jobUpdater.setPhase(job.getID(), ExecutionPhase.EXECUTING,
+                                    ExecutionPhase.ERROR, errorSummary,
+                                    new Date());
+            }
+            catch(Throwable oops)
+            {
+                log.debug("failed to set final error status after " + t, oops);
+            }
+        }
+    }
+    
+    private Principal getUserPrincipal(String userID, IdentityType type)
+    {
+        if (type == IdentityType.OPENID)
+        {
+            return new OpenIdPrincipal(userID);
+        }
+        if (type == IdentityType.UID)
+        {
+            try
+            {
+                Long numericId = Long.valueOf(userID);
+                return new NumericPrincipal(numericId);
+            }
+            catch (NumberFormatException e)
+            {
+                throw new IllegalArgumentException("Illegal UID userID " +
+                                                   userID + " because " +
+                                                   e.getMessage());
+            }
+        }
+        if (type == IdentityType.USERNAME)
+        {
+            return new HttpPrincipal(userID);
+        }
+        if (type == IdentityType.X500)
+        {
+            return new X500Principal(userID);
+        }
+        throw new IllegalArgumentException("Unknown user type " + 
+                                           type.getValue());
+    }
+    
 }
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAOTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAOTest.java
index 8213243891148bf8e3d3b79b8cd0ef1298cec165..47ae5cac9a91d71ca039ac345ef28905fbe98c3e 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAOTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAOTest.java
@@ -34,6 +34,7 @@
 
 package ca.nrc.cadc.ac.server.ldap;
 
+import ca.nrc.cadc.ac.ActivatedGroup;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -45,6 +46,7 @@ import javax.security.auth.x500.X500Principal;
 import org.junit.Test;
 
 import ca.nrc.cadc.ac.Group;
+import ca.nrc.cadc.ac.GroupNotFoundException;
 import ca.nrc.cadc.ac.GroupProperty;
 import ca.nrc.cadc.ac.Role;
 import ca.nrc.cadc.ac.User;
@@ -59,13 +61,11 @@ public class LdapGroupDAOTest
 {
     private static final Logger log = Logger.getLogger(LdapGroupDAOTest.class);
     
+    static User<X500Principal> cadctest;
     static User<X500Principal> authtest1;
     static User<X500Principal> authtest2;
     static User<X500Principal> regtest1;
     
-    static String groupID1;
-    static String groupID2;
-    
     static LdapConfig config;
     
     @BeforeClass
@@ -73,10 +73,9 @@ public class LdapGroupDAOTest
         throws Exception
     {
         Log4jInit.setLevel("ca.nrc.cadc.ac", Level.DEBUG);
-        
-        groupID1 = "acs-daotest-group1-" + System.currentTimeMillis();
-        groupID2 = "acs-daotest-group2-" + System.currentTimeMillis();
-        
+
+        cadctest = new User<X500Principal>(
+                new X500Principal("CN=CADCtest_636,OU=CADC,O=HIA,C=CA"));
         authtest1 = new User<X500Principal>(
                 new X500Principal("cn=cadc authtest1 10627,ou=cadc,o=hia"));
         authtest2 = new User<X500Principal>(
@@ -96,8 +95,13 @@ public class LdapGroupDAOTest
         return new LdapGroupDAO<X500Principal>(config,
                 new LdapUserDAO<X500Principal>(config));
     }
+    
+    String getGroupID()
+    {
+        return "acs-daotest-group1-" + System.currentTimeMillis();
+    }
 
-//    @Test
+    @Test
     public void testOneGroup() throws Exception
     {
         Subject subject = new Subject();
@@ -110,18 +114,24 @@ public class LdapGroupDAOTest
             {
                 try
                 {
-                    Group expectGroup = new Group(groupID1, authtest1);
+                    Group expectGroup = new Group(getGroupID(), authtest1);
                     Group actualGroup = getGroupDAO().addGroup(expectGroup);
+                    log.debug("addGroup: " + expectGroup.getID());
                     assertGroupsEqual(expectGroup, actualGroup);
                     
-                    Group otherGroup = new Group(groupID2, authtest1);
+                    Group otherGroup = new Group(getGroupID(), authtest1);
                     otherGroup = getGroupDAO().addGroup(otherGroup);
+                    log.debug("addGroup: " + otherGroup.getID());
 
                     // modify group fields
                     // description
                     expectGroup.description = "Happy testing";
                     actualGroup = getGroupDAO().modifyGroup(expectGroup);
                     assertGroupsEqual(expectGroup, actualGroup);
+                    
+                    expectGroup.description = null;
+                    actualGroup = getGroupDAO().modifyGroup(expectGroup);
+                    assertGroupsEqual(expectGroup, actualGroup);
 
                     // groupRead
                     expectGroup.groupRead = otherGroup;
@@ -137,88 +147,60 @@ public class LdapGroupDAOTest
                     expectGroup.publicRead = true;
                     actualGroup = getGroupDAO().modifyGroup(expectGroup);
                     assertGroupsEqual(expectGroup, actualGroup);
+                    
+                    expectGroup.publicRead = false;
+                    actualGroup = getGroupDAO().modifyGroup(expectGroup);
+                    assertGroupsEqual(expectGroup, actualGroup);
 
                     // userMembers
                     expectGroup.getUserMembers().add(authtest2);
                     actualGroup = getGroupDAO().modifyGroup(expectGroup);
                     assertGroupsEqual(expectGroup, actualGroup);
 
-                    // groupMembers
-                    expectGroup.getGroupMembers().add(otherGroup);
+                    expectGroup.getUserMembers().remove(authtest2);
                     actualGroup = getGroupDAO().modifyGroup(expectGroup);
                     assertGroupsEqual(expectGroup, actualGroup);
-                    return null;
-                }
-                catch (Exception e)
-                {
-                    throw new Exception("Problems", e);
-                }
-            }
-        });
-    }
-
-//    @Test
-    public void testMultipleGroups() throws Exception
-    {
-        Subject subject = new Subject();
-        subject.getPrincipals().add(authtest1.getUserID());
-
-        // do everything as owner
-        Subject.doAs(subject, new PrivilegedExceptionAction<Object>()
-        {
-            public Object run() throws Exception
-            {
-                try
-                {
-                    Group expectGroup = new Group(groupID1, authtest1);
-                    Group actualGroup = getGroupDAO().addGroup(expectGroup);
-                    assertGroupsEqual(expectGroup, actualGroup);
                     
-                    Group otherGroup = new Group(groupID2, authtest1);
-                    otherGroup = getGroupDAO().addGroup(otherGroup);
-
-                    // modify group fields
-                    // description
-                    expectGroup.description = "Happy testing";
-                    actualGroup = getGroupDAO().modifyGroup(expectGroup);
-                    assertGroupsEqual(expectGroup, actualGroup);
-
-                    // groupRead
-                    expectGroup.groupRead = otherGroup;
-                    actualGroup = getGroupDAO().modifyGroup(expectGroup);
-                    assertGroupsEqual(expectGroup, actualGroup);
-
-                    // groupWrite
-                    expectGroup.groupWrite = otherGroup;
+                    // groupMembers
+                    expectGroup.getGroupMembers().add(otherGroup);
                     actualGroup = getGroupDAO().modifyGroup(expectGroup);
                     assertGroupsEqual(expectGroup, actualGroup);
-
-                    // publicRead
-                    expectGroup.publicRead = true;
+                    
+                    expectGroup.getGroupMembers().remove(otherGroup);
                     actualGroup = getGroupDAO().modifyGroup(expectGroup);
                     assertGroupsEqual(expectGroup, actualGroup);
-
-                    // userMembers
-                    expectGroup.getUserMembers().add(authtest2);
-                    actualGroup = getGroupDAO().modifyGroup(expectGroup);
+                    
+                    // delete the group
+                    getGroupDAO().deleteGroup(expectGroup.getID());
+                    try
+                    {
+                        getGroupDAO().getGroup(expectGroup.getID());
+                        fail("get on deleted group should throw exception");
+                    }
+                    catch (GroupNotFoundException ignore) {}
+                    
+                    // reactivate the group
+                    actualGroup = getGroupDAO().addGroup(expectGroup);
+                    assertTrue(actualGroup instanceof ActivatedGroup);
                     assertGroupsEqual(expectGroup, actualGroup);
-
-                    // groupMembers
-                    expectGroup.getGroupMembers().add(otherGroup);
-                    actualGroup = getGroupDAO().modifyGroup(expectGroup);
+                    
+                    // get the activated group
+                    actualGroup = getGroupDAO().getGroup(expectGroup.getID());
                     assertGroupsEqual(expectGroup, actualGroup);
+                    
                     return null;
                 }
                 catch (Exception e)
                 {
+                    e.printStackTrace();
                     throw new Exception("Problems", e);
                 }
             }
         });
     }
     
-    @Test
-    public void testGetGroups() throws Exception
+//    @Test
+    public void testSearchOwnerGroups() throws Exception
     {
         Subject subject = new Subject();
         subject.getPrincipals().add(authtest1.getUserID());
@@ -230,12 +212,14 @@ public class LdapGroupDAOTest
             {
                 try
                 {
-                    Group expectGroup = new Group(groupID1, authtest1);
+                    Group expectGroup = new Group(getGroupID(), authtest1);
                     Group actualGroup = getGroupDAO().addGroup(expectGroup);
                     assertGroupsEqual(expectGroup, actualGroup);
-                    System.out.println("new group: " + groupID1);
+                    System.out.println("new group: " + expectGroup.getID());
                     
-                    Collection<Group> groups = getGroupDAO().getGroups(authtest1, Role.OWNER);
+                    Collection<Group> groups = 
+                        getGroupDAO().searchGroups(authtest1.getUserID(), 
+                                                   Role.OWNER, null);
                     System.out.println("# groups found: " + groups.size());
                     boolean found = false;
                     for (Group group : groups)
@@ -245,7 +229,7 @@ public class LdapGroupDAOTest
                         {
                             fail("returned group with wrong owner");
                         }
-                        if (group.getID().equals(groupID1))
+                        if (group.getID().equals(expectGroup.getID()))
                         {
                             found = true;
                         }
@@ -254,6 +238,95 @@ public class LdapGroupDAOTest
                     {
                         fail("");
                     }
+                    getGroupDAO().deleteGroup(expectGroup.getID());
+                }
+                catch (Exception e)
+                {
+                    throw new Exception("Problems", e);
+                }
+                return null;
+            }
+        });
+    }
+    
+//    @Test
+    public void testSearchMemberGroups() throws Exception
+    {
+        Subject subject = new Subject();
+        subject.getPrincipals().add(cadctest.getUserID());
+
+        // do everything as owner
+        Subject.doAs(subject, new PrivilegedExceptionAction<Object>()
+        {
+            public Object run() throws Exception
+            {
+                try
+                {                    
+                    Collection<Group> groups = 
+                        getGroupDAO().searchGroups(cadctest.getUserID(), 
+                                                   Role.MEMBER, null);
+                    System.out.println("# groups found: " + groups.size());
+//                    boolean found = false;
+//                    for (Group group : groups)
+//                    {
+//                        System.out.println("found group: " + group.getID());
+//                        if (!group.getOwner().equals(cadctest))
+//                        {
+//                            fail("returned group with wrong owner");
+//                        }
+//                        if (group.getID().equals(groupID1))
+//                        {
+//                            found = true;
+//                        }
+//                    }
+//                    if (!found)
+//                    {
+//                        fail("");
+//                    }
+                }
+                catch (Exception e)
+                {
+                    throw new Exception("Problems", e);
+                }
+                return null;
+            }
+        });
+    }
+    
+//    @Test
+    public void testSearchRWGroups() throws Exception
+    {
+        Subject subject = new Subject();
+        subject.getPrincipals().add(authtest1.getUserID());
+
+        // do everything as owner
+        Subject.doAs(subject, new PrivilegedExceptionAction<Object>()
+        {
+            public Object run() throws Exception
+            {
+                try
+                {                    
+                    Collection<Group> groups = 
+                        getGroupDAO().searchGroups(authtest1.getUserID(), 
+                                                   Role.RW, null);
+                    System.out.println("# groups found: " + groups.size());
+//                    boolean found = false;
+//                    for (Group group : groups)
+//                    {
+//                        System.out.println("found group: " + group.getID());
+//                        if (!group.getOwner().equals(authtest1))
+//                        {
+//                            fail("returned group with wrong owner");
+//                        }
+//                        if (group.getID().equals(groupID1))
+//                        {
+//                            found = true;
+//                        }
+//                    }
+//                    if (!found)
+//                    {
+//                        fail("");
+//                    }
                 }
                 catch (Exception e)
                 {
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAOTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8526a90a2ddd162e90872eb8eedd1bc0f77ec895
--- /dev/null
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAOTest.java
@@ -0,0 +1,215 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  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/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac.server.ldap;
+
+import ca.nrc.cadc.ac.Group;
+import ca.nrc.cadc.ac.User;
+import ca.nrc.cadc.util.Log4jInit;
+import java.security.PrivilegedExceptionAction;
+import java.util.Collection;
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ *
+ * @author jburke
+ */
+public class LdapUserDAOTest
+{
+    private static final Logger log = Logger.getLogger(LdapUserDAOTest.class);
+    
+    static final String cadcTestDN = "CN=CADCtest_636,OU=CADC,O=HIA,C=CA";
+    
+    static User<X500Principal> cadcTest;
+    static LdapConfig config;
+    
+    @BeforeClass
+    public static void setUpBeforeClass()
+        throws Exception
+    {
+        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.DEBUG);
+        
+        cadcTest = new User<X500Principal>(new X500Principal(cadcTestDN));
+    
+        config = new LdapConfig("mach275.cadc.dao.nrc.ca", 389,
+            "uid=webproxy,ou=administrators,ou=topologymanagement,o=netscaperoot",
+            "go4it", 
+            "ou=Users,ou=ds,dc=canfar,dc=net",
+            "ou=TestGroups,ou=ds,dc=canfar,dc=net",
+            "ou=DeletedGroups,ou=ds,dc=canfar,dc=net");
+    }
+
+    LdapUserDAO<X500Principal> getUserDAO()
+    {
+        return new LdapUserDAO<X500Principal>(config);
+    }
+    
+    /**
+     * Test of getUser method, of class LdapUserDAO.
+     */
+//    @Test
+    public void testGetUser() throws Exception
+    {
+        Subject subject = new Subject();
+        subject.getPrincipals().add(cadcTest.getUserID());
+
+        // do everything as owner
+        Subject.doAs(subject, new PrivilegedExceptionAction<Object>()
+        {
+            public Object run() throws Exception
+            {
+                try
+                {
+                    User actual = getUserDAO().getUser(cadcTest.getUserID());
+                    assertEquals(cadcTest, actual);
+                    
+                    return null;
+                }
+                catch (Exception e)
+                {
+                    throw new Exception("Problems", e);
+                }
+            }
+        });
+    }
+
+    /**
+     * Test of getUserGroups method, of class LdapUserDAO.
+     */
+//    @Test
+    public void testGetUserGroups() throws Exception
+    {
+        Subject subject = new Subject();
+        subject.getPrincipals().add(cadcTest.getUserID());
+
+        // do everything as owner
+        Subject.doAs(subject, new PrivilegedExceptionAction<Object>()
+        {
+            public Object run() throws Exception
+            {
+                try
+                {            
+                    Collection<Group> groups = getUserDAO().getUserGroups(cadcTest.getUserID());
+                    assertNotNull(groups);
+                    assertTrue(!groups.isEmpty());
+                    for (Group group : groups)
+                        log.debug(group);
+                    
+                    return null;
+                }
+                catch (Exception e)
+                {
+                    throw new Exception("Problems", e);
+                }
+            }
+        });
+    }
+    
+    /**
+     * Test of getUserGroups method, of class LdapUserDAO.
+     */
+    @Test
+    public void testIsMember() throws Exception
+    {
+        Subject subject = new Subject();
+        subject.getPrincipals().add(cadcTest.getUserID());
+
+        // do everything as owner
+        Subject.doAs(subject, new PrivilegedExceptionAction<Object>()
+        {
+            public Object run() throws Exception
+            {
+                try
+                {   
+                    boolean isMember = getUserDAO().isMember(cadcTest.getUserID(), "foo");
+                    assertFalse(isMember);
+                    
+                    String groupID = "cn=cadcsw,cn=groups,ou=ds,dc=canfar,dc=net";
+                    isMember = getUserDAO().isMember(cadcTest.getUserID(), groupID);
+                    assertTrue(isMember);
+                    
+                    return null;
+                }
+                catch (Exception e)
+                {
+                    throw new Exception("Problems", e);
+                }
+            }
+        });
+    }
+    
+}
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/AC.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/AC.java
index b38354626dd5cebcde44d86585d82ec45da2544e..44c83bb7541dd1896aa2cf05d86eb7243ee6c82c 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/AC.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/AC.java
@@ -90,8 +90,8 @@ public class AC
     // Group URI attribute once the group name is appended
     public static final String GROUP_URI = "ivo://cadc.nrc.ca/gms#";
     
-    public static final String ID_TYPE_X500 = "X500";
-    public static final String ID_TYPE_OPENID = "OpenID";
-    public static final String ID_TYPE_USERNAME = "HTTP";
-    public static final String ID_TYPE_UID = "UID";
+//    public static final String ID_TYPE_X500 = "X500";
+//    public static final String ID_TYPE_OPENID = "OpenID";
+//    public static final String ID_TYPE_USERNAME = "HTTP";
+//    public static final String ID_TYPE_UID = "UID";
 }
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/ActivatedGroup.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/ActivatedGroup.java
new file mode 100644
index 0000000000000000000000000000000000000000..6720c6434e08760b9fc862e05d68e13d25a5ec11
--- /dev/null
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/ActivatedGroup.java
@@ -0,0 +1,85 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  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/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac;
+
+import java.security.Principal;
+
+/**
+ *
+ * @author jburke
+ */
+public class ActivatedGroup extends Group
+{
+
+    public ActivatedGroup(String groupID, User<? extends Principal> owner)
+    {
+        super(groupID, owner);
+    }
+    
+}
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/GroupsWriter.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/GroupsWriter.java
index f98b38e15c5064cec6e4f226a2a01a6766f661b4..0bdcf1f09ebeb40da3610f6a5ce69be834f86540 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/GroupsWriter.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/GroupsWriter.java
@@ -7,7 +7,7 @@ import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.UnsupportedEncodingException;
 import java.io.Writer;
-import java.util.List;
+import java.util.Collection;
 import org.jdom2.Document;
 import org.jdom2.Element;
 import org.jdom2.output.Format;
@@ -22,7 +22,7 @@ public class GroupsWriter
      * @throws java.io.IOException
      * @throws ca.nrc.cadc.ac.WriterException
      */
-    public static void write(List<Group> groups, StringBuilder builder)
+    public static void write(Collection<Group> groups, StringBuilder builder)
         throws IOException, WriterException
     {
         write(groups, new StringBuilderWriter(builder));
@@ -36,7 +36,7 @@ public class GroupsWriter
      * @throws IOException if the writer fails to write.
      * @throws ca.nrc.cadc.ac.WriterException
      */
-    public static void write(List<Group> groups, OutputStream out)
+    public static void write(Collection<Group> groups, OutputStream out)
         throws IOException, WriterException
     {
         OutputStreamWriter outWriter;
@@ -59,7 +59,7 @@ public class GroupsWriter
      * @throws IOException if the writer fails to write.
      * @throws ca.nrc.cadc.ac.WriterException
      */
-    public static void write(List<Group> groups, Writer writer)
+    public static void write(Collection<Group> groups, Writer writer)
         throws IOException, WriterException
     {
         if (groups == null)
@@ -76,7 +76,7 @@ public class GroupsWriter
      * @return Element of list of Group's.
      * @throws ca.nrc.cadc.ac.WriterException 
      */
-    public static Element getGroupsElement(List<Group> groups)
+    public static Element getGroupsElement(Collection<Group> groups)
         throws WriterException
     {
         Element groupsElement = new Element("groups");