diff --git a/projects/cadcAccessControl-Server/build.xml b/projects/cadcAccessControl-Server/build.xml
index 6799d81d742f9b12258fb3fe5266fba621e2b928..fcbdcb0a09d7d1bad61f94339c854030567c47f7 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-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/ldap/LdapGroupDAO.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAO.java
index 93d2965204cbb5356893da016af1629026d6edf7..2d1d31ab2d8c0428c44d8d0c9e1a62a17f974ba4 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
@@ -314,12 +314,12 @@ public class LdapGroupDAO<T extends Principal> extends LdapDAO
      * 
      * @throws TransientException If an temporary, unexpected problem occurred.
      */
-    public Collection<String> getGroupNames() throws TransientException,
-               AccessControlException
+    public Collection<String> getGroupNames()
+        throws TransientException, AccessControlException
     {
         try
         {
-            Filter filter = null;
+            Filter filter = Filter.createPresenceFilter("entrydn");
             String [] attributes = new String[] {"cn", "nsaccountlock"};
             
             SearchRequest searchRequest = 
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 9fc50999851137c78fcf9a2a25f176e6ee555966..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
@@ -94,8 +94,8 @@ public class LdapGroupPersistence<T extends Principal>
         config = LdapConfig.getLdapConfig();
     }
     
-    public Collection<String> getGroupNames() throws TransientException,
-        AccessControlException
+    public Collection<String> getGroupNames()
+        throws TransientException, AccessControlException
     {
         LdapGroupDAO<T> groupDAO = null;
         LdapUserDAO<T> userDAO = null;
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
new file mode 100644
index 0000000000000000000000000000000000000000..04f2b12e968b4ee2566101cb35a1f124bd43c79a
--- /dev/null
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/GetGroupNamesAction.java
@@ -0,0 +1,108 @@
+/*
+ ************************************************************************
+ *******************  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 java.util.Collection;
+
+import ca.nrc.cadc.ac.server.GroupPersistence;
+
+import com.csvreader.CsvWriter;
+
+import javax.servlet.http.HttpServletResponse;
+
+public class GetGroupNamesAction extends GroupsAction
+{
+
+    GetGroupNamesAction(GroupLogInfo logInfo)
+    {
+        super(logInfo);
+    }
+
+    public Object run()
+        throws Exception
+    {
+        GroupPersistence groupPersistence = getGroupPersistence();
+        Collection<String> groups = groupPersistence.getGroupNames();
+        getHttpServletResponse().setContentType("text/csv");
+        
+        CsvWriter writer = new CsvWriter(getHttpServletResponse().getWriter(), ',');
+        
+        for (String group : groups)
+        {
+            writer.write(group);
+        }
+        writer.endRecord();
+        return null;
+    }
+
+    protected HttpServletResponse getHttpServletResponse()
+    {
+        return this.response;
+    }
+
+}
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..4af0b5922fe8343e23ccdad1fb1a17c70bb90b5b 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
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());
+        }
+    }
+
+}