syncbase: Infer id blessings and enforce on creation.

Id blessings in database, collection, and syncgroup names are
properly inferred from the context - preferring app and app:user,
falling back to ... and user. Inference fails if ambiguous
(blessings for different apps/users or no conventional blessings).

Perms are sanity checked to be non-empty, contain at least one admin,
and contain only tags relevant to the hierarchy level (DB: XRWA,
Collection: RWA, SG: RA).

Passing nil perms when creating a database or collection now defaults
to giving the creator all permissions instead of inheriting from the
parent in the hierarchy.

Implicit permissions are enforced for database, collection, and
syncgroup creation - the creator must have a blessing that matches
the blessing pattern in the id. This requirement is waived for service
admins when creating databases, but not in other cases (collection and
syncgroup metadata is synced, so the chain of trust must not be broken).

Also fixed glob (double encode step).

MultiPart: 3/4
Change-Id: I976504ec729804c91b1ba5cd5fb83cfea9add047
diff --git a/lib/src/main/java/io/v/v23/syncbase/CollectionImpl.java b/lib/src/main/java/io/v/v23/syncbase/CollectionImpl.java
index ac33577..b9eef94 100644
--- a/lib/src/main/java/io/v/v23/syncbase/CollectionImpl.java
+++ b/lib/src/main/java/io/v/v23/syncbase/CollectionImpl.java
@@ -4,10 +4,15 @@
 
 package io.v.v23.syncbase;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.util.concurrent.ListenableFuture;
 import io.v.impl.google.naming.NamingUtil;
 import io.v.v23.InputChannel;
 import io.v.v23.context.VContext;
+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.services.syncbase.BatchHandle;
 import io.v.v23.services.syncbase.CollectionClient;
@@ -43,6 +48,16 @@
 
     @Override
     public ListenableFuture<Void> create(VContext ctx, Permissions perms) {
+        if (perms == null) {
+            // Default to giving full permissions to the creator.
+            String blessing = Util.UserBlessingFromContext(ctx);
+            AccessList acl = new AccessList(
+                ImmutableList.of(new BlessingPattern(blessing)), ImmutableList.<String>of());
+            perms = new Permissions(ImmutableMap.of(
+                Constants.READ.getValue(), acl,
+                Constants.WRITE.getValue(), acl,
+                Constants.ADMIN.getValue(), acl));
+        }
         return client.create(ctx, this.batchHandle, perms);
     }
 
diff --git a/lib/src/main/java/io/v/v23/syncbase/DatabaseImpl.java b/lib/src/main/java/io/v/v23/syncbase/DatabaseImpl.java
index 39c2e02..0d3e4eb 100644
--- a/lib/src/main/java/io/v/v23/syncbase/DatabaseImpl.java
+++ b/lib/src/main/java/io/v/v23/syncbase/DatabaseImpl.java
@@ -18,6 +18,9 @@
 import io.v.v23.InputChannels;
 import io.v.v23.VFutures;
 import io.v.v23.context.VContext;
+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.services.permissions.ObjectClient;
 import io.v.v23.services.syncbase.BatchOptions;
@@ -154,6 +157,17 @@
         VdlOptional metadataOpt = schema != null
                 ? VdlOptional.of(schema.getMetadata())
                 : new VdlOptional<SchemaMetadata>(Types.optionalOf(SchemaMetadata.VDL_TYPE));
+        if (perms == null) {
+            // Default to giving full permissions to the creator.
+            String blessing = Util.UserBlessingFromContext(ctx);
+            AccessList acl = new AccessList(
+                ImmutableList.of(new BlessingPattern(blessing)), ImmutableList.<String>of());
+            perms = new Permissions(ImmutableMap.of(
+                Constants.RESOLVE.getValue(), acl,
+                Constants.READ.getValue(), acl,
+                Constants.WRITE.getValue(), acl,
+                Constants.ADMIN.getValue(), acl));
+        }
         return client.create(ctx, metadataOpt, perms);
     }
 
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 ecf197a..cff0476 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
@@ -236,8 +236,11 @@
      * Returns the app blessing obtained from the context.
      */
     public static String AppBlessingFromContext(VContext ctx) {
-        // NOTE(sadovsky): For now, we use a blessing string that will be easy to
-        // find-replace when we actually implement this method.
+        // TODO(ivanpi,sadovsky): Hook up the Go implementation through the new
+        // Cgo bridge. For now, Java clients must provide explicit id blessings
+        // and perms.
+        // TODO(ivanpi): Add id blessing and perms inference tests when this is
+        // fixed.
         return "v.io:a:xyz";
     }
 
@@ -245,8 +248,11 @@
      * Returns the user blessing obtained from the context.
      */
     public static String UserBlessingFromContext(VContext ctx) {
-        // NOTE(sadovsky): For now, we use a blessing string that will be easy to
-        // find-replace when we actually implement this method.
+        // TODO(ivanpi,sadovsky): Hook up the Go implementation through the new
+        // Cgo bridge. For now, Java clients must provide explicit id blessings
+        // and perms.
+        // TODO(ivanpi): Add id blessing and perms inference tests when this is
+        // fixed.
         return "v.io:u:sam";
     }
 
diff --git a/lib/src/test/java/io/v/v23/syncbase/SyncbaseTest.java b/lib/src/test/java/io/v/v23/syncbase/SyncbaseTest.java
index 5be89ca..13246e7 100644
--- a/lib/src/test/java/io/v/v23/syncbase/SyncbaseTest.java
+++ b/lib/src/test/java/io/v/v23/syncbase/SyncbaseTest.java
@@ -21,7 +21,10 @@
 import io.v.v23.rpc.ListenSpec;
 import io.v.v23.rpc.Server;
 import io.v.v23.security.BlessingPattern;
+import io.v.v23.security.Blessings;
 import io.v.v23.security.Caveat;
+import io.v.v23.security.VPrincipal;
+import io.v.v23.security.VSecurity;
 import io.v.v23.security.access.AccessList;
 import io.v.v23.security.access.Constants;
 import io.v.v23.security.access.Permissions;
@@ -59,42 +62,67 @@
  * Client-server syncbase tests.
  */
 public class SyncbaseTest extends TestCase {
-    private static final Id DB_ID = new Id("v.io:a:xyz", "db");
+    private static final Id DB_ID = new Id("jroot:o:coffee", "db");
     private static final String COLLECTION_NAME = "collection";
-    private static final Id COLLECTION_ID = new Id("v.io:u:sam", COLLECTION_NAME);
+    private static final Id COLLECTION_ID = new Id("jroot:o:coffee:bean", COLLECTION_NAME);
     private static final String ROW_NAME = "row/a#%b";  // symbols are okay
     private static final String ROW_NAME2 = "row/a#%c";  // symbols are okay
     private static final String ROW_NAME3 = "row/a#%d";  // symbols are okay
 
+    private VContext rootCtx;
     private VContext ctx;
-    private Permissions allowAll;
+    private Permissions allowAllDb;
+    private Permissions allowAllCx;
+    private Permissions allowAllSg;
     private Endpoint serverEndpoint;
 
     @Override
     protected void setUp() throws Exception {
-        ctx = V.init();
-        ctx = V.withListenSpec(ctx, V.getListenSpec(ctx).withAddress(
+        rootCtx = V.init();
+        VPrincipal rootPrincipal = VSecurity.newPrincipal();
+        Blessings blessings = rootPrincipal.blessSelf("jroot");
+        rootPrincipal.blessingStore().setDefaultBlessings(blessings);
+        rootPrincipal.blessingStore().set(blessings, new BlessingPattern("..."));
+        rootPrincipal.roots().add(rootPrincipal.publicKey(), new BlessingPattern("jroot"));
+        rootCtx = V.withPrincipal(rootCtx, rootPrincipal);
+        // Note, the client should bless the server. This doesn't currently happen, but it doesn't
+        // affect any of the Java tests. A workaround used in Go tests is manually blessing the
+        // server with the same extension as the client, or adding the server blessing to all ACLs.
+        VContext serverCtx = forkContext(rootCtx, "r:server");
+        serverCtx = V.withListenSpec(serverCtx, V.getListenSpec(serverCtx).withAddress(
                 new ListenSpec.Address("tcp", "localhost:0")));
         AccessList acl = new AccessList(
-                ImmutableList.of(new BlessingPattern("...")), ImmutableList.<String>of());
-        allowAll = new Permissions(ImmutableMap.of(
+                ImmutableList.of(
+                    new BlessingPattern("jroot:o:coffee:bean"),
+                    new BlessingPattern("jroot:u")),
+                ImmutableList.<String>of());
+        allowAllDb = new Permissions(ImmutableMap.of(
+                Constants.RESOLVE.getValue(), acl,
                 Constants.READ.getValue(), acl,
                 Constants.WRITE.getValue(), acl,
                 Constants.ADMIN.getValue(), acl));
+        allowAllCx = new Permissions(ImmutableMap.of(
+                Constants.READ.getValue(), acl,
+                Constants.WRITE.getValue(), acl,
+                Constants.ADMIN.getValue(), acl));
+        allowAllSg = new Permissions(ImmutableMap.of(
+                Constants.READ.getValue(), acl,
+                Constants.ADMIN.getValue(), acl));
         String tmpDir = Files.createTempDir().getAbsolutePath();
-        ctx = SyncbaseServer.withNewServer(ctx, new SyncbaseServer.Params()
-                .withPermissions(allowAll)
+        serverCtx = SyncbaseServer.withNewServer(serverCtx, new SyncbaseServer.Params()
+                .withPermissions(allowAllDb)
                 .withStorageRootDir(tmpDir));
-        Server server = V.getServer(ctx);
+        Server server = V.getServer(serverCtx);
         assertThat(server).isNotNull();
         Endpoint[] endpoints = server.getStatus().getEndpoints();
         assertThat(endpoints).isNotEmpty();
         serverEndpoint = endpoints[0];
+        ctx = forkContext(rootCtx, "o:coffee:bean:phone");
     }
 
     @Override
     protected void tearDown() throws Exception {
-        ctx.cancel();
+        rootCtx.cancel();
     }
 
     public void testService() throws Exception {
@@ -114,7 +142,7 @@
                 NamingUtil.join(serverEndpoint.name(), Util.encodeId(DB_ID)));
         assertThat(sync(db.exists(ctx))).isFalse();
         assertThat(sync(service.listDatabases(ctx))).isEmpty();
-        sync(db.create(ctx, allowAll));
+        sync(db.create(ctx, allowAllDb));
         assertThat(sync(db.exists(ctx))).isTrue();
         assertThat(sync(service.listDatabases(ctx))).containsExactly(db.id());
         assertThat(sync(db.listCollections(ctx))).isEmpty();
@@ -134,7 +162,7 @@
                         Util.encodeId(COLLECTION_ID)));
         assertThat(sync(collection.exists(ctx))).isFalse();
         assertThat(sync(db.listCollections(ctx))).isEmpty();
-        sync(collection.create(ctx, allowAll));
+        sync(collection.create(ctx, allowAllCx));
         assertThat(sync(collection.exists(ctx))).isTrue();
         assertThat(sync(db.listCollections(ctx))).containsExactly(COLLECTION_ID);
 
@@ -387,10 +415,10 @@
         Database db = createDatabase(createService());
 
         // Create and populate two collections.
-        Collection cxAFoo = db.getCollection(new Id("v.io:u:alice", "foo"));
-        sync(cxAFoo.create(ctx, allowAll));
-        Collection cxBFoo = db.getCollection(new Id("v.io:u:bob", "foobar"));
-        sync(cxBFoo.create(ctx, allowAll));
+        Collection cxAFoo = db.getCollection(new Id("jroot:u:alice", "foo"));
+        sync(cxAFoo.create(forkContext(rootCtx, "u:alice"), allowAllCx));
+        Collection cxBFoo = db.getCollection(new Id("jroot:u:bob", "foobar"));
+        sync(cxBFoo.create(forkContext(rootCtx, "u:bob"), allowAllCx));
         Collection collection = createCollection(db);
         Foo foo = new Foo(42, "LUE");
 
@@ -407,13 +435,13 @@
         final VContext ctxC = ctx.withCancel();
         Iterator<WatchChange> it1 = InputChannels.asIterable(
                 db.watch(ctxC, ImmutableList.of(
-                        new CollectionRowPattern("v.io:u:%", "foo", "abc%"),
-                        new CollectionRowPattern("v.io:u:%", "foo%",
+                        new CollectionRowPattern("jroot:u:%", "foo", "abc%"),
+                        new CollectionRowPattern("jroot:u:%", "foo%",
                                 "%" + Util.escapePattern("c_")))))
                 .iterator();
         Iterator<WatchChange> it2 = InputChannels.asIterable(
                 db.watch(ctxC, ImmutableList.of(
-                        Util.rowPrefixPattern(new Id("v.io:u:bob", "foobar"), "bc_"),
+                        Util.rowPrefixPattern(new Id("jroot:u:bob", "foobar"), "bc_"),
                         new CollectionRowPattern("%:alice", "%", "%bcd"))))
                 .iterator();
 
@@ -444,8 +472,8 @@
         sync(cxBFoo.getRow("bc_").delete(ctx));
 
         // Create and populate another collection.
-        Collection cxAFoobar = db.getCollection(new Id("v.io:u:alice", "foobar"));
-        sync(cxAFoobar.create(ctx, allowAll));
+        Collection cxAFoobar = db.getCollection(new Id("jroot:u:alice", "foobar"));
+        sync(cxAFoobar.create(forkContext(rootCtx, "u:alice"), allowAllCx));
         sync(cxAFoobar.put(ctx, "abcd", foo));
         sync(cxAFoobar.put(ctx, "abc_", foo));
 
@@ -585,10 +613,10 @@
     public void testSyncgroup() throws Exception {
         Database db = createDatabase(createService());
         Collection collection = createCollection(db);
-        Id syncgroupId = new Id("blessing", "test");
+        Id syncgroupId = new Id(COLLECTION_ID.getBlessing(), "test");
 
         // "A" creates the group.
-        SyncgroupSpec spec = new SyncgroupSpec("test", "", allowAll,
+        SyncgroupSpec spec = new SyncgroupSpec("test", "", allowAllSg,
                 ImmutableList.of(COLLECTION_ID),
                 ImmutableList.<String>of(), false);
         SyncgroupMemberInfo memberInfo = new SyncgroupMemberInfo();
@@ -603,7 +631,7 @@
         }
         // TODO(spetrovic): test leave() and destroy().
 
-        SyncgroupSpec specRMW = new SyncgroupSpec("testRMW", "", allowAll,
+        SyncgroupSpec specRMW = new SyncgroupSpec("testRMW", "", allowAllSg,
                 ImmutableList.of(COLLECTION_ID),
                 ImmutableList.<String>of(), false);
         assertThat(sync(group.getSpec(ctx)).keySet()).isNotEmpty();
@@ -611,7 +639,7 @@
         sync(group.setSpec(ctx, specRMW, version));
         assertThat(sync(group.getSpec(ctx)).values()).containsExactly(specRMW);
 
-        SyncgroupSpec specOverwrite = new SyncgroupSpec("testOverwrite", "", allowAll,
+        SyncgroupSpec specOverwrite = new SyncgroupSpec("testOverwrite", "", allowAllSg,
                 ImmutableList.of(COLLECTION_ID),
                 ImmutableList.<String>of(), false);
         sync(group.setSpec(ctx, specOverwrite, ""));
@@ -811,6 +839,18 @@
     }
 
 
+    private VContext forkContext(VContext rootCtx, String extension) throws VException {
+        VPrincipal rootPrincipal = V.getPrincipal(rootCtx);
+        VPrincipal principal = VSecurity.newPrincipal();
+        Blessings blessings = rootPrincipal.bless(principal.publicKey(),
+                rootPrincipal.blessingStore().defaultBlessings(), extension,
+                VSecurity.newUnconstrainedUseCaveat());
+        principal.blessingStore().setDefaultBlessings(blessings);
+        principal.blessingStore().set(blessings, new BlessingPattern("..."));
+        principal.roots().add(rootPrincipal.publicKey(), new BlessingPattern("jroot"));
+        return V.withPrincipal(rootCtx, principal);
+    }
+
     private SyncbaseService createService() throws Exception {
         return Syncbase.newService(serverEndpoint.name());
     }
@@ -818,13 +858,13 @@
 
     private Database createDatabase(SyncbaseService service) throws Exception {
         Database db = service.getDatabase(DB_ID, null);
-        sync(db.create(ctx, allowAll));
+        sync(db.create(ctx, allowAllDb));
         return db;
     }
 
     private Collection createCollection(Database db) throws Exception {
         Collection collection = db.getCollection(COLLECTION_ID);
-        sync(collection.create(ctx, allowAll));
+        sync(collection.create(ctx, allowAllCx));
         return collection;
     }