diff --git a/projects/cadcAccessControl-Server/build.xml b/projects/cadcAccessControl-Server/build.xml
index 6799d81d742f9b12258fb3fe5266fba621e2b928..f30a6185a9e20cee5cc39941fc1f563c830e3410 100644
--- a/projects/cadcAccessControl-Server/build.xml
+++ b/projects/cadcAccessControl-Server/build.xml
@@ -69,7 +69,7 @@
 
 
 <!DOCTYPE project>
-<project default="build" basedir=".">
+<project name="cadcAccessControl-Server" default="build" basedir=".">
     <property environment="env"/>
     <property file="local.build.properties" />
 
@@ -93,14 +93,16 @@
     <property name="cadcRegistry"        value="${lib}/cadcRegistryClient.jar" />
     <property name="cadcUtil"            value="${lib}/cadcUtil.jar" />
     <property name="cadcUWS"             value="${lib}/cadcUWS.jar" />
-    
+    <property name="wsUtil"              value="${lib}/wsUtil.jar" />
+
+    <property name="javacsv"             value="${ext.lib}/javacsv.jar" />
     <property name="jdom2"               value="${ext.lib}/jdom2.jar" />
     <property name="log4j"               value="${ext.lib}/log4j.jar" />
     <property name="servlet"             value="${ext.lib}/servlet-api.jar" />
     <property name="unboundid"           value="${ext.lib}/unboundid-ldapsdk-se.jar" />
     <property name="xerces"              value="${ext.lib}/xerces.jar" />
 
-    <property name="jars" value="${cadcAccessControl}:${cadcLog}:${cadcRegistry}:${cadcUtil}:${cadcUWS}:${jdom2}:${log4j}:${servlet}:${unboundid}:${xerces}" />
+    <property name="jars" value="${javacsv}:${jdom2}:${log4j}:${servlet}:${unboundid}:${xerces}:${cadcAccessControl}:${cadcLog}:${cadcRegistry}:${cadcUtil}:${cadcUWS}:${wsUtil}" />
 
     <target name="build" depends="compile">
         <jar jarfile="${build}/lib/${project}.jar"
@@ -129,18 +131,18 @@
             </fileset>
         </copy>
     </target>
-    
-<!--    <target name="test" depends="compile-test,resources">
-        <echo message="Running test suite..." />
-        <junit printsummary="yes" haltonfailure="yes" fork="yes">
-            <classpath>
-                <pathelement path="${build}/class"/>
-                <pathelement path="${build}/test/class"/>
-                <pathelement path="${testingJars}"/>
-            </classpath>
-            <test name="ca.nrc.cadc.ac.server.ldap.LdapDAOTest" />
-            <formatter type="plain" usefile="false" />
-        </junit>
-    </target>-->
+
+    <!--<target name="test" depends="compile,compile-test,resources">-->
+        <!--<echo message="Running test suite..." />-->
+        <!--<junit printsummary="yes" haltonfailure="yes" fork="yes">-->
+            <!--<classpath>-->
+                <!--<pathelement path="${build}/class"/>-->
+                <!--<pathelement path="${build}/test/class"/>-->
+                <!--<pathelement path="${testingJars}"/>-->
+            <!--</classpath>-->
+            <!--<test name="ca.nrc.cadc.ac.server.ldap.LdapGroupDAOTest" />-->
+            <!--<formatter type="plain" usefile="false" />-->
+        <!--</junit>-->
+    <!--</target>-->
 
 </project>
diff --git a/projects/cadcAccessControl-Server/config/.dbrc_example b/projects/cadcAccessControl-Server/config/.dbrc_example
new file mode 100644
index 0000000000000000000000000000000000000000..ced094848f172ce8628940a22568afa4efb3d6be
--- /dev/null
+++ b/projects/cadcAccessControl-Server/config/.dbrc_example
@@ -0,0 +1,2 @@
+#server	proxyuser proxyUserDN password driver serverURL
+<server hostname> <proxyUser in LdapConfig.properties> <proxyUserLdapDN> <password> N/A N/A
diff --git a/projects/cadcAccessControl-Server/config/LdapConfig.properties b/projects/cadcAccessControl-Server/config/LdapConfig.properties
index af73b275d60d77e64b1482aaa4a89f0f6450409d..5eb874d802b890852e308e6880946751513437dc 100644
--- a/projects/cadcAccessControl-Server/config/LdapConfig.properties
+++ b/projects/cadcAccessControl-Server/config/LdapConfig.properties
@@ -1,9 +1,7 @@
-# This are the configuration fields required by the Ldap ldap-dao unit tests
-server = mach275.cadc.dao.nrc.ca
-port = 389
-admin = uid=webproxy,ou=administrators,ou=topologymanagement,o=netscaperoot
-passwd = go4it
-usersDn = ou=Users,ou=ds,dc=canfar,dc=net
-groupsDn = ou=Groups,ou=ds,dc=canfar,dc=net
-deletedGroupsDN = ou=DeletedGroups,ou=ds,dc=canfar,dc=net
-testGroupsDN = ou=TestGroups,ou=ds,dc=canfar,dc=net
\ No newline at end of file
+# This are the configuration fields required by the Ldap
+server = <name of server> 
+port = <389 or 636>
+proxyUser = <name of proxy user>
+usersDn = <DN of users branch>
+groupsDn = <DN of groups branch>
+adminGroupsDn = <DN of admin groups>
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 caf9dc42cabef2c2ddb1cbf3af504742b05a9706..bdfa4e05c79516396085e5ffb90d031dc2d7c3e2 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
@@ -81,6 +81,16 @@ import ca.nrc.cadc.net.TransientException;
 
 public abstract interface GroupPersistence<T extends Principal>
 {
+    /**
+     * Get all group names.
+     * 
+     * @return A collection of strings.
+     * @throws TransientException If an temporary, unexpected problem occurred.
+     * @throws AccessControlException If the operation is not permitted.
+     */
+    public Collection<String> getGroupNames()
+            throws TransientException, AccessControlException;
+    
     /**
      * Get the group with the given Group ID.
      *
@@ -108,10 +118,13 @@ public abstract interface GroupPersistence<T extends Principal>
      * @throws TransientException If an temporary, unexpected problem occurred.
      * @throws AccessControlException If the operation is not permitted.
      * @throws UserNotFoundException If owner or a member not valid user.
+     * @throws GroupNotFoundException if one of the groups in group members or
+     * group admins does not exist in the server.
      */
     public abstract Group addGroup(Group group)
         throws GroupAlreadyExistsException, TransientException,
-               AccessControlException, UserNotFoundException;
+               AccessControlException, UserNotFoundException, 
+               GroupNotFoundException;
 
     /**
      * Deletes the group.
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConfig.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConfig.java
index 17bd814f4431099e7c30f708ea4770c19e03f31f..42995612395ededc619d730d5a1441c31aea0900 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConfig.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConfig.java
@@ -68,14 +68,24 @@
  */
 package ca.nrc.cadc.ac.server.ldap;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.net.URL;
-import java.util.Properties;
+import java.util.List;
 
 import org.apache.log4j.Logger;
 
+import ca.nrc.cadc.db.ConnectionConfig;
+import ca.nrc.cadc.db.DBConfig;
+import ca.nrc.cadc.util.MultiValuedProperties;
+import ca.nrc.cadc.util.PropertiesReader;
 import ca.nrc.cadc.util.StringUtil;
 
+/**
+ * Reads and stores the LDAP configuration information. The information 
+ * 
+ * @author adriand
+ *
+ */
 public class LdapConfig
 {
     private static final Logger logger = Logger.getLogger(LdapConfig.class);
@@ -84,125 +94,122 @@ public class LdapConfig
                                         ".properties";
     public static final String LDAP_SERVER = "server";
     public static final String LDAP_PORT = "port";
-    public static final String LDAP_ADMIN = "admin";
-    public static final String LDAP_PASSWD = "passwd";
+    public static final String LDAP_SERVER_PROXY_USER = "proxyUser";
     public static final String LDAP_USERS_DN = "usersDn";
     public static final String LDAP_GROUPS_DN = "groupsDn";
     public static final String LDAP_ADMIN_GROUPS_DN  = "adminGroupsDn";
-    
-    public static final String LDAP_AVAIL_TEST_GROUP  = "availabilityTestGroup";
-    public static final String LDAP_AVAIL_TEST_CALLING_USER_DN  = "availabilityTestCallingUserDN";
+
+    private final static int SECURE_PORT = 636;
 
     private String usersDN;
     private String groupsDN;
     private String adminGroupsDN;
     private String server;
     private int port;
-    private String adminUserDN;
-    private String adminPasswd;
+    private String proxyUserDN;
+    private String proxyPasswd;
     
-    private String availabilityTestGroup;
-    private String availabilityTestCallingUserDN;
+    public String getProxyUserDN()
+    {
+        return proxyUserDN;
+    }
+
+    public String getProxyPasswd()
+    {
+        return proxyPasswd;
+    }
 
     public static LdapConfig getLdapConfig()
     {
-        Properties config = new Properties();
-        URL url = null;
-        try
-        {
-            url = LdapConfig.class.getClassLoader().getResource(CONFIG);
-            logger.debug("Using config from: " + url);
-            if (url != null)
-            {
-                config.load(url.openStream());
-            }
-            else
-            {
-                throw new IOException("File not found");
-            }
-        }
-        catch (Exception ex)
+        return getLdapConfig(CONFIG);
+    }
+
+    public static LdapConfig getLdapConfig(final String ldapProperties)
+    {
+        PropertiesReader pr = new PropertiesReader(ldapProperties);
+        
+        MultiValuedProperties config = pr.getAllProperties();
+        
+        if (config.keySet() == null)
         {
-            throw new RuntimeException("failed to read " + CONFIG + 
-                                       " from " + url, ex);
+            throw new RuntimeException("failed to read any LDAP property ");
         }
-
-        String server = config.getProperty(LDAP_SERVER);
-        if (!StringUtil.hasText(server))
+        
+        List<String> prop = config.getProperty(LDAP_SERVER);
+        if ((prop == null) || (prop.size() != 1))
         {
             throw new RuntimeException("failed to read property " + 
                                        LDAP_SERVER);
         }
+        String server = prop.get(0);
 
-        String port = config.getProperty(LDAP_PORT);
-        if (!StringUtil.hasText(port))
+        prop = config.getProperty(LDAP_PORT);
+        if ((prop == null) || (prop.size() != 1))
         {
             throw new RuntimeException("failed to read property " + LDAP_PORT);
         }
-
-        String ldapAdmin = config.getProperty(LDAP_ADMIN);
-        if (!StringUtil.hasText(ldapAdmin))
-        {
-            throw new RuntimeException("failed to read property " + LDAP_ADMIN);
-        }
-
-        String ldapPasswd = config.getProperty(LDAP_PASSWD);
-        if (!StringUtil.hasText(ldapPasswd))
+        int port = Integer.valueOf(prop.get(0));
+        
+        prop = config.getProperty(LDAP_SERVER_PROXY_USER);
+        if ((prop == null) || (prop.size() != 1))
         {
             throw new RuntimeException("failed to read property " + 
-                                       LDAP_PASSWD);
+                    LDAP_SERVER_PROXY_USER);
         }
-
-        String ldapUsersDn = config.getProperty(LDAP_USERS_DN);
-        if (!StringUtil.hasText(ldapUsersDn))
+        String ldapProxy = prop.get(0);
+        
+        prop = config.getProperty(LDAP_USERS_DN);
+        if ((prop == null) || (prop.size() != 1))
         {
             throw new RuntimeException("failed to read property " + 
                                        LDAP_USERS_DN);
         }
+        String ldapUsersDn = prop.get(0);
 
-        String ldapGroupsDn = config.getProperty(LDAP_GROUPS_DN);
-        if (!StringUtil.hasText(ldapGroupsDn))
+        prop = config.getProperty(LDAP_GROUPS_DN);
+        if ((prop == null) || (prop.size() != 1))
         {
             throw new RuntimeException("failed to read property " + 
                                        LDAP_GROUPS_DN);
         }
+        String ldapGroupsDn = prop.get(0);
         
-        String ldapAdminGroupsDn = config.getProperty(LDAP_ADMIN_GROUPS_DN);
-        if (!StringUtil.hasText(ldapAdminGroupsDn))
+        prop = config.getProperty(LDAP_ADMIN_GROUPS_DN);
+        if ((prop == null) || (prop.size() != 1))
         {
             throw new RuntimeException("failed to read property " + 
                                        LDAP_ADMIN_GROUPS_DN);
         }
+        String ldapAdminGroupsDn = prop.get(0);
         
-        String availGroup = config.getProperty(LDAP_AVAIL_TEST_GROUP);
-        if (!StringUtil.hasText(availGroup))
+        DBConfig dbConfig;
+        try
         {
-            throw new RuntimeException("failed to read property " + 
-                                       LDAP_AVAIL_TEST_GROUP);
+            dbConfig = new DBConfig();
+        } 
+        catch (FileNotFoundException e)
+        {
+            throw new RuntimeException("failed to find .dbrc file ");
+        } 
+        catch (IOException e)
+        {
+            throw new RuntimeException("failed to read .dbrc file ");
         }
-        
-        String availUser = config.getProperty(LDAP_AVAIL_TEST_CALLING_USER_DN);
-        if (!StringUtil.hasText(availUser))
+        ConnectionConfig cc = dbConfig.getConnectionConfig(server, ldapProxy);
+        if ( (cc == null) || (cc.getUsername() == null) || (cc.getPassword() == null))
         {
-            throw new RuntimeException("failed to read property " + 
-                                       LDAP_AVAIL_TEST_CALLING_USER_DN);
+            throw new RuntimeException("failed to find connection info in ~/.dbrc");
         }
-
-        return new LdapConfig(server, Integer.valueOf(port), ldapAdmin, 
-                              ldapPasswd, ldapUsersDn, ldapGroupsDn,
-                              ldapAdminGroupsDn, availGroup, availUser);
+        
+        return new LdapConfig(server, Integer.valueOf(port), cc.getUsername(), 
+                              cc.getPassword(), ldapUsersDn, ldapGroupsDn,
+                              ldapAdminGroupsDn);
     }
     
-    public LdapConfig(String server, int port, String adminUserDN, 
-            String adminPasswd, String usersDN, String groupsDN,
-            String adminGroupsDN)
-    {
-        this(server, port, adminUserDN, adminPasswd, usersDN, groupsDN, adminGroupsDN, null, null);
-    }
 
-    public LdapConfig(String server, int port, String adminUserDN, 
-                      String adminPasswd, String usersDN, String groupsDN,
-                      String adminGroupsDN, String availGroup, String availUser)
+    public LdapConfig(String server, int port, String proxyUserDN, 
+                      String proxyPasswd, String usersDN, String groupsDN,
+                      String adminGroupsDN)
     {
         if (!StringUtil.hasText(server))
         {
@@ -213,11 +220,11 @@ public class LdapConfig
             throw new IllegalArgumentException("Illegal LDAP server port: " + 
                                                port);
         }
-        if (!StringUtil.hasText(adminUserDN))
+        if (!StringUtil.hasText(proxyUserDN))
         {
             throw new IllegalArgumentException("Illegal Admin DN");
         }
-        if (!StringUtil.hasText(adminPasswd))
+        if (!StringUtil.hasText(proxyPasswd))
         {
             throw new IllegalArgumentException("Illegal Admin password");
         }
@@ -234,16 +241,14 @@ public class LdapConfig
             throw new IllegalArgumentException("Illegal admin groups LDAP DN");
         }
         
-
         this.server = server;
         this.port = port;
-        this.adminUserDN = adminUserDN;
-        this.adminPasswd = adminPasswd;
+        this.proxyUserDN = proxyUserDN;
+        this.proxyPasswd = proxyPasswd;
         this.usersDN = usersDN;
         this.groupsDN = groupsDN;
         this.adminGroupsDN = adminGroupsDN;
-        this.availabilityTestGroup = availGroup;
-        this.availabilityTestCallingUserDN = availUser;
+        logger.debug(toString());
     }
 
     public String getUsersDN()
@@ -271,24 +276,32 @@ public class LdapConfig
         return this.port;
     }
 
-    public String getAdminUserDN()
+    public boolean isSecure()
     {
-        return this.adminUserDN;
+        return getPort() == SECURE_PORT;
     }
 
-    public String getAdminPasswd()
+    public String getAdminUserDN()
     {
-        return this.adminPasswd;
+        return this.proxyUserDN;
     }
-    
-    public String getAvailabilityTestGroup()
+
+    public String getAdminPasswd()
     {
-        return this.availabilityTestGroup;
+        return this.proxyPasswd;
     }
-    
-    public String getAvailabilityTestCallingUserDN()
+
+    public String toString()
     {
-        return this.availabilityTestCallingUserDN;
+        StringBuilder sb = new StringBuilder();
+        sb.append("server = ");
+        sb.append(server);
+        sb.append(" port = ");
+        sb.append(port);
+        sb.append(" proxyUserDN = ");
+        sb.append(proxyUserDN);
+        sb.append(" proxyPasswd = ");
+        sb.append(proxyPasswd);
+        return sb.toString(); 
     }
-
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java
index c33961f36a8119f28f915b2405a4375127044558..abd19b24bde0eda15142be362587d4b22241b26a 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java
@@ -68,31 +68,28 @@
  */
 package ca.nrc.cadc.ac.server.ldap;
 
-import java.security.AccessControlException;
-import java.security.AccessController;
-import java.security.Principal;
-import java.util.Set;
-
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
 import javax.security.auth.Subject;
 import javax.security.auth.x500.X500Principal;
+import java.io.File;
+import java.net.MalformedURLException;
+import java.security.*;
+import java.security.cert.CertificateException;
+import java.util.Set;
+
+import com.unboundid.ldap.sdk.*;
+import com.unboundid.util.ssl.*;
 
-import ca.nrc.cadc.auth.HttpPrincipal;
-import ca.nrc.cadc.auth.NumericPrincipal;
-import ca.nrc.cadc.auth.OpenIdPrincipal;
+import ca.nrc.cadc.auth.*;
 import ca.nrc.cadc.net.TransientException;
 
-import com.unboundid.ldap.sdk.DN;
-import com.unboundid.ldap.sdk.LDAPConnection;
-import com.unboundid.ldap.sdk.LDAPException;
-import com.unboundid.ldap.sdk.ResultCode;
-import com.unboundid.ldap.sdk.SearchResult;
-import com.unboundid.ldap.sdk.SearchResultEntry;
-import com.unboundid.ldap.sdk.SearchScope;
 
 public abstract class LdapDAO
 {
     private LDAPConnection conn;
-    
+
     LdapConfig config;
     DN subjDN = null;
 
@@ -114,34 +111,64 @@ public abstract class LdapDAO
     }
 
     protected LDAPConnection getConnection()
-        throws LDAPException, AccessControlException
+            throws LDAPException, AccessControlException
     {
         if (conn == null)
         {
-            conn = new LDAPConnection(config.getServer(), config.getPort());
+            conn = new LDAPConnection(getSocketFactory(), config.getServer(),
+                                      config.getPort());
             conn.bind(config.getAdminUserDN(), config.getAdminPasswd());
         }
 
         return conn;
     }
 
+    private SocketFactory getSocketFactory()
+    {
+        final SocketFactory socketFactory;
+
+        if (config.isSecure())
+        {
+            socketFactory = createSSLSocketFactory();
+        }
+        else
+        {
+            socketFactory = SocketFactory.getDefault();
+        }
+
+        return socketFactory;
+    }
+
+    private SSLSocketFactory createSSLSocketFactory()
+    {
+        try
+        {
+            return new com.unboundid.util.ssl.SSLUtil().
+                    createSSLSocketFactory();
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new RuntimeException("Unexpected error.", e);
+        }
+    }
+
     protected DN getSubjectDN() throws LDAPException
     {
         if (subjDN == null)
         {
-            Subject callerSubject = 
+            Subject callerSubject =
                     Subject.getSubject(AccessController.getContext());
             if (callerSubject == null)
             {
                 throw new AccessControlException("Caller not authenticated.");
             }
-            
+
             Set<Principal> principals = callerSubject.getPrincipals();
             if (principals.isEmpty())
             {
                 throw new AccessControlException("Caller not authenticated.");
             }
-            
+
             String ldapField = null;
             for (Principal p : principals)
             {
@@ -172,31 +199,31 @@ public abstract class LdapDAO
                 throw new AccessControlException("Identity of caller unknown.");
             }
 
-            SearchResult searchResult = 
-                    getConnection().search(config.getUsersDN(), SearchScope.ONE, 
-                                ldapField, new String[] {"entrydn"});
+            SearchResult searchResult =
+                    getConnection().search(config.getUsersDN(), SearchScope.ONE,
+                                           ldapField, "entrydn");
 
             if (searchResult.getEntryCount() < 1)
             {
                 throw new AccessControlException(
                         "No LDAP account when search with rule " + ldapField);
             }
-            
-            subjDN = ((SearchResultEntry) searchResult.getSearchEntries()
-                    .get(0)).getAttributeValueAsDN("entrydn");
+
+            subjDN = (searchResult.getSearchEntries().get(0))
+                    .getAttributeValueAsDN("entrydn");
         }
         return subjDN;
     }
-    
+
     /**
      * Checks the Ldap result code, and if the result is not SUCCESS,
-     * throws an appropriate exception. This is the place to decide on 
+     * throws an appropriate exception. This is the place to decide on
      * mapping between ldap errors and exception types
-     * @param code
-     * @param errorMsg
-     * @throws TransientException 
+     *
+     * @param code          The code returned from an LDAP request.
+     * @throws TransientException
      */
-    protected static void checkLdapResult(ResultCode code) 
+    protected static void checkLdapResult(ResultCode code)
             throws TransientException
     {
         if (code == ResultCode.INSUFFICIENT_ACCESS_RIGHTS)
@@ -207,7 +234,8 @@ public abstract class LdapDAO
         {
             throw new AccessControlException("Invalid credentials ");
         }
-        else if ((code == ResultCode.SUCCESS) || (code == ResultCode.NO_SUCH_OBJECT) )
+        else if ((code == ResultCode.SUCCESS) || (code
+                                                  == ResultCode.NO_SUCH_OBJECT))
         {
             // all good. nothing to do
         }
@@ -216,7 +244,7 @@ public abstract class LdapDAO
             throw new IllegalArgumentException("Error in Ldap parameters ");
         }
         else if (code == ResultCode.BUSY ||
-                 code == ResultCode.CONNECT_ERROR )
+                 code == ResultCode.CONNECT_ERROR)
         {
             throw new TransientException("Connection problems ");
         }
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 b85a4a31addbde3ca5d12220f2e6de6a0569118f..70ee0595f868c4805fda36310ff6cbd9b9278a5b 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
@@ -88,6 +88,7 @@ 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 ca.nrc.cadc.util.StringUtil;
 
 import com.unboundid.ldap.sdk.AddRequest;
 import com.unboundid.ldap.sdk.Attribute;
@@ -134,10 +135,12 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
      *                                     exists.
      * @throws TransientException If an temporary, unexpected problem occurred.
      * @throws UserNotFoundException If owner or a member not valid user.
+     * @throws GroupNotFoundException 
      */
     public Group addGroup(final Group group)
         throws GroupAlreadyExistsException, TransientException,
-               UserNotFoundException, AccessControlException
+               UserNotFoundException, AccessControlException, 
+               GroupNotFoundException
     {
         if (group.getOwner() == null)
         {
@@ -204,7 +207,8 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
                                 final DN ownerDN, final String description, 
                                 final Set<User<? extends Principal>> users, 
                                 final Set<Group> groups)
-        throws UserNotFoundException, LDAPException, TransientException
+        throws UserNotFoundException, LDAPException, TransientException, 
+        AccessControlException, GroupNotFoundException
     {
         // add new group
         List<Attribute> attributes = new ArrayList<Attribute>();
@@ -214,7 +218,7 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
         attributes.add(new Attribute("objectClass", "groupofuniquenames"));
         attributes.add(new Attribute("cn", groupID));
         
-        if (description != null)
+        if (StringUtil.hasText(description))
         {
             attributes.add(new Attribute("description", description));
         }
@@ -227,7 +231,12 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
         }
         for (Group groupMember : groups)
         {
-            DN memberDN = getGroupDN(groupMember.getID());
+            final String groupMemberID = groupMember.getID();
+            if (!checkGroupExists(groupMemberID))
+            {
+                throw new GroupNotFoundException(groupMemberID);
+            }
+            DN memberDN = getGroupDN(groupMemberID);
             members.add(memberDN.toNormalizedString());
         }
         if (!members.isEmpty())
@@ -305,6 +314,60 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
             throw new RuntimeException("Unexpected LDAP exception", e);
         }
     }
+    
+    
+    /**
+     * Get all group names.
+     * 
+     * @return A collection of strings
+     * 
+     * @throws TransientException If an temporary, unexpected problem occurred.
+     */
+    public Collection<String> getGroupNames()
+        throws TransientException
+    {
+        try
+        {
+            Filter filter = Filter.createPresenceFilter("cn");
+            String [] attributes = new String[] {"cn", "nsaccountlock"};
+            
+            SearchRequest searchRequest = 
+                    new SearchRequest(config.getGroupsDN(), 
+                                      SearchScope.SUB, filter, attributes);
+    
+            SearchResult searchResult = null;
+            try
+            {
+                searchResult = getConnection().search(searchRequest);
+            }
+            catch (LDAPSearchException e)
+            {
+                if (e.getResultCode() == ResultCode.NO_SUCH_OBJECT)
+                {
+                    logger.debug("Could not find groups root", e);
+                    throw new IllegalStateException("Could not find groups root");
+                }
+            }
+            
+            LdapDAO.checkLdapResult(searchResult.getResultCode());
+            List<String> groupNames = new ArrayList<String>();
+            for (SearchResultEntry next : searchResult.getSearchEntries())
+            {
+                if (!next.hasAttribute("nsaccountlock"))
+                {
+                    groupNames.add(next.getAttributeValue("cn"));
+                }
+            }
+            
+            return groupNames;
+        }
+        catch (LDAPException e1)
+        {
+            LdapDAO.checkLdapResult(e1.getResultCode());
+            throw new IllegalStateException("Unexpected exception: " + e1.getMatchedDN(), e1);
+        }
+        
+    }
 
     /**
      * Get the group with the given Group ID.
@@ -418,8 +481,12 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
                 throw new GroupNotFoundException(groupID);
             }
             
-            String groupCN = searchEntry.getAttributeValue("cn");
             DN groupOwner = searchEntry.getAttributeValueAsDN("owner");
+            if (groupOwner == null)
+            {
+                //TODO assume user not allowed to read group
+                throw new AccessControlException(groupID);
+            }
             
             User<X500Principal> owner;
             try
@@ -431,7 +498,7 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
                 throw new RuntimeException("BUG: group owner not found");
             }
             
-            Group ldapGroup = new Group(groupCN, owner);
+            Group ldapGroup = new Group(groupID, owner);
             if (searchEntry.hasAttribute("description"))
             {
                 ldapGroup.description = 
@@ -544,8 +611,12 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
         }
         for (Group gr : group.getGroupMembers())
         {
-                DN grDN = getGroupDN(gr.getID());
-                newMembers.add(grDN.toNormalizedString());
+            if (!checkGroupExists(gr.getID()))
+            {
+                throw new GroupNotFoundException(gr.getID());
+            }
+            DN grDN = getGroupDN(gr.getID());
+            newMembers.add(grDN.toNormalizedString());
         }
         List<String> newAdmins = new ArrayList<String>();
         for (User<?> member : group.getUserAdmins())
@@ -555,8 +626,12 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
         }
         for (Group gr : group.getGroupAdmins())
         {
+            if (!checkGroupExists(gr.getID()))
+            {
+                throw new GroupNotFoundException(gr.getID());
+            }
             DN grDN = getGroupDN(gr.getID());
-            newMembers.add(grDN.toNormalizedString());
+            newAdmins.add(grDN.toNormalizedString());
         }
 
         mods.add(new Modification(ModificationType.REPLACE, "uniquemember", 
@@ -691,7 +766,16 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
                GroupNotFoundException, UserNotFoundException
     {
         User<T> user = new User<T>(userID);
-        DN userDN = userPersist.getUserDN(user);
+        DN userDN = null;
+        try
+        {
+            userDN = userPersist.getUserDN(user);
+        }
+        catch (UserNotFoundException e)
+        {
+            // no anonymous searches
+            throw new AccessControlException("Not authorized to search");
+        }
         
         Collection<DN> groupDNs = new HashSet<DN>();
         if (role == Role.OWNER)
@@ -731,9 +815,10 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
                 }
                 catch (GroupNotFoundException e)
                 {
-                    throw new IllegalStateException(
-                        "BUG: group " + groupDN + " not found but " +
-                        "membership exists (" + userID + ")");
+                    final String message = "BUG: group " + groupDN + " not found but " +
+                                           "membership exists (" + userID + ")";
+                    logger.error(message);
+                    //throw new IllegalStateException(message);
                 }
             }
         }
@@ -931,5 +1016,18 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
             throw new RuntimeException(e);
         }
     }
+    
+    private boolean checkGroupExists(String groupID) 
+            throws TransientException
+    {
+        for (String groupName : getGroupNames())
+        {
+            if (groupName.equalsIgnoreCase(groupID))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
 
 }
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 4ef7ef53f58a5bcfa7346d972499ce736633e0fa..f59bc1518d82c3522bf0cd104bc3ca8fecfc7ebf 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
@@ -93,7 +93,32 @@ public class LdapGroupPersistence<T extends Principal>
     {
         config = LdapConfig.getLdapConfig();
     }
-
+    
+    public Collection<String> getGroupNames()
+        throws TransientException, AccessControlException
+    {
+        LdapGroupDAO<T> groupDAO = null;
+        LdapUserDAO<T> userDAO = null;
+        try
+        {
+            userDAO = new LdapUserDAO<T>(config);
+            groupDAO = new LdapGroupDAO<T>(config, userDAO);
+            Collection<String> ret = groupDAO.getGroupNames();
+            return ret;
+        }
+        finally
+        {
+            if (groupDAO != null)
+            {
+                groupDAO.close();
+            }
+            if (userDAO != null)
+            {
+                userDAO.close();
+            }
+        }
+    }
+    
     public Group getGroup(String groupName)
         throws GroupNotFoundException, TransientException,
                AccessControlException
@@ -122,7 +147,8 @@ public class LdapGroupPersistence<T extends Principal>
 
     public Group addGroup(Group group)
         throws GroupAlreadyExistsException, TransientException, 
-               AccessControlException, UserNotFoundException
+               AccessControlException, UserNotFoundException, 
+               GroupNotFoundException
     {
         LdapGroupDAO<T> groupDAO = null;
         LdapUserDAO<T> userDAO = null;
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 c3e33dca2cfe6a99fd87095489074f8ff234f804..f929385511697284380d6e5f6f4abef2d6457ce9 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,6 +68,7 @@
  */
 package ca.nrc.cadc.ac.server.ldap;
 
+import javax.security.auth.x500.X500Principal;
 import java.security.AccessControlException;
 import java.security.Principal;
 import java.util.Collection;
@@ -75,32 +76,25 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 
-import javax.security.auth.x500.X500Principal;
-
+import com.unboundid.ldap.sdk.*;
+import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl;
 import org.apache.log4j.Logger;
 
 import ca.nrc.cadc.ac.PersonalDetails;
 import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.UserNotFoundException;
-import ca.nrc.cadc.auth.AuthenticationUtil;
 import ca.nrc.cadc.auth.HttpPrincipal;
 import ca.nrc.cadc.net.TransientException;
 
-import com.unboundid.ldap.sdk.DN;
-import com.unboundid.ldap.sdk.Filter;
-import com.unboundid.ldap.sdk.LDAPException;
-import com.unboundid.ldap.sdk.SearchRequest;
-import com.unboundid.ldap.sdk.SearchResultEntry;
-import com.unboundid.ldap.sdk.SearchScope;
-import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl;
 
 public class LdapUserDAO<T extends Principal> extends LdapDAO
 {
     private static final Logger logger = Logger.getLogger(LdapUserDAO.class);
 
     // Map of identity type to LDAP attribute
-    private Map<Class<?>, String> userLdapAttrib = new HashMap<Class<?>, String>();
-    
+    private Map<Class<?>, String> userLdapAttrib =
+            new HashMap<Class<?>, String>();
+
     // User attributes returned to the GMS
     private static final String LDAP_FNAME = "givenname";
     private static final String LDAP_LNAME = "sn";
@@ -113,54 +107,60 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
         super(config);
         this.userLdapAttrib.put(HttpPrincipal.class, "uid");
         this.userLdapAttrib.put(X500Principal.class, "distinguishedname");
-        
+
         // add the id attributes to user and member attributes
-        String[] princs = userLdapAttrib.values().toArray(new String[userLdapAttrib.values().size()]);
+        String[] princs = userLdapAttrib.values()
+                .toArray(new String[userLdapAttrib.values().size()]);
         String[] tmp = new String[userAttribs.length + princs.length];
         System.arraycopy(princs, 0, tmp, 0, princs.length);
-        System.arraycopy(userAttribs, 0, tmp, princs.length, userAttribs.length);
+        System.arraycopy(userAttribs, 0, tmp, princs.length,
+                         userAttribs.length);
         userAttribs = tmp;
-        
+
         tmp = new String[memberAttribs.length + princs.length];
         System.arraycopy(princs, 0, tmp, 0, princs.length);
-        System.arraycopy(memberAttribs, 0, tmp, princs.length, memberAttribs.length);
+        System.arraycopy(memberAttribs, 0, tmp, princs.length,
+                         memberAttribs.length);
         memberAttribs = tmp;
     }
-    
 
 
     /**
      * 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 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
+            throws UserNotFoundException, TransientException,
+                   AccessControlException
     {
-        String searchField = (String) userLdapAttrib.get(userID.getClass());
+        String searchField = userLdapAttrib.get(userID.getClass());
         if (searchField == null)
         {
             throw new IllegalArgumentException(
                     "Unsupported principal type " + userID.getClass());
         }
 
-        searchField = "(&(objectclass=cadcaccount)(" + searchField + "=" + userID.getName() + "))";
+        searchField =
+                "(&(objectclass=cadcaccount)(" + searchField + "=" + userID
+                        .getName() + "))";
 
         SearchResultEntry searchResult = null;
         try
         {
-            SearchRequest searchRequest = new SearchRequest(config.getUsersDN(), 
-                    SearchScope.SUB, searchField, userAttribs);
- 
+            SearchRequest searchRequest = new SearchRequest(config.getUsersDN(),
+                                                            SearchScope.SUB,
+                                                            searchField,
+                                                            userAttribs);
+
             searchRequest.addControl(
-                    new ProxiedAuthorizationV2RequestControl("dn:" + 
-                            getSubjectDN().toNormalizedString()));
+                    new ProxiedAuthorizationV2RequestControl("dn:" +
+                                                             getSubjectDN()
+                                                                     .toNormalizedString()));
 
             searchResult = getConnection().searchForEntry(searchRequest);
         }
@@ -178,57 +178,57 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
         User<T> user = new User<T>(userID);
         user.getIdentities().add(
                 new HttpPrincipal(searchResult.getAttributeValue(userLdapAttrib
-                        .get(HttpPrincipal.class))));
-        
+                                                                         .get(HttpPrincipal.class))));
+
         String fname = searchResult.getAttributeValue(LDAP_FNAME);
         String lname = searchResult.getAttributeValue(LDAP_LNAME);
         user.details.add(new PersonalDetails(fname, lname));
         //TODO populate user with the other returned personal or posix attributes
         return user;
-    }   
+    }
 
     /**
      * Get all groups the user specified by userID belongs to.
-     * 
-     * @param userID The userID.
+     *
+     * @param userID  The userID.
      * @param isAdmin
-     * 
      * @return Collection of Group instances.
-     * 
      * @throws UserNotFoundException  when the user is not found.
-     * @throws TransientException If an temporary, unexpected problem occurred., e.getMessage(
+     * @throws TransientException     If an temporary, unexpected problem occurred., e.getMessage(
      * @throws AccessControlException If the operation is not permitted.
      */
     public Collection<DN> getUserGroups(final T userID, final boolean isAdmin)
-        throws UserNotFoundException, TransientException, AccessControlException
+            throws UserNotFoundException, TransientException,
+                   AccessControlException
     {
         Collection<DN> groupDNs = new HashSet<DN>();
         try
         {
-            String searchField = (String) userLdapAttrib.get(userID.getClass());
+            String searchField = userLdapAttrib.get(userID.getClass());
             if (searchField == null)
             {
                 throw new IllegalArgumentException(
                         "Unsupported principal type " + userID.getClass());
             }
 
-            User<T> user = getUser(userID);            
+            User<T> user = getUser(userID);
             Filter filter = Filter.createANDFilter(
-                        Filter.createEqualityFilter(searchField, 
-                                                    user.getUserID().getName()),
-                        Filter.createPresenceFilter("memberOf"));
+                    Filter.createEqualityFilter(searchField,
+                                                user.getUserID().getName()),
+                    Filter.createPresenceFilter("memberOf"));
 
-            SearchRequest searchRequest = 
-                    new SearchRequest(config.getUsersDN(), SearchScope.SUB, 
+            SearchRequest searchRequest =
+                    new SearchRequest(config.getUsersDN(), SearchScope.SUB,
                                       filter, "memberOf");
 
             searchRequest.addControl(
-                    new ProxiedAuthorizationV2RequestControl("dn:" + 
-                            getSubjectDN().toNormalizedString()));
+                    new ProxiedAuthorizationV2RequestControl("dn:" +
+                                                             getSubjectDN()
+                                                                     .toNormalizedString()));
 
-            SearchResultEntry searchResult = 
+            SearchResultEntry searchResult =
                     getConnection().searchForEntry(searchRequest);
-            
+
             DN parentDN;
             if (isAdmin)
             {
@@ -238,7 +238,7 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
             {
                 parentDN = new DN(config.getGroupsDN());
             }
-            
+
             if (searchResult != null)
             {
                 String[] members = searchResult.getAttributeValues("memberOf");
@@ -253,7 +253,7 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
                         }
                     }
                 }
-            } 
+            }
         }
         catch (LDAPException e)
         {
@@ -261,26 +261,24 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
         }
         return groupDNs;
     }
-    
+
     /**
      * Check whether the user is a member of the group.
      *
-     * @param userID The userID.
+     * @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 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
+            throws UserNotFoundException, TransientException,
+                   AccessControlException
     {
         try
         {
-            String searchField = (String) userLdapAttrib.get(userID.getClass());
+            String searchField = userLdapAttrib.get(userID.getClass());
             if (searchField == null)
             {
                 throw new IllegalArgumentException(
@@ -289,26 +287,23 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
 
             User<T> user = getUser(userID);
             Filter filter = Filter.createANDFilter(
-                        Filter.createEqualityFilter(searchField, 
-                                                    user.getUserID().getName()),
-                        Filter.createEqualityFilter("memberOf", groupID));
+                    Filter.createEqualityFilter(searchField,
+                                                user.getUserID().getName()),
+                    Filter.createEqualityFilter("memberOf", groupID));
 
-            SearchRequest searchRequest = 
-                    new SearchRequest(config.getUsersDN(), SearchScope.SUB, 
-                                      filter, new String[] {"cn"});
+            SearchRequest searchRequest =
+                    new SearchRequest(config.getUsersDN(), SearchScope.SUB,
+                                      filter, "cn");
 
             searchRequest.addControl(
-                    new ProxiedAuthorizationV2RequestControl("dn:" + 
-                            getSubjectDN().toNormalizedString()));
-            
-            SearchResultEntry searchResults = 
+                    new ProxiedAuthorizationV2RequestControl("dn:" +
+                                                             getSubjectDN()
+                                                                     .toNormalizedString()));
+
+            SearchResultEntry searchResults =
                     getConnection().searchForEntry(searchRequest);
-            
-            if (searchResults == null)
-            {
-                return false;
-            }
-            return true;
+
+            return (searchResults != null);
         }
         catch (LDAPException e)
         {
@@ -316,7 +311,7 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
         }
         return false;
     }
-    
+
 //    public boolean isMember(T userID, String groupID)
 //        throws UserNotFoundException, TransientException,
 //               AccessControlException
@@ -351,29 +346,30 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
 //            throw new RuntimeException("Unexpected LDAP exception", e);
 //        }
 //    }
-    
+
     /**
      * Returns a member user identified by the X500Principal only. The
      * returned object has the fields required by the GMS.
-     * Note that this method binds as a proxy user and not as the 
+     * Note that this method binds as a proxy user and not as the
      * subject.
+     *
      * @param userDN
      * @return
      * @throws UserNotFoundException
      * @throws LDAPException
      */
     User<X500Principal> getMember(DN userDN)
-        throws UserNotFoundException, LDAPException
+            throws UserNotFoundException, LDAPException
     {
-        Filter filter = 
-            Filter.createEqualityFilter("entrydn", 
-                                        userDN.toNormalizedString());
-        
-        SearchRequest searchRequest = 
-                new SearchRequest(this.config.getUsersDN(), SearchScope.SUB, 
+        Filter filter =
+                Filter.createEqualityFilter("entrydn",
+                                            userDN.toNormalizedString());
+
+        SearchRequest searchRequest =
+                new SearchRequest(this.config.getUsersDN(), SearchScope.SUB,
                                   filter, memberAttribs);
-        
-        SearchResultEntry searchResult = 
+
+        SearchResultEntry searchResult =
                 getConnection().searchForEntry(searchRequest);
 
         if (searchResult == null)
@@ -384,9 +380,9 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
         }
         User<X500Principal> user = new User<X500Principal>(
                 new X500Principal(searchResult.getAttributeValue(
-                        (String) userLdapAttrib.get(X500Principal.class))));
+                        userLdapAttrib.get(X500Principal.class))));
         String princ = searchResult.getAttributeValue(
-                (String) userLdapAttrib.get(HttpPrincipal.class));
+                userLdapAttrib.get(HttpPrincipal.class));
         if (princ != null)
         {
             user.getIdentities().add(new HttpPrincipal(princ));
@@ -396,32 +392,36 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
         user.details.add(new PersonalDetails(fname, lname));
         return user;
     }
-    
+
 
     DN getUserDN(User<? extends Principal> user)
-        throws UserNotFoundException, TransientException
+            throws UserNotFoundException, TransientException
     {
-        String searchField = (String) userLdapAttrib.get(user.getUserID().getClass());
+        String searchField =
+                userLdapAttrib.get(user.getUserID().getClass());
         if (searchField == null)
         {
             throw new IllegalArgumentException(
-                "Unsupported principal type " + user.getUserID().getClass());
+                    "Unsupported principal type " + user.getUserID()
+                            .getClass());
         }
 
-        searchField = "(" + searchField + "=" + 
-                user.getUserID().getName() + ")";
+        searchField = "(" + searchField + "=" +
+                      user.getUserID().getName() + ")";
 
         SearchResultEntry searchResult = null;
         try
         {
-            SearchRequest searchRequest = new SearchRequest(this.config.getUsersDN(), SearchScope.SUB, 
-                             searchField, new String[] {"entrydn"});
-        
+            SearchRequest searchRequest =
+                    new SearchRequest(this.config.getUsersDN(), SearchScope.SUB,
+                                      searchField, "entrydn");
 
-            searchResult = 
-                getConnection().searchForEntry(searchRequest);
 
-        } catch (LDAPException e)
+            searchResult =
+                    getConnection().searchForEntry(searchRequest);
+
+        }
+        catch (LDAPException e)
         {
             LdapDAO.checkLdapResult(e.getResultCode());
         }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/CreateGroupAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/CreateGroupAction.java
index 15689805571ce6c7e8ca441ecfcc087548a7301a..32661345fc85f1042784a01421c27da98d4b9c48 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/CreateGroupAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/CreateGroupAction.java
@@ -68,17 +68,15 @@
  */
 package ca.nrc.cadc.ac.server.web;
 
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
 import ca.nrc.cadc.ac.Group;
 import ca.nrc.cadc.ac.GroupReader;
 import ca.nrc.cadc.ac.GroupWriter;
 import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.server.GroupPersistence;
-import java.io.InputStream;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import javax.servlet.http.HttpServletResponse;
 
 public class CreateGroupAction extends GroupsAction
 {
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GetGroupAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GetGroupAction.java
index 9854b25d4e785ac066a2d6de97ed2919ea634352..e72003567a83a2723ee6ce15376d5a5beee22027 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GetGroupAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GetGroupAction.java
@@ -70,7 +70,6 @@
 import ca.nrc.cadc.ac.Group;
 import ca.nrc.cadc.ac.GroupWriter;
 import ca.nrc.cadc.ac.server.GroupPersistence;
-import javax.servlet.http.HttpServletResponse;
 
 public class GetGroupAction extends GroupsAction
 {
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GetGroupNamesAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GetGroupNamesAction.java
index 40a0d780938c326ff18fb3df7478ea264e9e75ac..a29e5fcf29a37ebf66ded5fd96cff4edbe77f26c 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GetGroupNamesAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GetGroupNamesAction.java
@@ -65,16 +65,21 @@
  *  $Revision: 4 $
  *
  ************************************************************************
- */package ca.nrc.cadc.ac.server.web;
+ */
 
+package ca.nrc.cadc.ac.server.web;
+
+import java.io.Writer;
 import java.util.Collection;
 
-import ca.nrc.cadc.ac.server.GroupPersistence;
+import org.apache.log4j.Logger;
 
-import com.csvreader.CsvWriter;
+import ca.nrc.cadc.ac.server.GroupPersistence;
 
 public class GetGroupNamesAction extends GroupsAction
 {
+    
+    private static final Logger log = Logger.getLogger(GetGroupNamesAction.class);
 
     GetGroupNamesAction(GroupLogInfo logInfo)
     {
@@ -86,16 +91,21 @@ public class GetGroupNamesAction extends GroupsAction
     {
         GroupPersistence groupPersistence = getGroupPersistence();
         Collection<String> groups = groupPersistence.getGroupNames();
-        response.setContentType("text/csv");
-        
-        CsvWriter writer = new CsvWriter(response.getWriter(), ',');
-        
-        for (String group : groups)
+        log.debug("Found " + groups.size() + " group names");
+        response.setContentType("text/plain");
+        log.debug("Set content-type to text/plain");
+        Writer writer = response.getWriter();
+        boolean start = true;
+        for (final String group : groups)
         {
+            if (!start)
+            {
+                writer.write("\r\n");
+            }
             writer.write(group);
+            start = false;
         }
-        writer.endRecord();
+        
         return null;
     }
-
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GroupsActionFactory.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GroupsActionFactory.java
index f71e21c28a7cd96240d8d1ee92d6a5426a6de382..83ecc9d1f7a0caae37d88e1b53d7c7183e1079e5 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GroupsActionFactory.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GroupsActionFactory.java
@@ -68,12 +68,16 @@
  */
 package ca.nrc.cadc.ac.server.web;
 
-import ca.nrc.cadc.util.StringUtil;
 import java.io.IOException;
+import java.net.URL;
 import java.net.URLDecoder;
+
 import javax.servlet.http.HttpServletRequest;
+
 import org.apache.log4j.Logger;
 
+import ca.nrc.cadc.util.StringUtil;
+
 public class GroupsActionFactory
 {
     private static final Logger log = Logger.getLogger(GroupsActionFactory.class);
@@ -89,16 +93,19 @@ public class GroupsActionFactory
 
         if (path == null)
         {
-            path = new String();
+            path = "";
         }
+
         if (path.startsWith("/"))
         {
             path = path.substring(1);
         }
+
         if (path.endsWith("/"))
         {
             path = path.substring(0, path.length() - 1);
         }
+
         String[] segments = new String[0];
         if (StringUtil.hasText(path))
         {
@@ -109,7 +116,7 @@ public class GroupsActionFactory
         {
             if (method.equals("GET"))
             {
-                action = new ListGroupsAction(logInfo);
+                action = new GetGroupNamesAction(logInfo);
             }
             else if (method.equals("PUT"))
             {
@@ -130,7 +137,16 @@ public class GroupsActionFactory
             }
             else if (method.equals("POST"))
             {
-                action = new ModifyGroupAction(logInfo, groupName, request.getRequestURI(), request.getInputStream());
+                final URL requestURL =
+                        new URL(request.getRequestURL().toString());
+                final String redirectURI = requestURL.getProtocol() + "://"
+                                           + requestURL.getHost() + ":"
+                                           + requestURL.getPort()
+                                           + request.getContextPath()
+                                           + request.getServletPath()
+                                           + "/" + path;
+                action = new ModifyGroupAction(logInfo, groupName, redirectURI,
+                                               request.getInputStream());
             }
         }
         else if (segments.length == 3)
@@ -169,6 +185,7 @@ public class GroupsActionFactory
 
         if (action != null)
         {
+            log.debug("Returning action: " + action.getClass());
             return action;
         }
         throw new IllegalArgumentException("Bad groups request: " + method + " on " + path);
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/ModifyGroupAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/ModifyGroupAction.java
index af5b564249e6c4ba4f268a8364c28e3527785923..c7a03ca5688c0d2c697ff24296428310767edc29 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/ModifyGroupAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/ModifyGroupAction.java
@@ -68,16 +68,14 @@
  */
 package ca.nrc.cadc.ac.server.web;
 
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
 import ca.nrc.cadc.ac.Group;
 import ca.nrc.cadc.ac.GroupReader;
 import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.server.GroupPersistence;
-import java.io.InputStream;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import javax.servlet.http.HttpServletResponse;
 
 public class ModifyGroupAction extends GroupsAction
 {
@@ -85,7 +83,8 @@ public class ModifyGroupAction extends GroupsAction
     private final String request;
     private final InputStream inputStream;
 
-    ModifyGroupAction(GroupLogInfo logInfo, String groupName, String request, InputStream inputStream)
+    ModifyGroupAction(GroupLogInfo logInfo, String groupName,
+                      final String request, InputStream inputStream)
     {
         super(logInfo);
         this.groupName = groupName;
@@ -99,8 +98,8 @@ public class ModifyGroupAction extends GroupsAction
         GroupPersistence groupPersistence = getGroupPersistence();
         Group group = GroupReader.read(this.inputStream);
         Group oldGroup = groupPersistence.getGroup(this.groupName);
-        Group modifiedGroup = groupPersistence.modifyGroup(group);
-        
+        groupPersistence.modifyGroup(group);
+
         List<String> addedMembers = new ArrayList<String>();
         for (User member : group.getUserMembers())
         {
@@ -134,8 +133,9 @@ public class ModifyGroupAction extends GroupsAction
             deletedMembers = null;
         }
         logGroupInfo(group.getID(), deletedMembers, addedMembers);
-        
+
         this.response.sendRedirect(request);
+
         return null;
     }
 
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/AbstractLdapDAOTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/AbstractLdapDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5801981ac380b8f2314d4b321952cd86d15789ea
--- /dev/null
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/AbstractLdapDAOTest.java
@@ -0,0 +1,82 @@
+/**
+ ************************************************************************
+ *******************  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/>.
+ *
+ ************************************************************************
+ */
+
+package ca.nrc.cadc.ac.server.ldap;
+
+/**
+ * Created by jburke on 2014-11-03.
+ */
+public class AbstractLdapDAOTest
+{
+    static final String CONFIG = LdapConfig.class.getSimpleName() + ".test.properties";
+
+    static protected LdapConfig getLdapConfig()
+    {
+        return LdapConfig.getLdapConfig(CONFIG);
+    }
+
+}
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapDAOTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapDAOTest.java
index a79aa1a456ba2e629c331b1143f5d08900dec9ea..ecd65e9483ab73d8813a600fc6833ca38dbfb673 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapDAOTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapDAOTest.java
@@ -68,38 +68,39 @@
 
 package ca.nrc.cadc.ac.server.ldap;
 
-import static ca.nrc.cadc.ac.server.ldap.LdapGroupDAOTest.config;
-import static org.junit.Assert.assertTrue;
-
 import java.security.PrivilegedExceptionAction;
 
+import javax.net.ssl.SSLSocketFactory;
 import javax.security.auth.Subject;
 import javax.security.auth.x500.X500Principal;
 
-import org.junit.Test;
-
 import ca.nrc.cadc.auth.HttpPrincipal;
 import ca.nrc.cadc.auth.NumericPrincipal;
 
 import com.unboundid.ldap.sdk.LDAPConnection;
 
-public class LdapDAOTest
+import org.junit.Test;
+import org.junit.BeforeClass;
+import static org.junit.Assert.*;
+
+
+public class LdapDAOTest extends AbstractLdapDAOTest
 {
-    static String server = "mach275.cadc.dao.nrc.ca";
-    static int port = 389;
-    static String adminDN = "uid=webproxy,ou=WebProxy,ou=topologymanagement,o=netscaperoot";
-    static String adminPW = "go4it";
-    static String usersDN = "ou=Users,ou=ds,dc=canfartest,dc=net";
-    static String groupsDN = "ou=Groups,ou=ds,dc=canfartest,dc=net";
-    static String adminGroupsDN = "ou=adminGroups,ou=ds,dc=canfartest,dc=net";
-    
-    LdapConfig config = new LdapConfig(server, port, adminDN, adminPW, usersDN, groupsDN, adminGroupsDN);
+    static LdapConfig config;
     
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception
+    {
+        // get the configuration of the development server from and config files...
+        config = getLdapConfig();
+    }
     @Test
     public void testLdapBindConnection() throws Exception
     {
         //TODO use a test user to test with. To be done when addUser available.
         //LdapUserDAO<X500Principal> userDAO = new LdapUserDAO<X500Principal>();
+        final X500Principal subjPrincipal = new X500Principal(
+                "cn=cadcdaotest1,ou=cadc,o=hia,c=ca");
 
         // User authenticated with HttpPrincipal
         HttpPrincipal httpPrincipal = new HttpPrincipal("CadcDaoTest1");
@@ -115,8 +116,7 @@ public class LdapDAOTest
             {
                 try
                 {
-                    LDAPConnection ldapCon = ldapDao.getConnection();
-                    assertTrue(ldapCon.isConnected());
+                    testConnection(ldapDao.getConnection());
                     return null;
                 }
                 catch (Exception e)
@@ -126,9 +126,7 @@ public class LdapDAOTest
             }
         });
                
-        
-        X500Principal subjPrincipal = new X500Principal(
-                "cn=cadcdaotest1,ou=cadc,o=hia,c=ca");
+
         subject = new Subject();
         subject.getPrincipals().add(subjPrincipal);
         
@@ -138,8 +136,7 @@ public class LdapDAOTest
             {
                 try
                 {
-                    LDAPConnection ldapCon = ldapDao.getConnection();
-                    assertTrue(ldapCon.isConnected());
+                    testConnection(ldapDao.getConnection());
                     return null;
                 }
                 catch (Exception e)
@@ -160,8 +157,7 @@ public class LdapDAOTest
                 try
                 {
 
-                    LDAPConnection ldapCon = ldapDao.getConnection();
-                    assertTrue(ldapCon.isConnected());
+                    testConnection(ldapDao.getConnection());
                     return null;
                 }
                 catch (Exception e)
@@ -172,4 +168,10 @@ public class LdapDAOTest
         });
 
     }
+
+    private void testConnection(final LDAPConnection ldapCon)
+    {
+        assertTrue("Not connected but should be.", ldapCon.isConnected());
+    }
+
 }
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapDAOTestImpl.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapDAOTestImpl.java
index cf3c8d2e2e4eb5b87e7adbe9ef8631173ce8a4f9..7d3be479f44fc01e2eaaab7805e645a1dc9f573a 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapDAOTestImpl.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapDAOTestImpl.java
@@ -66,7 +66,6 @@
  */
 
 
-
 package ca.nrc.cadc.ac.server.ldap;
 
 import java.security.AccessControlException;
@@ -74,16 +73,17 @@ import java.security.AccessControlException;
 import com.unboundid.ldap.sdk.LDAPConnection;
 import com.unboundid.ldap.sdk.LDAPException;
 
+
 public class LdapDAOTestImpl extends LdapDAO
 {
     public LdapDAOTestImpl(LdapConfig config)
     {
         super(config);
     }
-    
+
     @Override
     public LDAPConnection getConnection() throws LDAPException,
-    AccessControlException
+                                                 AccessControlException
     {
         return super.getConnection();
     }
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 e29da595b841c4ee6651ae5a5c1c1a3bb780082e..7af54bc0ae0a9ec3c1e3621a31038538440a7ae9 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
@@ -1,34 +1,67 @@
-/*
+/**
  ************************************************************************
- ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
  *
- * (c) 2014.                            (c) 2014.
- * National Research Council            Conseil national de recherches
- * Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
- * All rights reserved                  Tous droits reserves
+ *  (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 denie toute garantie
- * expressed, implied, or statu-        enoncee, implicite ou legale,
- * tory, of any kind with respect       de quelque nature que se soit,
- * to the software, including           concernant le logiciel, y com-
- * without limitation any war-          pris sans restriction toute
- * ranty of merchantability or          garantie de valeur marchande
- * fitness for a particular pur-        ou de pertinence pour un usage
- * pose.  NRC shall not be liable       particulier.  Le CNRC ne
- * in any event for any damages,        pourra en aucun cas etre tenu
- * whether direct or indirect,          responsable de tout dommage,
- * special or general, consequen-       direct ou indirect, particul-
- * tial or incidental, arising          ier ou general, accessoire ou
- * from the use of the software.        fortuit, resultant de l'utili-
- *                                      sation du logiciel.
+ *  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/>.
  *
- * @author adriand
- * 
- * @version $Revision: $
- * 
- * 
- ****  C A N A D I A N   A S T R O N O M Y   D A T A   C E N T R E  *****
  ************************************************************************
  */
 
@@ -61,20 +94,10 @@ import ca.nrc.cadc.ac.UserNotFoundException;
 import ca.nrc.cadc.util.Log4jInit;
 import static org.junit.Assert.assertNotNull;
 
-public class LdapGroupDAOTest
+public class LdapGroupDAOTest extends AbstractLdapDAOTest
 {
     private static final Logger log = Logger.getLogger(LdapGroupDAOTest.class);
     
-    static String server = "mach275.cadc.dao.nrc.ca";
-    static int port = 389;
-    static String adminDN = "uid=webproxy,ou=webproxy,ou=topologymanagement,o=netscaperoot";
-    static String adminPW = "go4it";
-    static String usersDN = "ou=Users,ou=ds,dc=canfartest,dc=net";
-    static String groupsDN = "ou=Groups,ou=ds,dc=canfartest,dc=net";
-    static String adminGroupsDN = "ou=adminGroups,ou=ds,dc=canfartest,dc=net";
-//    static String usersDN = "ou=Users,ou=ds,dc=canfar,dc=net";
-//    static String groupsDN = "ou=Groups,ou=ds,dc=canfar,dc=net";
-    
     static String daoTestDN1 = "cn=cadcdaotest1,ou=cadc,o=hia,c=ca";
     static String daoTestDN2 = "cn=cadcdaotest2,ou=cadc,o=hia,c=ca";
     static String daoTestDN3 = "cn=cadcdaotest3,ou=cadc,o=hia,c=ca";
@@ -84,37 +107,36 @@ public class LdapGroupDAOTest
     static X500Principal daoTestPrincipal2;
     static X500Principal daoTestPrincipal3;
     static X500Principal unknownPrincipal;
-    static X500Principal adminPrincipal;
-    
+
     static User<X500Principal> daoTestUser1;
     static User<X500Principal> daoTestUser2;
     static User<X500Principal> daoTestUser3;
     static User<X500Principal> unknownUser;
-    static User<X500Principal> adminUser;
-    
+
     static Subject daoTestUser1Subject;
     static Subject daoTestUser2Subject;
     static Subject anonSubject;
-    
+
     static LdapConfig config;
     
     @BeforeClass
     public static void setUpBeforeClass()
         throws Exception
     {
-        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.DEBUG);
+        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO);
+        
+        // get the configuration of the development server from and config files...
+        config = getLdapConfig();
         
         daoTestPrincipal1 = new X500Principal(daoTestDN1);
         daoTestPrincipal2 = new X500Principal(daoTestDN2);
         daoTestPrincipal3 = new X500Principal(daoTestDN3);
         unknownPrincipal = new X500Principal(unknownDN);
-        adminPrincipal = new X500Principal(adminDN);
 
         daoTestUser1 = new User<X500Principal>(daoTestPrincipal1);
         daoTestUser2 = new User<X500Principal>(daoTestPrincipal2);
         daoTestUser3 = new User<X500Principal>(daoTestPrincipal3);
         unknownUser = new User<X500Principal>(unknownPrincipal);
-        adminUser = new User<X500Principal>(adminPrincipal);
         
         daoTestUser1Subject = new Subject();
         daoTestUser1Subject.getPrincipals().add(daoTestUser1.getUserID());
@@ -124,8 +146,6 @@ public class LdapGroupDAOTest
         
         anonSubject = new Subject();
         anonSubject.getPrincipals().add(unknownUser.getUserID());
-    
-        config = new LdapConfig(server, port, adminDN, adminPW, usersDN, groupsDN, adminGroupsDN);
     }
 
     LdapGroupDAO<X500Principal> getGroupDAO()
@@ -185,12 +205,24 @@ public class LdapGroupDAOTest
                     expectGroup.getGroupMembers().remove(otherGroup);
                     actualGroup = getGroupDAO().modifyGroup(expectGroup);
                     assertGroupsEqual(expectGroup, actualGroup);
-                    
-                    // delete the group
+
                     expectGroup.description = "Happy testing";
                     expectGroup.getUserMembers().add(daoTestUser2);
                     expectGroup.getGroupMembers().add(otherGroup);
-                    
+
+                    // userAdmins
+                    expectGroup.getUserAdmins().add(daoTestUser3);
+                    actualGroup = getGroupDAO().modifyGroup(expectGroup);
+                    assertGroupsEqual(expectGroup, actualGroup);
+
+                    // groupAdmins
+                    Group adminGroup = new Group(getGroupID(), daoTestUser1);
+                    adminGroup = getGroupDAO().addGroup(adminGroup);
+                    expectGroup.getGroupAdmins().add(adminGroup);
+                    actualGroup = getGroupDAO().modifyGroup(expectGroup);
+                    assertGroupsEqual(expectGroup, actualGroup);
+
+                    // delete the group
                     getGroupDAO().deleteGroup(expectGroup.getID());
                     try
                     {
@@ -315,7 +347,7 @@ public class LdapGroupDAOTest
                     assertNotNull(groups);
                     assertTrue(groups.size() >= 2);
                     
-                    log.debug("# groups found: " + groups.size());
+                    log.debug("testSearchMemberGroups groups found: " + groups.size());
                     boolean found1 = false;
                     boolean found2 = false;
                     for (Group group : groups)
@@ -413,7 +445,7 @@ public class LdapGroupDAOTest
                             getGroupDAO().getGroups(daoTestUser2.getUserID(), 
                                                     Role.ADMIN, null);
                     
-                    log.debug("# groups found: " + groups.size());
+                    log.debug("testSearchAdminGroups groups found: " + groups.size());
                     assertNotNull(groups);
                     assertTrue(groups.size() >= 2);
                     
@@ -471,6 +503,97 @@ public class LdapGroupDAOTest
             }
         });
     }
+
+    @Test
+    public void testGetGroupNames() throws Exception
+    {
+        final String groupID = getGroupID();
+        final String testGroup1ID = groupID + ".1";
+        final String testGroup2ID = groupID + ".2";
+
+        Subject.doAs(daoTestUser1Subject, new PrivilegedExceptionAction<Object>()
+        {
+            public Object run() throws Exception
+            {
+                try
+                {
+                    Group testGroup1 = new Group(testGroup1ID, daoTestUser1);
+                    testGroup1 = getGroupDAO().addGroup(testGroup1);
+                    log.debug("add group: " + testGroup1ID);
+
+                    Group testGroup2 = new Group(testGroup2ID, daoTestUser1);
+                    testGroup2 = getGroupDAO().addGroup(testGroup2);
+                    log.debug("add group: " + testGroup2ID);
+                    //Thread.sleep(1000); // sleep to let memberof plugin do its work
+                }
+                catch (Exception e)
+                {
+                    throw new Exception("Problems", e);
+                }
+                return null;
+            }
+        });
+
+        Subject.doAs(daoTestUser1Subject, new PrivilegedExceptionAction<Object>()
+        {
+            public Object run() throws Exception
+            {
+                try
+                {
+                    Collection<String> groups = getGroupDAO().getGroupNames();
+
+                    log.debug("testGetGroupNames groups found: " + groups.size());
+                    assertNotNull(groups);
+                    assertTrue(groups.size() >= 2);
+
+                    boolean found1 = false;
+                    boolean found2 = false;
+                    for (String name : groups)
+                    {
+                        log.debug("group: " + name);
+                        if (name.equals(testGroup1ID))
+                        {
+                            found1 = true;
+                        }
+                        if (name.equals(testGroup2ID))
+                        {
+                            found2 = true;
+                        }
+                    }
+                    if (!found1)
+                    {
+                        fail("Admin group " + testGroup1ID + " not found");
+                    }
+                    if (!found2)
+                    {
+                        fail("Admin group " + testGroup2ID + " not found");
+                    }
+                }
+                catch (Exception e)
+                {
+                    throw new Exception("Problems", e);
+                }
+                return null;
+            }
+        });
+
+        Subject.doAs(daoTestUser1Subject, new PrivilegedExceptionAction<Object>()
+        {
+            public Object run() throws Exception
+            {
+                try
+                {
+                    getGroupDAO().deleteGroup(testGroup1ID);
+                    getGroupDAO().deleteGroup(testGroup2ID);
+                }
+                catch (Exception e)
+                {
+                    throw new Exception("Problems", e);
+                }
+                return null;
+            }
+        });
+    }
     
     @Test
     public void testAddGroupExceptions() throws Exception
@@ -570,23 +693,24 @@ public class LdapGroupDAOTest
                 return null;
             }
         });
-        
-        Subject.doAs(daoTestUser2Subject, new PrivilegedExceptionAction<Object>()
-        {
-            public Object run() throws Exception
-            {
-                try
-                {                    
-                    getGroupDAO().getGroup(groupID);
-                    fail("getGroup with anonymous access should throw " + 
-                         "AccessControlException");
-                }
-                catch (AccessControlException ignore) {}
-                return null;
-            }
-        });
+
+        // All access ACI's will allow anonymous access
+//        Subject.doAs(daoTestUser2Subject, new PrivilegedExceptionAction<Object>()
+//        {
+//            public Object run() throws Exception
+//            {
+//                try
+//                {
+//                    getGroupDAO().getGroup(groupID);
+//                    fail("getGroup with anonymous access should throw " +
+//                         "AccessControlException");
+//                }
+//                catch (AccessControlException ignore) {}
+//                return null;
+//            }
+//        });
     }
-    
+
     @Test
     public void testModifyGroupExceptions() throws Exception
     {        
@@ -699,7 +823,7 @@ public class LdapGroupDAOTest
                     fail("searchGroups with unknown user should throw " + 
                          "UserNotFoundException");
                 }
-                catch (UserNotFoundException ignore) {}
+                catch (AccessControlException ignore) {}
                 
                 try
                 {
@@ -728,38 +852,35 @@ public class LdapGroupDAOTest
                 return null;
             }
         });
-        
-        Subject.doAs(daoTestUser1Subject, new PrivilegedExceptionAction<Object>()
-        {
-            public Object run() throws Exception
-            {               
-                getGroupDAO().deleteGroup(groupID);
-                return null;
-            }
-        });
-        
-        
+
+        //
         // change the user
-        Subject.doAs(daoTestUser2Subject, new PrivilegedExceptionAction<Object>()
+//        Subject.doAs(daoTestUser2Subject, new PrivilegedExceptionAction<Object>()
+//        {
+//            public Object run() throws Exception
+//            {
+//                try
+//                {
+//                    Group group = getGroupDAO().getGroup(groupID);
+//                    assertTrue(group == null);
+//
+//                    fail("searchGroups with un-authorized user should throw " +
+//                         "AccessControlException");
+//                }
+//                catch (AccessControlException ignore)
+//                {
+//
+//                }
+//
+//                return null;
+//            }
+//        });
+
+        Subject.doAs(daoTestUser1Subject, new PrivilegedExceptionAction<Object>()
         {
-            
             public Object run() throws Exception
             {
-
-                
-                try
-                {
-                    Group group = getGroupDAO().getGroup(groupID);
-                    assertTrue(group == null);
-                    
-                    fail("searchGroups with un-authorized user should throw " + 
-                         "AccessControlException");
-                }
-                catch (AccessControlException ignore) 
-                {
-
-                }
-
+                getGroupDAO().deleteGroup(groupID);
                 return null;
             }
         });
@@ -775,12 +896,14 @@ public class LdapGroupDAOTest
         assertEquals(gr1.getID(), gr2.getID());
         assertEquals(gr1.description, gr2.description);
         assertEquals(gr1.getOwner(), gr2.getOwner());
+
         assertEquals(gr1.getGroupMembers(), gr2.getGroupMembers());
         assertEquals(gr1.getGroupMembers().size(), gr2.getGroupMembers().size());
         for (Group gr : gr1.getGroupMembers())
         {
             assertTrue(gr2.getGroupMembers().contains(gr));
         }
+
         assertEquals(gr1.getUserMembers(), gr2.getUserMembers());
         assertEquals(gr1.getUserMembers().size(), gr2.getUserMembers()
                 .size());
@@ -788,6 +911,22 @@ public class LdapGroupDAOTest
         {
             assertTrue(gr2.getUserMembers().contains(user));
         }
+
+        assertEquals(gr1.getGroupAdmins(), gr2.getGroupAdmins());
+        assertEquals(gr1.getGroupAdmins().size(), gr2.getGroupAdmins().size());
+        for (Group gr : gr1.getGroupAdmins())
+        {
+            assertTrue(gr2.getGroupAdmins().contains(gr));
+        }
+
+        assertEquals(gr1.getUserAdmins(), gr2.getUserAdmins());
+        assertEquals(gr1.getUserAdmins().size(), gr2.getUserAdmins()
+                .size());
+        for (User<?> user : gr1.getUserAdmins())
+        {
+            assertTrue(gr2.getUserAdmins().contains(user));
+        }
+
         assertEquals(gr1.getProperties(), gr2.getProperties());
         for (GroupProperty prop : gr1.getProperties())
         {
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
index 6bda5b89871e4aaa8bbd6ffe71f3dc56cfa6e809..abce3f7810d0941c878c2ea03a98de9c093dfc00 100644
--- 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
@@ -85,38 +85,21 @@ import org.apache.log4j.Logger;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import ca.nrc.cadc.ac.Group;
 import ca.nrc.cadc.ac.PersonalDetails;
 import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.UserDetails;
 import ca.nrc.cadc.auth.HttpPrincipal;
-import ca.nrc.cadc.auth.NumericPrincipal;
 import ca.nrc.cadc.util.Log4jInit;
 
 import com.unboundid.ldap.sdk.DN;
 
-/**
- *
- * @author jburke
- */
-public class LdapUserDAOTest
+public class LdapUserDAOTest extends AbstractLdapDAOTest
 {
     private static final Logger log = Logger.getLogger(LdapUserDAOTest.class);
-    
-    static String server = "mach275.cadc.dao.nrc.ca";
-    static int port = 389;
-    static String adminDN = "uid=webproxy,ou=Webproxy,ou=topologymanagement,o=netscaperoot";
-    static String adminPW = "go4it";
-    static String usersDN = "ou=Users,ou=ds,dc=canfartest,dc=net";
-    static String groupsDN = "ou=Groups,ou=ds,dc=canfartest,dc=net";
-    static String adminGroupsDN = "ou=adminGroups,ou=ds,dc=canfartest,dc=net";
-//    static String userBaseDN = "ou=Users,ou=ds,dc=canfar,dc=net";
-//    static String groupBaseDN = "ou=Groups,ou=ds,dc=canfar,dc=net";
-    
+
     static final String testUserX509DN = "cn=cadcdaotest1,ou=cadc,o=hia,c=ca";
-    static final String testUserDN = "uid=cadcdaotest1," + usersDN;
-    
-    
+
+    static String testUserDN;
     static User<X500Principal> testUser;
     static LdapConfig config;
     
@@ -124,14 +107,16 @@ public class LdapUserDAOTest
     public static void setUpBeforeClass()
         throws Exception
     {
-        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.DEBUG);
-        
+        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO);
+
+        // get the configuration of the development server from and config files...
+        config = getLdapConfig();
+
         testUser = new User<X500Principal>(new X500Principal(testUserX509DN));
-    
-        config = new LdapConfig(server, port, adminDN, adminPW, usersDN, groupsDN, adminGroupsDN);
-        
         testUser.details.add(new PersonalDetails("CADC", "DAOTest1"));
-        testUser.getIdentities().add(new HttpPrincipal("CadcDaoTest1"));        
+        testUser.getIdentities().add(new HttpPrincipal("CadcDaoTest1"));
+
+        testUserDN = "uid=cadcdaotest1," + config.getUsersDN();
     }
 
     LdapUserDAO<X500Principal> getUserDAO()
@@ -226,7 +211,7 @@ public class LdapUserDAOTest
                     boolean isMember = getUserDAO().isMember(testUser.getUserID(), "foo");
                     assertFalse(isMember);
                     
-                    String groupDN = "cn=cadcdaotestgroup1,ou=groups,ou=ds,dc=canfartest,dc=net";
+                    String groupDN = "cn=cadcdaotestgroup1," + config.getGroupsDN();
                     isMember = getUserDAO().isMember(testUser.getUserID(), groupDN);
                     assertTrue(isMember);
                     
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GetGroupNamesActionTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GetGroupNamesActionTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0cb59383950ce877c3c5c88a4ee1da506adecce7
--- /dev/null
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GetGroupNamesActionTest.java
@@ -0,0 +1,159 @@
+/*
+ ************************************************************************
+ *******************  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.web;
+
+import ca.nrc.cadc.ac.server.GroupPersistence;
+import ca.nrc.cadc.util.Log4jInit;
+import ca.nrc.cadc.uws.server.SyncOutput;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.easymock.EasyMock;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.PrintWriter;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import static org.junit.Assert.fail;
+
+/**
+ *
+ * @author jburke
+ */
+public class GetGroupNamesActionTest
+{
+    private final static Logger log = Logger.getLogger(GetGroupNamesActionTest.class);
+
+    @BeforeClass
+    public static void setUpClass()
+    {
+        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO);
+    }
+
+    @Test
+    @Ignore
+    public void testRun() throws Exception
+    {
+        try
+        {
+            Collection<String> groupNames = new ArrayList<String>();
+            groupNames.add("foo");
+            groupNames.add("bar");
+
+            final GroupPersistence mockPersistence = EasyMock.createMock(GroupPersistence.class);
+            EasyMock.expect(mockPersistence.getGroupNames()).andReturn(groupNames).once();
+
+            final PrintWriter mockWriter = EasyMock.createMock(PrintWriter.class);
+            mockWriter.write("foo", 0, 3);
+            EasyMock.expectLastCall();
+            mockWriter.write(44);
+            EasyMock.expectLastCall();
+            mockWriter.write("bar", 0, 3);
+            EasyMock.expectLastCall();
+            mockWriter.write("\n");
+            EasyMock.expectLastCall();
+
+            final SyncOutput mockSyncOutput =
+                    EasyMock.createMock(SyncOutput.class);
+
+            mockSyncOutput.setHeader("Content-Type", "text/csv");
+
+            final HttpServletResponse mockResponse = EasyMock.createMock(HttpServletResponse.class);
+            mockResponse.setContentType("text/csv");
+            EasyMock.expectLastCall();
+            EasyMock.expect(mockResponse.getWriter()).andReturn(mockWriter).once();
+
+            GroupLogInfo mockLog = EasyMock.createMock(GroupLogInfo.class);
+
+            EasyMock.replay(mockPersistence, mockWriter, mockResponse, mockLog);
+
+            GetGroupNamesAction action = new GetGroupNamesAction(mockLog)
+            {
+                @Override
+                <T extends Principal> GroupPersistence<T> getGroupPersistence()
+                {
+                    return mockPersistence;
+                };
+            };
+
+            action.run();
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+
+}
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GroupActionFactoryTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GroupActionFactoryTest.java
index 22df7faa1794f7970cc14064ca050b3a7e9584a7..f62d030cc6b0f9131a9681250664fbee8a97444c 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GroupActionFactoryTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GroupActionFactoryTest.java
@@ -77,6 +77,8 @@ import org.junit.Test;
 
 import ca.nrc.cadc.util.Log4jInit;
 
+import java.net.URL;
+
 public class GroupActionFactoryTest
 {
     private final static Logger log = Logger.getLogger(GroupActionFactoryTest.class);
@@ -189,7 +191,7 @@ public class GroupActionFactoryTest
     }
 
     @Test
-    public void testCreateListGroupsAction()
+    public void testCreateGetGroupNamesAction()
     {
         try
         {
@@ -199,7 +201,7 @@ public class GroupActionFactoryTest
             EasyMock.replay(request);
             GroupsAction action = GroupsActionFactory.getGroupsAction(request, null);
             EasyMock.verify(request);
-            Assert.assertTrue("Wrong action", action instanceof ListGroupsAction);
+            Assert.assertTrue("Wrong action", action instanceof GetGroupNamesAction);
         }
         catch (Throwable t)
         {
@@ -213,10 +215,15 @@ public class GroupActionFactoryTest
     {
         try
         {
+            StringBuffer sb = new StringBuffer();
+            sb.append("http://localhost:80/ac/groups/foo");
+
             HttpServletRequest request = EasyMock.createMock(HttpServletRequest.class);
             EasyMock.expect(request.getPathInfo()).andReturn("groupName");
             EasyMock.expect(request.getMethod()).andReturn("POST");
-            EasyMock.expect(request.getRequestURI()).andReturn(null);
+            EasyMock.expect(request.getRequestURL()).andReturn(sb);
+            EasyMock.expect(request.getContextPath()).andReturn("");
+            EasyMock.expect(request.getServletPath()).andReturn("");
             EasyMock.expect(request.getInputStream()).andReturn(null);
             EasyMock.replay(request);
             GroupsAction action = GroupsActionFactory.getGroupsAction(request, null);
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GroupsActionTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GroupsActionTest.java
index d7b46ae5f7c7efc6f1d8e46abe0c21d73fe513a0..08b28e9725675f4d955688e194198862144b9f42 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GroupsActionTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GroupsActionTest.java
@@ -75,14 +75,19 @@ import ca.nrc.cadc.ac.MemberNotFoundException;
 import ca.nrc.cadc.ac.UserNotFoundException;
 import ca.nrc.cadc.net.TransientException;
 import ca.nrc.cadc.util.Log4jInit;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.security.AccessControlException;
+import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletResponse;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 import org.easymock.EasyMock;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 import static org.junit.Assert.*;
 
@@ -144,7 +149,7 @@ public class GroupsActionTest
         Exception e = new UserNotFoundException("foo");
         testDoAction(message, responseCode, e);
     }
-    
+
     @Test
     public void testDoActionMemberAlreadyExistsException() throws Exception
     {
@@ -186,14 +191,14 @@ public class GroupsActionTest
             response.setStatus(503);
             EasyMock.expectLastCall().once();
             EasyMock.replay(response);
-            
+
             GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
             logInfo.setSuccess(false);
             EasyMock.expectLastCall().once();
             logInfo.setMessage("Internal Transient Error: foo");
             EasyMock.expectLastCall().once();
             EasyMock.replay(logInfo);
-            
+
             GroupsActionImpl action = new GroupsActionImpl(logInfo);
             action.setException(new TransientException("foo"));
             action.doAction(null, response);
@@ -205,7 +210,8 @@ public class GroupsActionTest
         }
     }
     
-    private void testDoAction(String message, int responseCode, Exception e) throws Exception
+    private void testDoAction(String message, int responseCode, Exception e)
+        throws Exception
     {
         try
         {
@@ -218,12 +224,12 @@ public class GroupsActionTest
             response.setStatus(responseCode);
             EasyMock.expectLastCall().once();
             EasyMock.replay(response);
-            
+
             GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
             logInfo.setMessage(message);
             EasyMock.expectLastCall().once();
             EasyMock.replay(logInfo);
-            
+
             GroupsActionImpl action = new GroupsActionImpl(logInfo);
             action.setException(e);
             action.doAction(null, response);
diff --git a/projects/cadcAccessControl/build.xml b/projects/cadcAccessControl/build.xml
index 7b1f362a954dde0e97f2e3273e512eee538ae873..a3b964ed3878c53e9c2aec9a667ebce0cc72ca79 100644
--- a/projects/cadcAccessControl/build.xml
+++ b/projects/cadcAccessControl/build.xml
@@ -91,11 +91,12 @@
 	<property name="cadcRegistryClient" value="${lib}/cadcRegistryClient.jar" />
     
     <property name="jdom2"      value="${ext.lib}/jdom2.jar" />
+    <property name="javacsv"      value="${ext.lib}/javacsv.jar" />
     <property name="log4j"      value="${ext.lib}/log4j.jar" />
     <property name="unboundid"  value="${ext.lib}/unboundid-ldapsdk-se.jar" />
 
 
-    <property name="jars" value="${cadcUtil}:${cadcRegistryClient}:${jdom2}:${log4j}:${unboundid}" />
+    <property name="jars" value="${jdom2}:${log4j}:${javacsv}:${unboundid}:${cadcUtil}:${cadcRegistryClient}" />
     
     <target name="build" depends="compile">
         <jar jarfile="${build}/lib/${project}.jar"
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/AC.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/AC.java
index afbf97f64dd5d701898d16acf821a572b58d9e4a..cacb9a00c2efacc78ee92aa9d23ed35449fe38d9 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/AC.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/AC.java
@@ -85,7 +85,7 @@ public class AC
     // Denotes a group readable by public
     public static final String PROPERTY_PUBLIC = "ivo://ivoa.net/gms#public";
     
-    public static final String GMS_SERVICE_URI = "ivo://cadc.nrc.ca/gms";
+    public static final String GMS_SERVICE_URI = "ivo://cadc.nrc.ca/canfargms";
     
     // Group URI attribute once the group name is appended
     public static final String GROUP_URI = "ivo://cadc.nrc.ca/gms#";
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/GroupReader.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/GroupReader.java
index 8061f48773eeb3913a4b8020aba06af37e703fed..70ef1830f10432669f5b0bbd27be53286fa8d2b4 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/GroupReader.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/GroupReader.java
@@ -119,7 +119,7 @@ public class GroupReader
      * @throws java.net.URISyntaxException
      */
     public static Group read(InputStream in)
-        throws ReaderException, IOException, URISyntaxException
+        throws ReaderException, IOException
     {
         if (in == null)
         {
@@ -147,7 +147,7 @@ public class GroupReader
      * @throws java.net.URISyntaxException
      */
     public static Group read(Reader reader)
-        throws ReaderException, IOException, URISyntaxException
+        throws ReaderException, IOException
     {
         if (reader == null)
         {
@@ -179,7 +179,7 @@ public class GroupReader
     }
 
     protected static Group parseGroup(Element groupElement)
-        throws URISyntaxException, ReaderException
+        throws ReaderException
     {
         String uri = groupElement.getAttributeValue("uri");
         if (uri == null)
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java
index 6f1489c9f4999d2fcef6409d95c563622c3d18dc..7609ee7326133d0d2975059fdd7666c0e3435f0c 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java
@@ -68,9 +68,12 @@
  */
 package ca.nrc.cadc.ac.client;
 
+import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -79,6 +82,7 @@ import java.security.AccessControlContext;
 import java.security.AccessControlException;
 import java.security.AccessController;
 import java.security.Principal;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -104,8 +108,10 @@ import ca.nrc.cadc.auth.SSLUtil;
 import ca.nrc.cadc.net.HttpDownload;
 import ca.nrc.cadc.net.HttpPost;
 import ca.nrc.cadc.net.HttpUpload;
+import ca.nrc.cadc.net.InputStreamWrapper;
 import ca.nrc.cadc.net.NetUtil;
 
+
 /**
  * Client class for performing group searching and group actions
  * with the access control web service.
@@ -115,7 +121,8 @@ public class GMSClient
     private static final Logger log = Logger.getLogger(GMSClient.class);
     
     // socket factory to use when connecting
-    public SSLSocketFactory sslSocketFactory;
+    private SSLSocketFactory sslSocketFactory;
+    private SSLSocketFactory mySocketFactory;
     
     private String baseURL;
 
@@ -134,12 +141,7 @@ public class GMSClient
         }
         try
         {
-            URL testURL = new URL(baseURL);
-            if (!testURL.getProtocol().equals("https"))
-            {
-                throw new IllegalArgumentException(
-                        "URL must have HTTPS protocol");
-            }
+            new URL(baseURL);
         }
         catch (MalformedURLException e)
         {
@@ -229,7 +231,7 @@ public class GMSClient
         String retXML = transfer.getResponseBody();
         try
         {
-            log.debug("createGroup returned: " + groupXML);
+            log.debug("createGroup returned: " + retXML);
             return GroupReader.read(retXML);
         }
         catch (Exception bug)
@@ -262,7 +264,7 @@ public class GMSClient
         Throwable error = transfer.getThrowable();
         if (error != null)
         {
-            log.debug("getGroup throwable", error);
+            log.debug("getGroup throwable (" + transfer.getResponseCode() + ")", error);
             // transfer returns a -1 code for anonymous access.
             if ((transfer.getResponseCode() == -1) || 
                 (transfer.getResponseCode() == 401) || 
@@ -293,6 +295,74 @@ public class GMSClient
             throw new RuntimeException(bug);
         }
     }
+    
+    /**
+     * Get the all group names.
+     *
+     * @return The list of names.
+     * @throws AccessControlException If unauthorized to perform this operation.
+     * @throws java.io.IOException
+     */
+    public List<String> getGroupNames()
+        throws AccessControlException, IOException
+    {
+        final URL getGroupNamesURL = new URL(this.baseURL + "/groups");
+        log.debug("getGroupNames request to " + getGroupNamesURL.toString());
+
+        final List<String> groupNames = new ArrayList<String>();
+        final HttpDownload httpDownload =
+                new HttpDownload(getGroupNamesURL, new InputStreamWrapper()
+        {
+            @Override
+            public void read(final InputStream inputStream) throws IOException
+            {
+                try
+                {
+                    InputStreamReader inReader = new InputStreamReader(inputStream);
+                    BufferedReader reader = new BufferedReader(inReader);
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        groupNames.add(line);
+                    }
+                }
+                catch (Exception bug)
+                {
+                    log.error("Unexpected exception", bug);
+                    throw new RuntimeException(bug);
+                }
+            }
+        });
+
+        httpDownload.setSSLSocketFactory(getSSLSocketFactory());
+        httpDownload.run();
+
+        final Throwable error = httpDownload.getThrowable();
+
+        if (error != null)
+        {
+            final String errMessage = error.getMessage();
+            final int responseCode = httpDownload.getResponseCode();
+
+            log.debug("getGroupNames response " + responseCode + ": " +
+                      errMessage);
+
+            if ((responseCode == 401) || (responseCode == 403) || 
+                    (responseCode == -1))
+            {
+                throw new AccessControlException(errMessage);
+            }
+            if (responseCode == 400)
+            {
+                throw new IllegalArgumentException(errMessage);
+            }
+            throw new IOException("HttpResponse (" + responseCode + ") - " + errMessage);
+        }
+
+        log.debug("Content-Length: " + httpDownload.getContentLength());
+        log.debug("Content-Type: " + httpDownload.getContentType());
+
+        return groupNames;
+    }
 
     /**
      * Update a group.
@@ -301,11 +371,11 @@ public class GMSClient
      * @return The group after update.
      * @throws IllegalArgumentException If cyclical membership is detected.
      * @throws GroupNotFoundException If the group was not found.
-     * @throws GroupNotFoundException If a member was not found.
+     * @throws UserNotFoundException If a member was not found.
      * @throws AccessControlException If unauthorized to perform this operation.
      * @throws java.io.IOException
      */
-    public Group updateGroup(Group group)
+    public void updateGroup(Group group)
         throws IllegalArgumentException, GroupNotFoundException, UserNotFoundException,
                AccessControlException, IOException
     {
@@ -320,7 +390,7 @@ public class GMSClient
         log.debug("updateGroup: " + groupXML);
 
         HttpPost transfer = new HttpPost(updateGroupURL, groupXML.toString(), 
-                                         "application/xml", true);
+                                         "application/xml", false);
 
         transfer.setSSLSocketFactory(getSSLSocketFactory());
         transfer.run();
@@ -328,11 +398,6 @@ public class GMSClient
         Throwable error = transfer.getThrowable();
         if (error != null)
         {
-            log.debug("updateGroup throwable", error);
-            if (transfer.getResponseCode() == 302)
-            {
-                return getGroup(group.getID());
-            }
             // transfer returns a -1 code for anonymous access.
             if ((transfer.getResponseCode() == -1) || 
                 (transfer.getResponseCode() == 401) || 
@@ -353,18 +418,6 @@ public class GMSClient
             }
             throw new IOException(error);
         }
-
-        String retXML = transfer.getResponseBody();
-        try
-        {
-            log.debug("updateGroup returned: " + groupXML);
-            return GroupReader.read(retXML);
-        }
-        catch (Exception bug)
-        {
-            log.error("Unexpected exception", bug);
-            throw new RuntimeException(bug);
-        }
     }
 
     /**
@@ -383,7 +436,7 @@ public class GMSClient
         
         // reset the state of the cache
         clearCache();
-        
+
         HttpURLConnection conn = 
                 (HttpURLConnection) deleteGroupURL.openConnection();
         conn.setRequestMethod("DELETE");
@@ -394,7 +447,9 @@ public class GMSClient
             ((HttpsURLConnection) conn)
                     .setSSLSocketFactory(sf);
         }
-        int responseCode = -1;
+
+        final int responseCode;
+
         try
         {
             responseCode = conn.getResponseCode();
@@ -449,30 +504,17 @@ public class GMSClient
         // reset the state of the cache
         clearCache();
 
-        HttpURLConnection conn = 
-                (HttpURLConnection) addGroupMemberURL.openConnection();
-        conn.setRequestMethod("PUT");
+        final InputStream is = new ByteArrayInputStream(new byte[0]);
+        final HttpUpload httpUpload = new HttpUpload(is, addGroupMemberURL);
 
-        SSLSocketFactory sf = getSSLSocketFactory();
-        if ((sf != null) && ((conn instanceof HttpsURLConnection)))
-        {
-            ((HttpsURLConnection) conn)
-                    .setSSLSocketFactory(getSSLSocketFactory());
-        }
-        
-        // Try to handle anonymous access and throw AccessControlException 
-        int responseCode = -1;
-        try
-        {
-            responseCode = conn.getResponseCode();
-        }
-        catch (Exception ignore) {}
-    
-        if ((responseCode != 200) && (responseCode != 201))
+        httpUpload.setSSLSocketFactory(getSSLSocketFactory());
+        httpUpload.run();
+
+        final Throwable error = httpUpload.getThrowable();
+        if (error != null)
         {
-            String errMessage = NetUtil.getErrorBody(conn);
-            log.debug("addGroupMember response " + responseCode + ": " + 
-                      errMessage);
+            final int responseCode = httpUpload.getResponseCode();
+            final String errMessage = error.getMessage();
 
             if ((responseCode == -1) || 
                 (responseCode == 401) || 
@@ -498,7 +540,7 @@ public class GMSClient
      * @param targetGroupName The group in which to add the group member.
      * @param userID The user to add.
      * @throws GroupNotFoundException If the group was not found.
-     * @throws GroupNotFoundException If the member was not found.
+     * @throws UserNotFoundException If the member was not found.
      * @throws java.io.IOException
      * @throws AccessControlException If unauthorized to perform this operation.
      */
@@ -506,7 +548,7 @@ public class GMSClient
         throws GroupNotFoundException, UserNotFoundException, AccessControlException, IOException
     {
         String userIDType = AuthenticationUtil.getPrincipalType(userID);
-        String encodedUserID = URLEncoder.encode(userID.toString(), "UTF-8");
+        String encodedUserID = URLEncoder.encode(userID.getName(), "UTF-8");
         URL addUserMemberURL = new URL(this.baseURL + "/groups/" + 
                                        targetGroupName + "/userMembers/" + 
                                        encodedUserID + "?idType=" + userIDType);
@@ -516,30 +558,17 @@ public class GMSClient
         // reset the state of the cache
         clearCache();
 
-        HttpURLConnection conn = 
-                (HttpURLConnection) addUserMemberURL.openConnection();
-        conn.setRequestMethod("PUT");
+        final InputStream is = new ByteArrayInputStream(new byte[0]);
+        final HttpUpload httpUpload = new HttpUpload(is, addUserMemberURL);
 
-        SSLSocketFactory sf = getSSLSocketFactory();
-        if ((sf != null) && ((conn instanceof HttpsURLConnection)))
-        {
-            ((HttpsURLConnection) conn)
-                    .setSSLSocketFactory(getSSLSocketFactory());
-        }
-        
-        // Try to handle anonymous access and throw AccessControlException 
-        int responseCode = -1;
-        try
-        {
-            responseCode = conn.getResponseCode();
-        }
-        catch (Exception ignore) {}
+        httpUpload.setSSLSocketFactory(getSSLSocketFactory());
+        httpUpload.run();
 
-        if ((responseCode != 200) && (responseCode != 201))
+        final Throwable error = httpUpload.getThrowable();
+        if (error != null)
         {
-            String errMessage = NetUtil.getErrorBody(conn);
-            log.debug("addUserMember response " + responseCode + ": " + 
-                      errMessage);
+            final int responseCode = httpUpload.getResponseCode();
+            final String errMessage = error.getMessage();
 
             if ((responseCode == -1) || 
                 (responseCode == 401) || 
@@ -647,7 +676,7 @@ public class GMSClient
                                           encodedUserID + "?idType=" + 
                                           userIDType);
 
-        log.debug("removeUserMember request to " + 
+        log.debug("removeUserMember request to " +
                   removeUserMemberURL.toString());
         
         // reset the state of the cache
@@ -731,9 +760,11 @@ public class GMSClient
         StringBuilder searchGroupURL = new StringBuilder(this.baseURL);
         searchGroupURL.append("/search?");
         
-        searchGroupURL.append("ID=" + URLEncoder.encode(id, "UTF-8"));
-        searchGroupURL.append("&IDTYPE=" + URLEncoder.encode(idType, "UTF-8"));
-        searchGroupURL.append("&ROLE=" + URLEncoder.encode(roleString, "UTF-8"));
+        searchGroupURL.append("ID=").append(URLEncoder.encode(id, "UTF-8"));
+        searchGroupURL.append("&IDTYPE=")
+                .append(URLEncoder.encode(idType, "UTF-8"));
+        searchGroupURL.append("&ROLE=")
+                .append(URLEncoder.encode(roleString, "UTF-8"));
         
         log.debug("getMemberships request to " + searchGroupURL.toString());
         ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -826,7 +857,7 @@ public class GMSClient
         List<Group> cachedGroups = getCachedGroups(userID, role);
         if (cachedGroups != null)
         {
-            int index = cachedGroups.indexOf(groupName);
+            int index = cachedGroups.indexOf(new Group(groupName));
             if (index != -1)
             {
                 return cachedGroups.get(index);
@@ -843,11 +874,14 @@ public class GMSClient
         
         StringBuilder searchGroupURL = new StringBuilder(this.baseURL);
         searchGroupURL.append("/search?");
-        
-        searchGroupURL.append("ID=" + URLEncoder.encode(id, "UTF-8"));
-        searchGroupURL.append("&IDTYPE=" + URLEncoder.encode(idType, "UTF-8"));
-        searchGroupURL.append("&ROLE=" + URLEncoder.encode(roleString, "UTF-8"));
-        searchGroupURL.append("&GROUPID=" + URLEncoder.encode(groupName, "UTF-8"));
+
+        searchGroupURL.append("ID=").append(URLEncoder.encode(id, "UTF-8"));
+        searchGroupURL.append("&IDTYPE=")
+                .append(URLEncoder.encode(idType, "UTF-8"));
+        searchGroupURL.append("&ROLE=")
+                .append(URLEncoder.encode(roleString, "UTF-8"));
+        searchGroupURL.append("&GROUPID=")
+                .append(URLEncoder.encode(groupName, "UTF-8"));
         
         log.debug("getMembership request to " + searchGroupURL.toString());
         ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -947,23 +981,41 @@ public class GMSClient
      */
     public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory)
     {
+        if (mySocketFactory != null)
+            throw new IllegalStateException("Illegal use of GMSClient: "
+                    + "cannot set SSLSocketFactory after using one created from Subject");
         this.sslSocketFactory = sslSocketFactory;
+        clearCache();
     }
     
-    /**
-     * @return the sslSocketFactory
-     */
+    private int subjectHashCode = 0;
     private SSLSocketFactory getSSLSocketFactory()
     {
-        if (this.sslSocketFactory == null)
+        AccessControlContext ac = AccessController.getContext();
+        Subject s = Subject.getSubject(ac);
+        
+        // no real Subject: can only use the one from setSSLSocketFactory
+        if (s == null || s.getPrincipals().isEmpty())
+        {
+            return sslSocketFactory;
+        }
+        
+        // lazy init
+        if (this.mySocketFactory == null)
+        {
+            log.debug("getSSLSocketFactory: " + s);
+            this.mySocketFactory = SSLUtil.getSocketFactory(s);
+            this.subjectHashCode = s.hashCode();
+        }
+        else
         {
-            log.debug("initHTTPS: lazy init");
-            AccessControlContext ac = AccessController.getContext();
-            Subject s = Subject.getSubject(ac);
-            this.sslSocketFactory = SSLUtil.getSocketFactory(s);
-            log.debug("Socket Factory: " + this.sslSocketFactory);
+            int c = s.hashCode();
+            if (c != subjectHashCode)
+                throw new IllegalStateException("Illegal use of " 
+                        + this.getClass().getSimpleName()
+                        + ": subject change not supported for internal SSLSocketFactory");
         }
-        return this.sslSocketFactory;
+        return this.mySocketFactory;
     }
     
     protected void clearCache()
@@ -1008,7 +1060,7 @@ public class GMSClient
         {
             log.debug("Caching groups for " + userID + ", role " + role);
             
-            GroupMemberships groupCredentials = null;
+            final GroupMemberships groupCredentials;
             Set groupCredentialSet = subject.getPrivateCredentials(GroupMemberships.class);
             if ((groupCredentialSet != null) && 
                 (groupCredentialSet.size() == 1))
@@ -1033,13 +1085,9 @@ public class GMSClient
             return false;
         }
         
-        Set<Principal> subjectPrincipals = subject.getPrincipals();
-        Iterator<Principal> i = subjectPrincipals.iterator();
-        Principal next = null;
-        while (i.hasNext())
+        for (Principal subjectPrincipal : subject.getPrincipals())
         {
-            next = i.next();
-            if (next.equals(userID))
+            if (subjectPrincipal.equals(userID))
             {
                 return true;
             }