java: Add helper method to filter Permissions by tags

This was added to help simplify permissions management for Syncbase.

Note: This might belong in the security package in the future though.
Change-Id: I8e42b919610b6f0816de13c1a7777b91f4f22d11
diff --git a/lib/src/main/java/io/v/v23/syncbase/util/Util.java b/lib/src/main/java/io/v/v23/syncbase/util/Util.java
index c3429fd..ecf197a 100644
--- a/lib/src/main/java/io/v/v23/syncbase/util/Util.java
+++ b/lib/src/main/java/io/v/v23/syncbase/util/Util.java
@@ -16,12 +16,16 @@
 import io.v.v23.VFutures;
 import io.v.v23.context.VContext;
 import io.v.v23.naming.GlobReply;
+import io.v.v23.security.access.AccessList;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.security.access.Tag;
 import io.v.v23.services.syncbase.CollectionRowPattern;
 import io.v.v23.services.syncbase.Id;
 import io.v.v23.verror.VException;
 
 import java.io.UnsupportedEncodingException;
 import java.text.Collator;
+import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
 
@@ -247,6 +251,23 @@
     }
 
     /**
+     * Returns a filtered copy of the given permissions, only including the tags specified.
+     */
+    public static Permissions filterPermissionsByTags(Permissions perms, Iterable<Tag> tags) {
+        Permissions filtered = new Permissions();
+        for (Tag tag: tags) {
+            String tagStr = tag.getValue();
+            AccessList acl = perms.get(tagStr);
+            if (acl != null) {
+                AccessList aclCopy = new AccessList(new ArrayList<>(acl.getIn()),
+                        new ArrayList<>(acl.getNotIn()));
+                filtered.put(tagStr, aclCopy);
+            }
+        }
+        return filtered;
+    }
+
+    /**
      * Compares two Syncbase Ids.
      * Blessing is compared first and then the name.
      */
diff --git a/lib/src/test/java/io/v/v23/syncbase/util/UtilTest.java b/lib/src/test/java/io/v/v23/syncbase/util/UtilTest.java
index 0670abe..161d7ab 100644
--- a/lib/src/test/java/io/v/v23/syncbase/util/UtilTest.java
+++ b/lib/src/test/java/io/v/v23/syncbase/util/UtilTest.java
@@ -10,10 +10,23 @@
 import io.v.v23.context.VContext;
 import io.v.v23.namespace.Namespace;
 import io.v.v23.naming.Endpoint;
+import io.v.v23.security.BlessingPattern;
+import io.v.v23.security.access.AccessList;
+import io.v.v23.security.access.Constants;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.security.access.Tag;
 import io.v.v23.services.syncbase.Id;
 import junit.framework.TestCase;
 import org.joda.time.Duration;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 import static com.google.common.truth.Truth.assertThat;
 import static io.v.v23.VFutures.sync;
 
@@ -40,4 +53,83 @@
         sync(n.mount(ctx, "appblessing,database/userblessing,collection2", dummyServerEndpoint.name(), Duration.standardDays(1)));
         assertThat(sync(Util.listChildIds(ctx, "appblessing,database"))).containsExactly(new Id("userblessing", "collection1"), new Id("userblessing", "collection2"));
     }
+
+    private static class FilterTagTestCase {
+        private Permissions input;
+        private Iterable<Tag> allowed;
+        private Permissions wanted;
+
+        private FilterTagTestCase(Permissions input, Iterable<Tag> allowed, Permissions wanted) {
+            this.input = input;
+            this.allowed = allowed;
+            this.wanted = wanted;
+        }
+    }
+
+    public void testFilterTags() {
+        List<FilterTagTestCase> filterTagTestCases = new ArrayList<>();
+        List<BlessingPattern> aclIn = new ArrayList<>();
+        aclIn.add(new BlessingPattern("alice"));
+        aclIn.add(new BlessingPattern("bob"));
+        aclIn.add(new BlessingPattern("carol"));
+        List<String> aclNotIn = new ArrayList<>();
+        aclNotIn.add("alice:enemy");
+        AccessList acl = new AccessList(aclIn, aclNotIn);
+
+        Map<String, AccessList> mapping = new HashMap<>();
+        mapping.put(Constants.DEBUG.getValue(), acl);
+        mapping.put(Constants.RESOLVE.getValue(), acl);
+        mapping.put(Constants.READ.getValue(), acl);
+        mapping.put(Constants.ADMIN.getValue(), acl);
+
+        Permissions canonicalPerms = new Permissions(mapping);
+
+        Map<String, AccessList> noDebugMapping = new HashMap<>(mapping);
+        noDebugMapping.remove(Constants.DEBUG.getValue());
+        Permissions noDebugPerms = new Permissions(noDebugMapping);
+        Set<Tag> noDebugTags = new HashSet<>();
+        Collections.addAll(noDebugTags, Constants.RESOLVE, Constants.READ, Constants.WRITE,
+                Constants.ADMIN);
+
+        Map<String, AccessList> noResolveReadMapping = new HashMap<>(mapping);
+        noResolveReadMapping.remove(Constants.RESOLVE.getValue());
+        noResolveReadMapping.remove(Constants.READ.getValue());
+        Permissions noResolveReadPerms = new Permissions(noResolveReadMapping);
+        List<Tag> noResolveReadTags = new ArrayList<>();
+        Collections.addAll(noResolveReadTags, Constants.DEBUG, Constants.WRITE, Constants.ADMIN);
+
+        List<Tag> allTags = new ArrayList<>();
+        Collections.addAll(allTags, Constants.DEBUG, Constants.RESOLVE, Constants.READ,
+                Constants.WRITE, Constants.ADMIN);
+
+        filterTagTestCases.add(new FilterTagTestCase(new Permissions(), new ArrayList<Tag>(),
+                new Permissions()));
+        filterTagTestCases.add(new FilterTagTestCase(canonicalPerms, new HashSet<Tag>(),
+                new Permissions()));
+        filterTagTestCases.add(new FilterTagTestCase(canonicalPerms, noDebugTags, noDebugPerms));
+        filterTagTestCases.add(new FilterTagTestCase(canonicalPerms, noResolveReadTags,
+                noResolveReadPerms));
+        filterTagTestCases.add(new FilterTagTestCase(canonicalPerms, allTags, canonicalPerms));
+
+        // Confirm that things match up correctly.
+        for (FilterTagTestCase test : filterTagTestCases) {
+            Permissions actualPerms = Util.filterPermissionsByTags(test.input, test.allowed);
+            assertEquals(actualPerms, test.wanted);
+
+            // Confirm the filtered version is independent from the original.
+            Permissions origPerms = test.input;
+            String adminStr = Constants.ADMIN.getValue();
+            if (actualPerms.get(adminStr) != null) {
+                List<BlessingPattern> actualIn = actualPerms.get(adminStr).getIn();
+                List<BlessingPattern> origIn = origPerms.get(adminStr).getIn();
+
+                // It's possible that the admin access list is the same reference instead of copied.
+                // Confirm equality. Change the admin access list. Then confirm inequality.
+                assertEquals(actualIn, origIn);
+                actualIn.clear();
+                assertFalse(actualIn.equals(origIn));
+            }
+
+        }
+    }
 }