diff --git a/projects/cadcAccessControl/build.xml b/projects/cadcAccessControl/build.xml
new file mode 100644
index 0000000000000000000000000000000000000000..25762598f1a728a912d6fe99a1a6c27352a4e9cf
--- /dev/null
+++ b/projects/cadcAccessControl/build.xml
@@ -0,0 +1,123 @@
+<!--
+************************************************************************
+*******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+**************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+*
+*  (c) 2009.                            (c) 2009.
+*  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 $
+*
+************************************************************************
+-->
+
+
+<!DOCTYPE project>
+<project default="build" basedir=".">
+    <property environment="env"/>
+    <property file="local.build.properties" />
+    
+    <!-- site-specific build properties or overrides of values in opencadc.properties -->
+    <property file="${env.CADC_PREFIX}/etc/local.properties" />
+    
+    <!-- site-specific targets, e.g. install, cannot duplicate those in opencadc.targets.xml -->
+    <import file="${env.CADC_PREFIX}/etc/local.targets.xml" optional="true" />
+
+    <!-- default properties and targets -->
+    <property file="${env.CADC_PREFIX}/etc/opencadc.properties" />
+    <import file="${env.CADC_PREFIX}/etc/opencadc.targets.xml"/>
+    
+    <!-- developer convenience: place for extra targets and properties -->
+    <import file="extras.xml" optional="true" />
+
+    <property name="project" value="cadcAccessControl" />
+
+    <property name="cadcUtil" value="${lib}/cadcUtil.jar" />
+
+    <property name="jars" value="${cadcUtil}:${ext.lib}/log4j.jar" />
+  
+    <target name="build" depends="compile">
+        <jar jarfile="${build}/lib/${project}.jar"
+                    basedir="${build}/class"
+                    update="no">
+                <include name="ca/nrc/cadc/**" />
+        </jar>
+    </target>
+
+    <!-- JAR files needed to run the test suite -->
+    <property name="testingJars" value="${build}/class:${jars}:${ext.lib}/junit.jar:${ext.lib}/xerces.jar:${ext.dev}/easymock.jar:${ext.dev}/cglib.jar:${ext.dev}/objenesis.jar:${ext.dev}/asm.jar" />
+
+    <target name="test" depends="compile-test">
+        <echo message="Running test" />
+
+        <!-- Run the junit test suite -->
+        <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.UserTest" />
+            <test name="ca.nrc.cadc.ac.GroupTest" />
+            <formatter type="plain" usefile="false" />
+        </junit>
+    </target>
+    
+</project>
diff --git a/projects/cadcAccessControl/doc/AccessControl.png b/projects/cadcAccessControl/doc/AccessControl.png
new file mode 100644
index 0000000000000000000000000000000000000000..52bcea3a9f84a12817a27af6e2e3f7623e5b6b6f
Binary files /dev/null and b/projects/cadcAccessControl/doc/AccessControl.png differ
diff --git a/projects/cadcAccessControl/doc/auth.html b/projects/cadcAccessControl/doc/auth.html
new file mode 100644
index 0000000000000000000000000000000000000000..8608f3dde4f3f9b468e08ac6e1e5d5cd144b02b9
--- /dev/null
+++ b/projects/cadcAccessControl/doc/auth.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<title>
+	CADC User Authorization Model
+</title>
+</head>
+
+<body>
+
+<div class="main">
+
+<h1>CADC User Authorization Model</h1>
+
+<p>The CADC User Authorization Model is a model for representing CADC users and groups. The model is used primarily in the GMS and Users Web services.
+</p>
+
+<a href="uml/UserAuth.png">  <img src="uml/UserAuth.png" alt="CADC User Authorization Model"></a>
+
+<h2>User Class Features </h2>
+In the system, a user is uniquely identified by one Principal (in CADC's case that is of type NumericPrincipal) but can have a number of other  identities for different contexts:
+<ul>
+    <li>HttpPrincipal: Web user identity associated with Simple HHTP User Password access.</li>
+    <li>X500Principal: X509 certificate identity. </li>
+    <li>NumericPrincipal: An numeric identity associated with a user. Typically, used internally within a system.</li>
+    <li>OpenIdPrincipal: An OpenID identity. </li>
+</ul>
+
+<h2>Group Class Features</h2>
+Groups represet associations of users. Members of groups can be groups of users or simple users. groupWrite and groupRead represent the groups that have read and read-and-write permissions to the current group. 
+
+</body>
+</html>
diff --git a/projects/cadcAccessControl/doc/auth.zargo b/projects/cadcAccessControl/doc/auth.zargo
new file mode 100644
index 0000000000000000000000000000000000000000..57aa2846f75e01db9d2e032775009c297468d492
Binary files /dev/null and b/projects/cadcAccessControl/doc/auth.zargo differ
diff --git a/projects/cadcAccessControl/doc/uml/UserAuth.png b/projects/cadcAccessControl/doc/uml/UserAuth.png
new file mode 100644
index 0000000000000000000000000000000000000000..64354ac504dff79d1f26a7e0e17100cb82bac64a
Binary files /dev/null and b/projects/cadcAccessControl/doc/uml/UserAuth.png differ
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/Group.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/Group.java
new file mode 100644
index 0000000000000000000000000000000000000000..e89798147a80a826b7c03c819ed51ac35f48748a
--- /dev/null
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/Group.java
@@ -0,0 +1,193 @@
+/*
+ ************************************************************************
+ ****  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  *****
+ *
+ * (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
+ *
+ * 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.
+ *
+ *
+ * @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  *****
+ ************************************************************************
+ */
+
+package ca.nrc.cadc.ac;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+
+public class Group
+{
+    private String groupID;
+
+    private User<? extends Principal> owner;
+
+    // group's properties
+    protected Set<GroupProperty> properties = new HashSet<GroupProperty>();
+
+    // group's user members
+    private Set<User<? extends Principal>> userMembers = 
+            new HashSet<User<? extends Principal>>();
+    // group's group members
+    private Set<Group> groupMembers = new HashSet<Group>();
+
+    public String description;
+    
+    // Access Control properties
+    /**
+     * group that can read details of this group
+     * Note: this class does not enforce any access control rules
+     */
+    public Group groupRead;
+    /**
+     * group that can read and write details of this group
+     * Note: this class does not enforce any access control rules
+     */
+    public Group groupWrite;
+    /**
+     * flag that show whether the details of this group are publicly readable
+     * Note: this class does not enforce any access control rules
+     */
+    public boolean publicRead = false;
+
+    /**
+     * Ctor.
+     * 
+     * @param groupID
+     *            Unique ID for the group. Must be a valid URI fragment component,
+     *            so it's restricted to alphanumeric and "-", ".","_","~" characters.
+     * @param owner
+     *            Owner/Creator of the group.
+     */
+    public Group(final String groupID,
+            final User<? extends Principal> owner)
+    {
+        if(groupID == null)
+        {
+            throw new IllegalArgumentException("Null groupID");
+        }
+        
+        // check for invalid path characters in groupID
+        if(!groupID.matches("^[a-zA-Z0-9\\-\\.~_]*$"))
+            throw new IllegalArgumentException("Invalid group ID " + groupID
+                    + ": may not contain space ( ), slash (/), escape (\\), or percent (%)");
+
+        this.groupID = groupID;
+        if(owner == null)
+        {
+            throw new IllegalArgumentException("Null owner");
+        }
+        this.owner = owner;
+    }
+
+    /**
+     * Obtain this Group's unique id.
+     * 
+     * @return String group ID.
+     */
+    public String getID()
+    {
+        return groupID;
+    }
+
+    /**
+     * Obtain this group's owner
+     * @return owner of the group
+     */
+    public User<? extends Principal> getOwner()
+    {
+        return owner;
+    }
+
+    /**
+     * 
+     * @return a set of properties associated with a group
+     */
+    public Set<GroupProperty> getProperties()
+    {
+        return properties;
+    }
+
+    /**
+     * 
+     * @return individual user members of this group
+     */
+    public Set<User<? extends Principal>> getUserMembers()
+    {
+        return userMembers;
+    }
+
+    /**
+     * 
+     * @return group members of this group
+     */
+    public Set<Group> getGroupMembers()
+    {
+        return groupMembers;
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        return 31  + groupID.hashCode();
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (!(obj instanceof Group))
+        {
+            return false;
+        }
+        Group other = (Group) obj;
+        if (!groupID.equals(other.groupID))
+        {
+            return false;
+        }
+        return true;
+    }
+    
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + groupID + "]";
+    }
+
+}
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/GroupProperty.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/GroupProperty.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ddfa8016e0e75077bf34bac7b7b02b32291d4bc
--- /dev/null
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/GroupProperty.java
@@ -0,0 +1,174 @@
+/*
+************************************************************************
+*******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+**************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+*
+*  (c) 2009.                            (c) 2009.
+*  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;
+
+/**
+ * A property representing metadata for a group.
+ *
+ */
+public class GroupProperty
+{   
+    // The property identifier
+    private String key;
+    
+    // The value of the property
+    private Object value;
+    
+    // true if the property cannot be modified.
+    private boolean readOnly;
+    
+
+    /**
+     * GroupProperty constructor.
+     * 
+     * @param key The property key. Cannot be null.
+     * @param value The property value.
+     */
+    public GroupProperty(String key, Object value, boolean readOnly)
+    {
+        if(key == null)
+        {
+            throw new IllegalArgumentException("Null key");
+        }
+        if(value == null)
+        {
+            throw new IllegalArgumentException("Null value");
+        }
+        this.key = key;
+        this.value = value;
+        this.readOnly = readOnly;
+    }
+
+    /**
+     * @return property key
+     */
+    public String getKey()
+    {
+        return key;
+    }
+
+    /**
+     * @return value
+     */
+    public Object getValue()
+    {
+        return value;
+    }
+
+    /**
+     * @return read only
+     */
+    public boolean isReadOnly()
+    {
+        return readOnly;
+    }
+    
+
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((key == null) ? 0 : key.hashCode());
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (!(obj instanceof GroupProperty))
+        {
+            return false;
+        }
+        GroupProperty other = (GroupProperty) obj;
+        return key.equals(other.key);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + key + ": " + value + "]";
+    }
+
+}
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/PersonalDetails.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/PersonalDetails.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5ca2405f39ea169147858ebeba054c9963e08eb
--- /dev/null
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/PersonalDetails.java
@@ -0,0 +1,203 @@
+/*
+ ************************************************************************
+ ****  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  *****
+ *
+ * (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
+ *
+ * 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.
+ *
+ *
+ * @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  *****
+ ************************************************************************
+ */
+
+package ca.nrc.cadc.ac;
+
+public class PersonalDetails implements UserDetails
+{
+    private String firstName;
+    private String lastName;
+    private String email;
+    private String address;
+    private String institute;
+    private String city;
+    private String country;
+
+    public PersonalDetails(String firstName, String lastName, String email,
+            String address, String institute, String city, String country)
+    {
+        if (firstName == null)
+        {
+            throw new IllegalArgumentException("null firstName");
+        }
+        if (lastName == null)
+        {
+            throw new IllegalArgumentException("null lastName");
+        }
+        if (email == null)
+        {
+            throw new IllegalArgumentException("null email");
+        }
+
+        if (address == null)
+        {
+            throw new IllegalArgumentException("null address");
+        }
+        if (institute == null)
+        {
+            throw new IllegalArgumentException("null institute");
+        }
+        if (city == null)
+        {
+            throw new IllegalArgumentException("null city");
+        }
+        if (country == null)
+        {
+            throw new IllegalArgumentException("null country");
+        }
+        this.firstName = firstName;
+        this.lastName = lastName;
+        this.email = email;
+        this.address = address;
+        this.institute = institute;
+        this.city = city;
+        this.country = country;
+    }
+
+    public String getFirstName()
+    {
+        return firstName;
+    }
+
+    public String getLastName()
+    {
+        return lastName;
+    }
+
+    public String getEmail()
+    {
+        return email;
+    }
+
+    public String getAddress()
+    {
+        return address;
+    }
+
+    public String getInstitute()
+    {
+        return institute;
+    }
+
+    public String getCity()
+    {
+        return city;
+    }
+
+    public String getCountry()
+    {
+        return country;
+    }
+
+    /* (non-Javadoc)
+     * @see ca.nrc.cadc.auth.model.UserDetails#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + address.hashCode();
+        result = prime * result + city.hashCode();
+        result = prime * result + country.hashCode();
+        result = prime * result + email.hashCode();
+        result = prime * result + firstName.hashCode();
+        result = prime * result + institute.hashCode();
+        result = prime * result + lastName.hashCode();
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (!(obj instanceof PersonalDetails))
+        {
+            return false;
+        }
+        PersonalDetails other = (PersonalDetails) obj;
+        if (!firstName.equals(other.firstName))
+        {
+            return false;
+        }
+        if (!lastName.equals(other.lastName))
+        {
+            return false;
+        }
+        if (!email.equals(other.email))
+        {
+            return false;
+        }
+        if (!institute.equals(other.institute))
+        {
+            return false;
+        }
+        if (!address.equals(other.address))
+        {
+            return false;
+        }
+        if (!city.equals(other.city))
+        {
+            return false;
+        }
+        if (!country.equals(other.country))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see ca.nrc.cadc.auth.model.UserDetails#toString()
+     */
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + firstName + ", "
+                + lastName + ", " + email + ", " + address + ", "
+                + institute + ", " + city + ", " + country + "]";
+    }
+}
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/PosixDetails.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/PosixDetails.java
new file mode 100644
index 0000000000000000000000000000000000000000..12d8dec1b4560d9d3b68a681dc36793d51c81247
--- /dev/null
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/PosixDetails.java
@@ -0,0 +1,152 @@
+/*
+ ************************************************************************
+ ****  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  *****
+ *
+ * (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
+ *
+ * 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.
+ *
+ *
+ * @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  *****
+ ************************************************************************
+ */
+
+package ca.nrc.cadc.ac;
+
+/**
+ * Represents the posix account details associated with a user account.
+ */
+public class PosixDetails implements UserDetails
+{
+    private long uid;
+    private long gid;
+    private String homeDirectory;
+
+    /**
+     * user login shell
+     */
+    public String loginShell;
+
+    /**
+     * 
+     * @param uid
+     *            posix uid
+     * @param gid
+     *            posix gid
+     * @param homeDirectory
+     *            home directory
+     */
+    public PosixDetails(long uid, long gid, String homeDirectory)
+    {
+        this.uid = uid;
+        this.gid = gid;
+        if (homeDirectory == null)
+        {
+            throw new IllegalArgumentException(
+                    "null home directory in POSIX details");
+        }
+        this.homeDirectory = homeDirectory;
+    }
+
+    /**
+     * @return the uid
+     */
+    public long getUid()
+    {
+        return uid;
+    }
+
+    /**
+     * @return the gid
+     */
+    public long getGid()
+    {
+        return gid;
+    }
+
+    /**
+     * @return the homeDirectory
+     */
+    public String getHomeDirectory()
+    {
+        return homeDirectory;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (int) (gid ^ (gid >>> 32));
+        result = prime * result + homeDirectory.hashCode();
+        result = prime * result + (int) (uid ^ (uid >>> 32));
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (!(obj instanceof PosixDetails))
+        {
+            return false;
+        }
+        PosixDetails other = (PosixDetails) obj;
+        if (gid != other.gid)
+        {
+            return false;
+        }
+
+        if (!homeDirectory.equals(other.homeDirectory))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + uid + ", " + gid + ", "
+                + homeDirectory + "]";
+    }
+
+}
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/User.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/User.java
new file mode 100644
index 0000000000000000000000000000000000000000..3174d0136d7d2fbbc5bdd0a4fa5e758118ae0ded
--- /dev/null
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/User.java
@@ -0,0 +1,118 @@
+/*
+ ************************************************************************
+ ****  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  *****
+ *
+ * (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
+ *
+ * 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.
+ *
+ *
+ * @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  *****
+ ************************************************************************
+ */
+
+
+
+package ca.nrc.cadc.ac;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+
+public class User<T extends Principal>
+{
+    private T userID;
+
+    private Set<Principal> principals = new HashSet<Principal>();
+    
+    public Set<UserDetails> details = new HashSet<UserDetails>();
+    
+    
+    public User(final T userID)
+    {
+        if(userID == null)
+        {
+            throw new IllegalArgumentException("null userID");
+        }
+        this.userID = userID;
+    }
+    
+    
+    public Set<Principal> getPrincipals()
+    {
+        return principals;
+    }
+    
+    public T getUserID()
+    {
+        return userID;
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + userID.hashCode();
+        return result;
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        User<?> other = (User<?>) obj;
+        if (!userID.equals(other.userID))
+        {
+            return false;
+        }
+        return true;
+    }
+    
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + userID.getName() + "]";
+    }
+    
+}
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/UserDetails.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/UserDetails.java
new file mode 100644
index 0000000000000000000000000000000000000000..d03035c1be2377d4de5ebd9174729138006da364
--- /dev/null
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/UserDetails.java
@@ -0,0 +1,56 @@
+/*
+ ************************************************************************
+ ****  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  *****
+ *
+ * (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
+ *
+ * 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.
+ *
+ *
+ * @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  *****
+ ************************************************************************
+ */
+
+package ca.nrc.cadc.ac;
+
+public interface UserDetails
+{
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode();
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    public boolean equals(Object obj);
+
+    public String toString();
+
+}
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/GroupTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/GroupTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3e48c9a0c5cb5a5446d4645d7470db1951d7b37c
--- /dev/null
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/GroupTest.java
@@ -0,0 +1,163 @@
+/*
+ ************************************************************************
+ ****  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  *****
+ *
+ * (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
+ *
+ * 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.
+ *
+ *
+ * @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  *****
+ ************************************************************************
+ */
+
+
+
+package ca.nrc.cadc.ac;
+
+import org.apache.log4j.Logger;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import ca.nrc.cadc.auth.HttpPrincipal;
+
+public class GroupTest
+{
+    private static Logger log = Logger.getLogger(GroupTest.class);
+    
+    @Test
+    public void simpleGroupTest() throws Exception
+    {
+        
+        User<HttpPrincipal> owner = new User<HttpPrincipal>(new HttpPrincipal("owner"));
+        Group group1 = new Group("TestGroup", owner);
+        User<HttpPrincipal> user = new User<HttpPrincipal>(new HttpPrincipal("user"));
+        
+        group1.getUserMembers().add(user);
+        assertEquals(1, group1.getUserMembers().size());
+
+        Group group2 = group1;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1, group2);
+        assertTrue(group1 == group2);
+        
+        group2 = new Group("TestGroup", owner);
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1,group2);
+        
+        group2.getUserMembers().add(user);
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1,group2);
+        
+        group1.getGroupMembers().add(group2);
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1,group2);
+        
+        group1.description = "Test group";
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1,group2);
+        
+        // group read and write equality tests     
+        group1.groupRead = group2;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1,group2);
+        
+        // group write equality tests
+        group1.groupWrite = group2;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1,group2);
+
+        group1.publicRead = true;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1,group2);
+        
+        group2 = new Group("NewTestGroup-._~.", owner);
+        assertFalse(group1.hashCode() == group2.hashCode());
+        assertFalse(group1.equals(group2));
+        
+        // test toString
+        System.out.println(group1);
+    }
+    
+    @Test
+    public void exceptionTests()
+    {
+        boolean thrown = false;
+        try
+        {
+            new Group(null, new User<HttpPrincipal>(new HttpPrincipal("owner")));
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        
+        thrown = false;
+        try
+        {
+            new Group("NewTestGroup", null);
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        // invavlid group IDs
+        thrown = false;
+        try
+        {
+            new Group("New/Test/Group", new User<HttpPrincipal>(new HttpPrincipal("owner")));
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        thrown = false;
+        try
+        {
+            new Group("New%Test%Group", new User<HttpPrincipal>(new HttpPrincipal("owner")));
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        thrown = false;
+        try
+        {
+            new Group("New\\Test\\Group", new User<HttpPrincipal>(new HttpPrincipal("owner")));
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+    }
+}
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/UserTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/UserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0fce989ccfd154bcb79f29134780d0d486ec9a21
--- /dev/null
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/UserTest.java
@@ -0,0 +1,233 @@
+/*
+ ************************************************************************
+ ****  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  *****
+ *
+ * (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
+ *
+ * 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.
+ *
+ *
+ * @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  *****
+ ************************************************************************
+ */
+
+package ca.nrc.cadc.ac;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.log4j.Logger;
+import org.junit.Test;
+
+import ca.nrc.cadc.auth.HttpPrincipal;
+import ca.nrc.cadc.auth.NumericPrincipal;
+import ca.nrc.cadc.auth.OpenIdPrincipal;
+
+public class UserTest
+{
+    private static Logger log = Logger.getLogger(UserTest.class);
+
+    @Test
+    public void simpleEqualityTests() throws Exception
+    {
+
+        User<HttpPrincipal> user1 = new User<HttpPrincipal>(
+                new HttpPrincipal("user1"));
+        User<HttpPrincipal> user2 = user1;
+        assertEquals(user1, user2);
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user2 = new User<HttpPrincipal>(new HttpPrincipal("user1"));
+        assertEquals(user1, user2);
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user1.details.add(new PersonalDetails("Joe", "Raymond",
+                "jr@email.com", "123 Street", "CADC", "Victoria", "CA"));
+        assertEquals(user1, user2);
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+
+        User<X500Principal> user3 = new User<X500Principal>(
+                new X500Principal("cn=aaa,ou=ca"));
+        User<HttpPrincipal> user4 = new User<HttpPrincipal>(
+                new HttpPrincipal("cn=aaa,ou=ca"));
+        assertFalse(user3.equals(user4));
+        assertFalse(user3.hashCode() == user4.hashCode());
+
+        user1.getPrincipals().add(new X500Principal("cn=aaa,ou=ca"));
+        assertEquals(user1, user2);
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user1.details.add(new PosixDetails(12, 23,
+                "/home/myhome"));
+        assertEquals(user1, user2);
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        User<NumericPrincipal> user5 = new User<NumericPrincipal>(
+                new NumericPrincipal(32));
+        assertFalse(user1.equals(user5));
+        
+        // visual test of toString
+        System.out.println(user1);
+        System.out.println(new PersonalDetails("Joe", "Raymond",
+                "jr@email.com", "123 Street", "CADC", "Victoria", "CA"));
+        System.out.println(new PosixDetails(12, 23,"/home/myhome"));
+        
+    }
+    
+    @Test
+    public void exceptionTests()
+    {
+        boolean thrown = false;
+        try
+        {
+            new User<NumericPrincipal>(null);
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        thrown = false;
+        try
+        {
+            new PersonalDetails(null, "Raymond",
+                    "jr@email.com", "123 Street", "CADC", "Victoria", "CA");
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        thrown = false;
+        try
+        {
+            new PersonalDetails("Joe", null,
+                    "jr@email.com", "123 Street", "CADC", "Victoria", "CA");
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        thrown = false;
+        try
+        {
+            new PersonalDetails("Joe", "Raymond",
+                    null, "123 Street", "CADC", "Victoria", "CA");
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        thrown = false;
+        try
+        {
+            new PersonalDetails("Joe", "Raymond",
+                    "jr@email.com", null, "CADC", "Victoria", "CA");
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        thrown = false;
+        try
+        {
+            new PersonalDetails("Joe", "Raymond",
+                    "jr@email.com", "123 Street", null, "Victoria", "CA");
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        thrown = false;
+        try
+        {
+            new PersonalDetails("Joe", "Raymond",
+                    "jr@email.com", "123 Street", "CADC", null, "CA");
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        thrown = false;
+        try
+        {
+            new PersonalDetails("Joe", "Raymond",
+                    "jr@email.com", "123 Street", "CADC", "Victoria", null);
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        thrown = false;
+        try
+        {
+            new PosixDetails(11, 22, null);
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        thrown = false;
+        try
+        {
+            new HttpPrincipal(null);
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        
+        thrown = false;
+        try
+        {
+            new OpenIdPrincipal(null);
+        }
+        catch(IllegalArgumentException e)
+        {
+            thrown = true;
+        }
+        assertTrue(thrown);
+    }
+}