diff --git a/projects/cadcUtil/doc/uml/UserAuth.png b/projects/cadcUtil/doc/uml/UserAuth.png
index c36dfcc1fe9c2513edec0e715a4318b130ea89a2..64354ac504dff79d1f26a7e0e17100cb82bac64a 100644
Binary files a/projects/cadcUtil/doc/uml/UserAuth.png 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
index 30c155ceac2bcab7655029f42a0e9d4d013f308f..a5dc88727ff612250734a0cb834b024b7239e64e 100644
--- a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/Group.java
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/Group.java
@@ -76,7 +76,8 @@ public class Group
      * Ctor.
      * 
      * @param groupID
-     *            Unique ID for the group
+     *            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.
      */
@@ -87,6 +88,12 @@ public class Group
         {
             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)
         {
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/GroupProperty.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/GroupProperty.java
index 743af8e0c4e65c24a452eef018aa8e0440be1358..cee887259a4f64cf11b8317b225ab8aa3e96f6ea 100644
--- a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/GroupProperty.java
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/GroupProperty.java
@@ -140,9 +140,6 @@ public class GroupProperty
         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;
     }
 
@@ -165,26 +162,7 @@ public class 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.equals(other.value))
-        {
-            return false;
-        }
-        return true;
+        return key.equals(other.key);
     }
     
     @Override
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/PosixDetails.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/PosixDetails.java
index ba6cf69f504e5a914a79b4835278d326657bc0f8..44d8ff4637e90e37a215ed711a2fdaf1bd8856e1 100644
--- a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/PosixDetails.java
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/PosixDetails.java
@@ -37,7 +37,7 @@ package ca.nrc.cadc.auth.model;
 /**
  * Represents the posix account details associated with a user account.
  */
-public class PosixDetails
+public class PosixDetails implements UserDetails
 {
     private long uid;
     private long gid;
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/User.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/User.java
index 74ab9ad64f170ed2a1de9333fbf9da82afb13f19..e92232c215a6c60256bfb17a189bac4f481c50df 100644
--- a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/User.java
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/User.java
@@ -46,8 +46,7 @@ public class User<T extends Principal>
 
     private Set<Principal> principals = new HashSet<Principal>();
     
-    public UserDetails userDetails;
-    public PosixDetails posixDetails;
+    public Set<UserDetails> details = new HashSet<UserDetails>();
     
     
     public User(final T userID)
@@ -79,8 +78,7 @@ public class User<T extends Principal>
     {
         final int prime = 31;
         int result = 1;
-        result = prime * result
-                + ((userID == null) ? 0 : userID.hashCode());
+        result = prime * result + userID.hashCode();
         return result;
     }
 
diff --git a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/UserDetails.java b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/UserDetails.java
index dcede139b1ffe9305dade4727a2e2c517d09c3ce..6720b0f8978afd95367bd35b59a9f8e4791933d2 100644
--- a/projects/cadcUtil/src/ca/nrc/cadc/auth/model/UserDetails.java
+++ b/projects/cadcUtil/src/ca/nrc/cadc/auth/model/UserDetails.java
@@ -34,169 +34,23 @@
 
 package ca.nrc.cadc.auth.model;
 
-public class UserDetails
+public interface UserDetails
 {
-    private String firstName;
-    private String lastName;
-    private String email;
-    private String address;
-    private String institute;
-    private String city;
-    private String country;
-
-    public UserDetails(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 java.lang.Object#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;
-    }
+    public int 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 UserDetails))
-        {
-            return false;
-        }
-        UserDetails other = (UserDetails) 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;
-    }
+    public boolean equals(Object obj);
+
+    public String toString();
 
-    @Override
-    public String toString()
-    {
-        return getClass().getSimpleName() + "[" + firstName + ", "
-                + lastName + ", " + email + ", " + address + ", "
-                + institute + ", " + city + ", " + country + "]";
-    }
-}
+}
\ No newline at end of file
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
index 85c45dcbd9c25e8757a507b4ebb2049baf615bb2..3acb1a875761740b2169971dad220e3bf694b39e 100644
--- a/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/GroupTest.java
+++ b/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/GroupTest.java
@@ -92,7 +92,7 @@ public class GroupTest
         assertEquals(group1.hashCode(), group2.hashCode());
         assertEquals(group1,group2);
         
-        group2 = new Group("NewTestGroup", owner);
+        group2 = new Group("NewTestGroup-._~.", owner);
         assertFalse(group1.hashCode() == group2.hashCode());
         assertFalse(group1.equals(group2));
         
@@ -125,5 +125,39 @@ public class GroupTest
             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/cadcUtil/test/src/ca/nrc/cadc/auth/model/UserTest.java b/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/UserTest.java
index 547b37d44244bf9debb9506374fd14daf5907022..75f0dd92f3c607d33e2db2f722c450f234639e47 100644
--- a/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/UserTest.java
+++ b/projects/cadcUtil/test/src/ca/nrc/cadc/auth/model/UserTest.java
@@ -65,8 +65,8 @@ public class UserTest
         assertEquals(user1, user2);
         assertEquals(user1.hashCode(), user2.hashCode());
 
-        user1.userDetails = new UserDetails("Joe", "Raymond",
-                "jr@email.com", "123 Street", "CADC", "Victoria", "CA");
+        user1.details.add(new PersonalDetails("Joe", "Raymond",
+                "jr@email.com", "123 Street", "CADC", "Victoria", "CA"));
         assertEquals(user1, user2);
         assertEquals(user1.hashCode(), user2.hashCode());
 
@@ -82,8 +82,8 @@ public class UserTest
         assertEquals(user1, user2);
         assertEquals(user1.hashCode(), user2.hashCode());
 
-        user1.posixDetails = new PosixDetails(12, 23,
-                "/home/myhome");
+        user1.details.add(new PosixDetails(12, 23,
+                "/home/myhome"));
         assertEquals(user1, user2);
         assertEquals(user1.hashCode(), user2.hashCode());
 
@@ -93,8 +93,9 @@ public class UserTest
         
         // visual test of toString
         System.out.println(user1);
-        System.out.println(user1.userDetails);
-        System.out.println(user1.posixDetails);
+        System.out.println(new PersonalDetails("Joe", "Raymond",
+                "jr@email.com", "123 Street", "CADC", "Victoria", "CA"));
+        System.out.println(new PosixDetails(12, 23,"/home/myhome"));
         
     }
     
@@ -115,7 +116,7 @@ public class UserTest
         thrown = false;
         try
         {
-            new UserDetails(null, "Raymond",
+            new PersonalDetails(null, "Raymond",
                     "jr@email.com", "123 Street", "CADC", "Victoria", "CA");
         }
         catch(IllegalArgumentException e)
@@ -127,7 +128,7 @@ public class UserTest
         thrown = false;
         try
         {
-            new UserDetails("Joe", null,
+            new PersonalDetails("Joe", null,
                     "jr@email.com", "123 Street", "CADC", "Victoria", "CA");
         }
         catch(IllegalArgumentException e)
@@ -139,7 +140,7 @@ public class UserTest
         thrown = false;
         try
         {
-            new UserDetails("Joe", "Raymond",
+            new PersonalDetails("Joe", "Raymond",
                     null, "123 Street", "CADC", "Victoria", "CA");
         }
         catch(IllegalArgumentException e)
@@ -151,7 +152,7 @@ public class UserTest
         thrown = false;
         try
         {
-            new UserDetails("Joe", "Raymond",
+            new PersonalDetails("Joe", "Raymond",
                     "jr@email.com", null, "CADC", "Victoria", "CA");
         }
         catch(IllegalArgumentException e)
@@ -163,7 +164,7 @@ public class UserTest
         thrown = false;
         try
         {
-            new UserDetails("Joe", "Raymond",
+            new PersonalDetails("Joe", "Raymond",
                     "jr@email.com", "123 Street", null, "Victoria", "CA");
         }
         catch(IllegalArgumentException e)
@@ -175,7 +176,7 @@ public class UserTest
         thrown = false;
         try
         {
-            new UserDetails("Joe", "Raymond",
+            new PersonalDetails("Joe", "Raymond",
                     "jr@email.com", "123 Street", "CADC", null, "CA");
         }
         catch(IllegalArgumentException e)
@@ -187,7 +188,7 @@ public class UserTest
         thrown = false;
         try
         {
-            new UserDetails("Joe", "Raymond",
+            new PersonalDetails("Joe", "Raymond",
                     "jr@email.com", "123 Street", "CADC", "Victoria", null);
         }
         catch(IllegalArgumentException e)