diff --git a/projects/cadcUtil/doc/auth.html b/projects/cadcUtil/doc/auth.html
new file mode 100644
index 0000000000000000000000000000000000000000..461ef1b2f17e503b511f7f1e15f2fd1118370fd3
--- /dev/null
+++ b/projects/cadcUtil/doc/auth.html
@@ -0,0 +1,36 @@
+<!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 a Principal (in CADC's case that is the CadcPrincipal) 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>SShPubKeyPrincipal: An ssh key identity. </li>
+    <li>CadcPrincipal: An identity used internally at the CADC. </li>
+    <li>CookiePrincipal: Cookie based identity. </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/cadcUtil/doc/uml/UserAuth.png b/projects/cadcUtil/doc/uml/UserAuth.png
new file mode 100644
index 0000000000000000000000000000000000000000..ae0738a6cc4438945b20bac98a823a9b2f326086
Binary files /dev/null and b/projects/cadcUtil/doc/uml/UserAuth.png differ
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/Group.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/Group.java
new file mode 100644
index 0000000000000000000000000000000000000000..41e2b98d1706e66387baab863ceecf23b0a23c31
--- /dev/null
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/Group.java
@@ -0,0 +1,263 @@
+/*
+ ************************************************************************
+ ****  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.auth.model;
+
+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
+     * @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");
+        }
+        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 (description == null)
+        {
+            if (other.description != null)
+            {
+                return false;
+            }
+        }
+        else if (!description.equals(other.description))
+        {
+            return false;
+        }
+        if (groupRead == null)
+        {
+            if (other.groupRead != null)
+            {
+                return false;
+            }
+        }
+        else if (!groupRead.equals(other.groupRead))
+        {
+            return false;
+        }
+        if (groupWrite == null)
+        {
+            if (other.groupWrite != null)
+            {
+                return false;
+            }
+        }
+        else if (!groupWrite.equals(other.groupWrite))
+        {
+            return false;
+        }
+        if (groupID == null)
+        {
+            if (other.groupID != null)
+            {
+                return false;
+            }
+        }
+        else if (!groupID.equals(other.groupID))
+        {
+            return false;
+        }
+        if (groupMembers == null)
+        {
+            if (other.groupMembers != null)
+            {
+                return false;
+            }
+        }
+        else if (!groupMembers.equals(other.groupMembers))
+        {
+            return false;
+        }
+        if (!owner.equals(other.owner))
+        {
+            return false;
+        }
+        if (properties == null)
+        {
+            if (other.properties != null)
+            {
+                return false;
+            }
+        }
+        else if (!properties.equals(other.properties))
+        {
+            return false;
+        }
+        if (userMembers == null)
+        {
+            if (other.userMembers != null)
+            {
+                return false;
+            }
+        }
+        else if (!userMembers.equals(other.userMembers))
+        {
+            return false;
+        }
+        return (publicRead == other.publicRead);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + groupID + "]";
+    }
+
+}
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/GroupProperty.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/GroupProperty.java
new file mode 100644
index 0000000000000000000000000000000000000000..af96c28a0605b5d8bfd9ea9b13ae56ea202cdf4f
--- /dev/null
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/GroupProperty.java
@@ -0,0 +1,199 @@
+/*
+************************************************************************
+*******************  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.auth.model;
+
+/**
+ * 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");
+        }
+        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());
+        result = prime * result + (readOnly ? 1231 : 1237);
+        result = prime * result
+                + ((value == null) ? 0 : value.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;
+        if (key == null)
+        {
+            if (other.key != null)
+            {
+                return false;
+            }
+        }
+        else if (!key.equals(other.key))
+        {
+            return false;
+        }
+        if (readOnly != other.readOnly)
+        {
+            return false;
+        }
+        if (value == null)
+        {
+            if (other.value != null)
+            {
+                return false;
+            }
+        }
+        else if (!value.equals(other.value))
+        {
+            return false;
+        }
+        return true;
+    }
+    
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + key + ": " + value + "]";
+    }
+
+}
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/PosixDetails.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/PosixDetails.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd8ea444040d0ca5f02939ee87f40c032bc5c18d
--- /dev/null
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/PosixDetails.java
@@ -0,0 +1,167 @@
+/*
+ ************************************************************************
+ ****  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.auth.model;
+
+/**
+ * Represents the posix account details associated with a user account.
+ */
+public class PosixDetails
+{
+    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;
+        }
+        if (loginShell == null)
+        {
+            if (other.loginShell != null)
+            {
+                return false;
+            }
+        }
+        else if (!loginShell.equals(other.loginShell))
+        {
+            return false;
+        }
+        if (uid != other.uid)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + uid + ", " + gid + ", "
+                + homeDirectory + "]";
+    }
+
+}
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/User.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/User.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a1200945160126ea8988a5c3b9f135397e4e158
--- /dev/null
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/User.java
@@ -0,0 +1,146 @@
+/*
+ ************************************************************************
+ ****  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.auth.model;
+
+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 UserDetails userDetails;
+    public PosixDetails posixDetails;
+    
+    
+    public User(final T 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 == null) ? 0 : 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 == null)
+        {
+            if (other.userID != null)
+            {
+                return false;
+            }
+        }
+        else if (!userID.equals(other.userID))
+        {
+            return false;
+        }
+        if (userDetails == null)
+        {
+            if (other.userDetails != null)
+            {
+                return false;
+            }
+        }
+        else if (!userDetails.equals(other.userDetails))
+        {
+            return false;
+        }
+        if (posixDetails == null)
+        {
+            if (other.posixDetails != null)
+            {
+                return false;
+            }
+        }
+        else if (!posixDetails.equals(other.posixDetails))
+        {
+            return false;
+        }
+        return this.getPrincipals().equals(other.getPrincipals());
+
+    }
+    
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + userID.getName() + "]";
+    }
+    
+}
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/UserDetails.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/UserDetails.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b1101e8941a75749539f6ffbc7af8f89a72f074
--- /dev/null
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/UserDetails.java
@@ -0,0 +1,287 @@
+/*
+ ************************************************************************
+ ****  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.auth.model;
+
+public class UserDetails
+{
+    private String firstName;
+    private String lastName;
+    private String email;
+    private String address;
+    private String institute;
+    private String city;
+    private String country;
+
+    public PersonalTitle title;
+    public String telephone;
+    public String fax;
+    public String province;
+    public String postalCode;
+
+    public UserDetails(String firstName, String lastName, String email,
+            String address, String institute, String city, String 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;
+    }
+
+    public String getFax()
+    {
+        return fax;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((address == null) ? 0 : address.hashCode());
+        result = prime * result + ((city == null) ? 0 : city.hashCode());
+        result = prime * result
+                + ((country == null) ? 0 : country.hashCode());
+        result = prime * result
+                + ((email == null) ? 0 : email.hashCode());
+        result = prime * result
+                + ((firstName == null) ? 0 : firstName.hashCode());
+        result = prime * result
+                + ((institute == null) ? 0 : institute.hashCode());
+        result = prime * result
+                + ((lastName == null) ? 0 : 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 UserDetails))
+        {
+            return false;
+        }
+        UserDetails other = (UserDetails) obj;
+        if (address == null)
+        {
+            if (other.address != null)
+            {
+                return false;
+            }
+        }
+        else if (!address.equals(other.address))
+        {
+            return false;
+        }
+        if (city == null)
+        {
+            if (other.city != null)
+            {
+                return false;
+            }
+        }
+        else if (!city.equals(other.city))
+        {
+            return false;
+        }
+        if (country == null)
+        {
+            if (other.country != null)
+            {
+                return false;
+            }
+        }
+        else if (!country.equals(other.country))
+        {
+            return false;
+        }
+        if (email == null)
+        {
+            if (other.email != null)
+            {
+                return false;
+            }
+        }
+        else if (!email.equals(other.email))
+        {
+            return false;
+        }
+        if (fax == null)
+        {
+            if (other.fax != null)
+            {
+                return false;
+            }
+        }
+        else if (!fax.equals(other.fax))
+        {
+            return false;
+        }
+        if (firstName == null)
+        {
+            if (other.firstName != null)
+            {
+                return false;
+            }
+        }
+        else if (!firstName.equals(other.firstName))
+        {
+            return false;
+        }
+        if (institute == null)
+        {
+            if (other.institute != null)
+            {
+                return false;
+            }
+        }
+        else if (!institute.equals(other.institute))
+        {
+            return false;
+        }
+        if (lastName == null)
+        {
+            if (other.lastName != null)
+            {
+                return false;
+            }
+        }
+        else if (!lastName.equals(other.lastName))
+        {
+            return false;
+        }
+        if (postalCode == null)
+        {
+            if (other.postalCode != null)
+            {
+                return false;
+            }
+        }
+        else if (!postalCode.equals(other.postalCode))
+        {
+            return false;
+        }
+        if (province == null)
+        {
+            if (other.province != null)
+            {
+                return false;
+            }
+        }
+        else if (!province.equals(other.province))
+        {
+            return false;
+        }
+        if (telephone == null)
+        {
+            if (other.telephone != null)
+            {
+                return false;
+            }
+        }
+        else if (!telephone.equals(other.telephone))
+        {
+            return false;
+        }
+        if (title != other.title)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "[" + firstName + ", "
+                + lastName + ", " + email + ", " + address + ", "
+                + institute + ", " + city + ", " + country + "]";
+    }
+}
diff --git a/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/GroupTest.java b/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/GroupTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2fb6f9cea6101e31b6de7d5ac60dd478a2ca2754
--- /dev/null
+++ b/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/GroupTest.java
@@ -0,0 +1,122 @@
+/*
+ ************************************************************************
+ ****  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.auth.model;
+
+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());
+        assertFalse(group1.equals(group2));
+        
+        group2.getUserMembers().add(user);
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1, group2);
+        
+        group1.getGroupMembers().add(group2);
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertFalse(group1.equals(group2));
+        
+        group2.getGroupMembers().add(group2);
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1, group2);
+        
+        group1.description = "Test group";
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertFalse(group1.equals(group2));
+        
+        group2.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());
+        assertFalse(group1.equals(group2));
+        
+        group2.groupRead = group2;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1, group2);
+        
+        // group write equality tests
+        group1.groupWrite = group2;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertFalse(group1.equals(group2));
+        
+        group2.groupWrite = group2;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertEquals(group1, group2);
+        
+        group1.publicRead = true;
+        assertEquals(group1.hashCode(), group2.hashCode());
+        assertFalse(group1.equals(group2));
+        
+        group2.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);
+    }
+}
diff --git a/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/UserTest.java b/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/UserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c87163ed805b65b6e4ccb429c34ea0e8eb6c6bef
--- /dev/null
+++ b/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/UserTest.java
@@ -0,0 +1,110 @@
+/*
+ ************************************************************************
+ ****  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.auth.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+
+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;
+
+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.userDetails = new UserDetails("Joe", "Raymond",
+                "jr@email.com", "123 Street", "CADC", "Victoria", "CA");
+        assertFalse(user1.equals(user2));
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user2.userDetails = user1.userDetails;
+        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"));
+        assertFalse(user1.equals(user2));
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user2.getPrincipals().add(new X500Principal("cn=aaa,ou=ca"));
+        assertEquals(user1, user1);
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user1.posixDetails = new PosixDetails(12, 23,
+                "/home/myhome");
+        assertFalse(user1.equals(user2));
+        assertEquals(user1.hashCode(), user2.hashCode());
+
+        user2.getPrincipals().add(new X500Principal("cn=aaa,ou=ca"));
+        assertEquals(user1, user1);
+        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(user1.userDetails);
+        System.out.println(user1.posixDetails);
+        
+    }
+}