From 1e44bdb605d11da77f7577bc09ba1df2fc674d16 Mon Sep 17 00:00:00 2001
From: Patrick Dowler <patrick.dowler@nrc-cnrc.gc.ca>
Date: Wed, 19 Aug 2015 11:29:40 -0700
Subject: [PATCH] added support for incomplete cache of groups to improve
 isMember and getGroup when possible

---
 .../src/ca/nrc/cadc/ac/client/GMSClient.java  | 144 +++++++++++-------
 .../ca/nrc/cadc/ac/client/GMSClientTest.java  |  31 ++--
 2 files changed, 114 insertions(+), 61 deletions(-)

diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java
index ce1d0058..c1916258 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/client/GMSClient.java
@@ -774,7 +774,7 @@ public class GMSClient implements TransferListener
             throw new IllegalArgumentException("userID and role are required.");
         }
         
-        List<Group> cachedGroups = getCachedGroups(userID, role);
+        List<Group> cachedGroups = getCachedGroups(userID, role, true);
         if (cachedGroups != null)
         {
             return cachedGroups;
@@ -881,18 +881,10 @@ public class GMSClient implements TransferListener
             throw new IllegalArgumentException("userID and role are required.");
         }
         
-        List<Group> cachedGroups = getCachedGroups(userID, role);
-        if (cachedGroups != null)
+        Group cachedGroup = getCachedGroup(userID, groupName, role);
+        if (cachedGroup != null)
         {
-            int index = cachedGroups.indexOf(new Group(groupName));
-            if (index != -1)
-            {
-                return cachedGroups.get(index);
-            }
-            else
-            {
-                return null;
-            }
+            return cachedGroup;
         }
         
         String idType = AuthenticationUtil.getPrincipalType(userID);
@@ -951,9 +943,9 @@ public class GMSClient implements TransferListener
             }
             if (groups.size() == 1)
             {
-                // don't cache these results as it is not a complete
-                // list of memberships--it only applies to one group.
-                return groups.get(0);
+                Group ret = groups.get(0);
+                addCachedGroup(userID, ret, role);
+                return ret;
             }
             throw new IllegalStateException(
                     "Duplicate membership for " + id + " in group " + groupName);
@@ -1049,15 +1041,13 @@ public class GMSClient implements TransferListener
     {
         AccessControlContext acContext = AccessController.getContext();
         Subject subject = Subject.getSubject(acContext);
-        
         if (subject != null)
         {
-            log.debug("Clearing cache");
-            subject.getPrivateCredentials().clear();
+            subject.getPrivateCredentials().remove(new GroupMemberships());
         }
     }
 
-    protected List<Group> getCachedGroups(Principal userID, Role role)
+    protected GroupMemberships getGroupCache(Principal userID)
     {
         AccessControlContext acContext = AccessController.getContext();
         Subject subject = Subject.getSubject(acContext);
@@ -1065,43 +1055,81 @@ public class GMSClient implements TransferListener
         // only consult cache if the userID is of the calling subject
         if (userIsSubject(userID, subject))
         {
-            Set groupCredentialSet = subject.getPrivateCredentials(GroupMemberships.class);
-            if ((groupCredentialSet != null) && 
-                (groupCredentialSet.size() == 1))
+            Set<GroupMemberships> gset = subject.getPrivateCredentials(GroupMemberships.class);
+            if (gset == null || gset.isEmpty())
             {
-                Iterator i = groupCredentialSet.iterator();
-                GroupMemberships groupMemberships = ((GroupMemberships) i.next());
-                return groupMemberships.memberships.get(role);
+                GroupMemberships mems = new GroupMemberships();
+                subject.getPrivateCredentials().add(mems);
+                return mems;
             }
+            GroupMemberships mems = gset.iterator().next();
+            return mems;
         }
+        return null; // no cache
+    }
+    
+    protected Group getCachedGroup(Principal userID, String groupID, Role role)
+    {
+        List<Group> groups = getCachedGroups(userID, role, false);
+        if (groups == null)
+            return null; // no cache
+        for (Group g : groups)
+        {
+            if (g.getID().equals(groupID))
+                return g;
+        }
+        return null;
+    }
+    protected List<Group> getCachedGroups(Principal userID, Role role, boolean complete)
+    {
+        GroupMemberships mems = getGroupCache(userID);
+        if (mems == null)
+            return null; // no cache
+
+        Boolean cacheState = mems.complete.get(role);
+        if (!complete || Boolean.TRUE.equals(cacheState))
+            return mems.memberships.get(role);
+        
+        // caller wanted complete and we don't have that
         return null;
     }
 
+    protected void addCachedGroup(Principal userID, Group group, Role role)
+    {
+        GroupMemberships mems = getGroupCache(userID);
+        if (mems == null)
+            return; // no cache
+        
+        List<Group> groups = mems.memberships.get(role);
+        if (groups == null)
+        {
+            groups = new ArrayList<Group>();
+            mems.complete.put(role, Boolean.FALSE);
+            mems.memberships.put(role, groups);
+        }
+        if (!groups.contains(group))
+            groups.add(group);
+    }
+    
     protected void setCachedGroups(Principal userID, List<Group> groups, Role role)
     {
-        AccessControlContext acContext = AccessController.getContext();
-        Subject subject = Subject.getSubject(acContext);
+        GroupMemberships mems = getGroupCache(userID);
+        if (mems == null)
+            return; // no cache
         
-        // only save to cache if the userID is of the calling subject
-        if (userIsSubject(userID, subject))
+        log.debug("Caching groups for " + userID + ", role " + role);
+        List<Group> cur = mems.memberships.get(role);
+        if (cur == null)
         {
-            log.debug("Caching groups for " + userID + ", role " + role);
-            
-            final GroupMemberships groupCredentials;
-            Set groupCredentialSet = subject.getPrivateCredentials(GroupMemberships.class);
-            if ((groupCredentialSet != null) && 
-                (groupCredentialSet.size() == 1))
-            {
-                Iterator i = groupCredentialSet.iterator();
-                groupCredentials = ((GroupMemberships) i.next());
-            }
-            else
-            {
-                groupCredentials = new GroupMemberships();
-                subject.getPrivateCredentials().add(groupCredentials);
-            }
-            
-            groupCredentials.memberships.put(role,  groups);
+            cur = new ArrayList<Group>();
+            mems.complete.put(role, Boolean.FALSE);
+            mems.memberships.put(role, cur);
+        }
+        for (Group group : groups)
+        {
+            if (!cur.contains(group))
+                cur.add(group);
+            mems.complete.put(role, Boolean.TRUE);
         }
     }
     
@@ -1114,7 +1142,7 @@ public class GMSClient implements TransferListener
         
         for (Principal subjectPrincipal : subject.getPrincipals())
         {
-            if (subjectPrincipal.equals(userID))
+            if (AuthenticationUtil.equals(subjectPrincipal, userID))
             {
                 return true;
             }
@@ -1123,17 +1151,31 @@ public class GMSClient implements TransferListener
     }
 
     /**
-     * Class used to hold list of groups in which
-     * a user is a member.
+     * Class used to hold list of groups in which a user is known to be a member.
      */
-    protected class GroupMemberships
+    protected class GroupMemberships implements Comparable
     {
         Map<Role, List<Group>> memberships = new HashMap<Role, List<Group>>();
-
+        Map<Role, Boolean> complete = new HashMap<Role, Boolean>();
+        
         protected GroupMemberships()
         {
         }
 
+        // only allow one in a set - makes clearCache simple too
+        public boolean equals(Object rhs)
+        {
+            if (rhs != null && rhs instanceof GroupMemberships)
+                return true;
+            return false;
+        }
+
+        public int compareTo(Object t)
+        {
+            if (this.equals(t))
+                return 0;
+            return -1; // wonder if this is sketchy
+        }
     }
 
 }
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/client/GMSClientTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/client/GMSClientTest.java
index 3025fb37..dd228052 100644
--- a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/client/GMSClientTest.java
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/client/GMSClientTest.java
@@ -154,27 +154,38 @@ public class GMSClientTest
                     public Object run() throws Exception
                     {
 
-                        List<Group> initial = client.getCachedGroups(test1UserID, Role.MEMBER);
+                        List<Group> initial = client.getCachedGroups(test1UserID, Role.MEMBER, true);
                         Assert.assertNull("Cache should be null", initial);
+                        
+                        // add single group as isMember might do
+                        Group group0 = new Group("0");
+                        client.addCachedGroup(test1UserID, group0, Role.MEMBER);
+                        List<Group> actual = client.getCachedGroups(test1UserID, Role.MEMBER, true);
+                        Assert.assertNull("Cache should be null", actual);
+                        
+                        Group g = client.getCachedGroup(test1UserID, "0", Role.MEMBER);
+                        Assert.assertNotNull("cached group from incomplete cache", g);
 
+                        // add all groups like getMemberships might do
                         List<Group> expected = new ArrayList<Group>();
                         Group group1 = new Group("1");
                         Group group2 = new Group("2");
+                        expected.add(group0);
                         expected.add(group1);
                         expected.add(group2);
 
                         client.setCachedGroups(test1UserID, expected, Role.MEMBER);
 
-                        List<Group> actual = client.getCachedGroups(test1UserID, Role.MEMBER);
+                        actual = client.getCachedGroups(test1UserID, Role.MEMBER, true);
                         Assert.assertEquals("Wrong cached groups", expected, actual);
                         
                         // check against another role
-                        actual = client.getCachedGroups(test1UserID, Role.OWNER);
+                        actual = client.getCachedGroups(test1UserID, Role.OWNER, true);
                         Assert.assertNull("Cache should be null", actual);
                         
                         // check against another userid
                         final HttpPrincipal anotherUserID = new HttpPrincipal("anotheruser");
-                        actual = client.getCachedGroups(anotherUserID, Role.MEMBER);
+                        actual = client.getCachedGroups(anotherUserID, Role.MEMBER, true);
                         Assert.assertNull("Cache should be null", actual);
 
                         return null;
@@ -192,7 +203,7 @@ public class GMSClientTest
                         public Object run() throws Exception
                         {
 
-                            List<Group> initial = client.getCachedGroups(test2UserID, Role.MEMBER);
+                            List<Group> initial = client.getCachedGroups(test2UserID, Role.MEMBER, true);
                             Assert.assertNull("Cache should be null", initial);
 
                             List<Group> expected = new ArrayList<Group>();
@@ -203,16 +214,16 @@ public class GMSClientTest
 
                             client.setCachedGroups(test2UserID, expected, Role.MEMBER);
 
-                            List<Group> actual = client.getCachedGroups(test2UserID, Role.MEMBER);
+                            List<Group> actual = client.getCachedGroups(test2UserID, Role.MEMBER, true);
                             Assert.assertEquals("Wrong cached groups", expected, actual);
                             
                             // check against another role
-                            actual = client.getCachedGroups(test2UserID, Role.OWNER);
+                            actual = client.getCachedGroups(test2UserID, Role.OWNER, true);
                             Assert.assertNull("Cache should be null", actual);
                             
                             // check against another userid
                             final HttpPrincipal anotherUserID = new HttpPrincipal("anotheruser");
-                            actual = client.getCachedGroups(anotherUserID, Role.MEMBER);
+                            actual = client.getCachedGroups(anotherUserID, Role.MEMBER, true);
                             Assert.assertNull("Cache should be null", actual);
 
                             return null;
@@ -221,7 +232,7 @@ public class GMSClientTest
 
             // do the same without a subject
 
-            List<Group> initial = client.getCachedGroups(test1UserID, Role.MEMBER);
+            List<Group> initial = client.getCachedGroups(test1UserID, Role.MEMBER, true);
             Assert.assertNull("Cache should be null", initial);
 
             List<Group> newgroups = new ArrayList<Group>();
@@ -232,7 +243,7 @@ public class GMSClientTest
 
             client.setCachedGroups(test1UserID, newgroups, Role.MEMBER);
 
-            List<Group> actual = client.getCachedGroups(test1UserID, Role.MEMBER);
+            List<Group> actual = client.getCachedGroups(test1UserID, Role.MEMBER, true);
             Assert.assertNull("Cache should still be null", actual);
         }
         catch (Throwable t)
-- 
GitLab