diff --git a/projects/cadcAccessControl-Server/build.xml b/projects/cadcAccessControl-Server/build.xml
index 6799d81d742f9b12258fb3fe5266fba621e2b928..7659e948fe30cb67e744b38dc800908278c5ff13 100644
--- a/projects/cadcAccessControl-Server/build.xml
+++ b/projects/cadcAccessControl-Server/build.xml
@@ -93,14 +93,15 @@
     <property name="cadcRegistry"        value="${lib}/cadcRegistryClient.jar" />
     <property name="cadcUtil"            value="${lib}/cadcUtil.jar" />
     <property name="cadcUWS"             value="${lib}/cadcUWS.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="${cadcAccessControl}:${cadcLog}:${cadcRegistry}:${cadcUtil}:${cadcUWS}:${javacsv}:${jdom2}:${log4j}:${servlet}:${unboundid}:${xerces}" />
 
     <target name="build" depends="compile">
         <jar jarfile="${build}/lib/${project}.jar"
@@ -130,7 +131,7 @@
         </copy>
     </target>
     
-<!--    <target name="test" depends="compile-test,resources">
+    <target name="test" depends="compile,compile-test,resources">
         <echo message="Running test suite..." />
         <junit printsummary="yes" haltonfailure="yes" fork="yes">
             <classpath>
@@ -138,9 +139,9 @@
                 <pathelement path="${build}/test/class"/>
                 <pathelement path="${testingJars}"/>
             </classpath>
-            <test name="ca.nrc.cadc.ac.server.ldap.LdapDAOTest" />
+            <test name="ca.nrc.cadc.ac.server.ldap.LdapGroupDAOTest" />
             <formatter type="plain" usefile="false" />
         </junit>
-    </target>-->
+    </target>
 
 </project>
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..7637d7281f81860117e0f9d4157b3579bf51f039 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.
      *
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..772bb7f6bc2bc22268f8fa20d35c0f581d95ac9a 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;
@@ -214,7 +215,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));
         }
@@ -305,6 +306,57 @@ 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, AccessControlException
+    {
+        try
+        {
+            Filter filter = Filter.createEqualityFilter("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("Count not find groups root", e);
+                    throw new IllegalStateException("Count not find groups root");
+                }
+            }
+            
+            LdapDAO.checkLdapResult(searchResult.getResultCode());
+            List<String> groupNames = new ArrayList<String>();
+            for (SearchResultEntry next : searchResult.getSearchEntries())
+            {
+                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.
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..66727c6e5969dde414d01855feda403560ba99d8 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
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..04f2b12e968b4ee2566101cb35a1f124bd43c79a 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
@@ -73,6 +73,8 @@ import ca.nrc.cadc.ac.server.GroupPersistence;
 
 import com.csvreader.CsvWriter;
 
+import javax.servlet.http.HttpServletResponse;
+
 public class GetGroupNamesAction extends GroupsAction
 {
 
@@ -86,9 +88,9 @@ public class GetGroupNamesAction extends GroupsAction
     {
         GroupPersistence groupPersistence = getGroupPersistence();
         Collection<String> groups = groupPersistence.getGroupNames();
-        response.setContentType("text/csv");
+        getHttpServletResponse().setContentType("text/csv");
         
-        CsvWriter writer = new CsvWriter(response.getWriter(), ',');
+        CsvWriter writer = new CsvWriter(getHttpServletResponse().getWriter(), ',');
         
         for (String group : groups)
         {
@@ -98,4 +100,9 @@ public class GetGroupNamesAction extends GroupsAction
         return null;
     }
 
+    protected HttpServletResponse getHttpServletResponse()
+    {
+        return this.response;
+    }
+
 }
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..d776f56ce827c6ef8fb86e5ec4f862a226104179 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,15 @@
  */
 package ca.nrc.cadc.ac.server.web;
 
-import ca.nrc.cadc.util.StringUtil;
 import java.io.IOException;
 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);
@@ -109,7 +112,7 @@ public class GroupsActionFactory
         {
             if (method.equals("GET"))
             {
-                action = new ListGroupsAction(logInfo);
+                action = new GetGroupNamesAction(logInfo);
             }
             else if (method.equals("PUT"))
             {
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..e5a6b024aacab8d783891784d88b53fb8085a08e 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
@@ -315,7 +315,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 +413,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 +471,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(daoTestUser2Subject, 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 +661,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
     {        
@@ -728,38 +820,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;
             }
         });
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..91bfea29d75ba6a2c19d4bc3178b40f4d79f816c
--- /dev/null
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GetGroupNamesActionTest.java
@@ -0,0 +1,156 @@
+/*
+ ************************************************************************
+ *******************  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 org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.easymock.EasyMock;
+import org.junit.BeforeClass;
+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
+    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 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;
+                };
+
+                @Override
+                protected HttpServletResponse getHttpServletResponse()
+                {
+                    return mockResponse;
+                }
+            };
+
+            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..849d4d718f5a62098284068ad12e76623c388662 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
@@ -189,7 +189,7 @@ public class GroupActionFactoryTest
     }
 
     @Test
-    public void testCreateListGroupsAction()
+    public void testCreateGetGroupNamesAction()
     {
         try
         {
@@ -199,7 +199,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)
         {
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/client/GMSClient.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java
index 6f1489c9f4999d2fcef6409d95c563622c3d18dc..8821068e465bbab3f66aff2a9364e24c3591a0d0 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.InputStreamReader;
+import java.io.Reader;
 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;
@@ -106,6 +110,8 @@ import ca.nrc.cadc.net.HttpPost;
 import ca.nrc.cadc.net.HttpUpload;
 import ca.nrc.cadc.net.NetUtil;
 
+import com.csvreader.CsvReader;
+
 /**
  * Client class for performing group searching and group actions
  * with the access control web service.
@@ -135,11 +141,11 @@ public class GMSClient
         try
         {
             URL testURL = new URL(baseURL);
-            if (!testURL.getProtocol().equals("https"))
-            {
-                throw new IllegalArgumentException(
-                        "URL must have HTTPS protocol");
-            }
+//            if (!testURL.getProtocol().equals("https"))
+//            {
+//                throw new IllegalArgumentException(
+//                        "URL must have HTTPS protocol");
+//            }
         }
         catch (MalformedURLException e)
         {
@@ -293,6 +299,78 @@ 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
+    {
+        URL getGroupNamesURL = new URL(this.baseURL + "/groups");
+        log.debug("getGroupNames request to " + getGroupNamesURL.toString());
+        
+        HttpURLConnection conn = 
+                (HttpURLConnection) getGroupNamesURL.openConnection();
+        conn.setRequestMethod("GET");
+
+        SSLSocketFactory sf = getSSLSocketFactory();
+        if ((sf != null) && ((conn instanceof HttpsURLConnection)))
+        {
+            ((HttpsURLConnection) conn)
+                    .setSSLSocketFactory(sf);
+        }
+        int responseCode = -1;
+        try
+        {
+            responseCode = conn.getResponseCode();
+        }
+        catch(Exception e)
+        {
+            throw new AccessControlException(e.getMessage());
+        }
+        
+        if (responseCode != 200)
+        {
+            String errMessage = NetUtil.getErrorBody(conn);
+            log.debug("deleteGroup 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);
+        }
+
+        try
+        {
+            List<String> groupNames = new ArrayList<String>();
+            Reader ioReader = new InputStreamReader(conn.getInputStream());
+            BufferedReader br = new BufferedReader(ioReader);
+            CsvReader reader = new CsvReader(br);
+            
+            for (int i=0; i<reader.getColumnCount(); i++)
+            {
+                groupNames.add(reader.get(i));
+            }
+            
+            return groupNames;
+        }
+        catch (Exception bug)
+        {
+            log.error("Unexpected exception", bug);
+            throw new RuntimeException(bug);
+        }
+    }
 
     /**
      * Update a group.
@@ -506,7 +584,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);
@@ -948,6 +1026,7 @@ public class GMSClient
     public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory)
     {
         this.sslSocketFactory = sslSocketFactory;
+        clearCache();
     }
     
     /**