services/mgmt/application/applicationd: rationalize permission list behaviour

Modify the implementation of permission lists in applicationd to

1. set a permission list associated with a specific application at the
time of uploading.

2. have the same permission list for all versions of an application
envelope.

Change-Id: Ie7a3875a11d639d16859f4b86316e4de0397b688
diff --git a/services/application/applicationd/acl_test.go b/services/application/applicationd/acl_test.go
index bfefc2f..86f5713 100644
--- a/services/application/applicationd/acl_test.go
+++ b/services/application/applicationd/acl_test.go
@@ -121,15 +121,32 @@
 		t.Fatalf("Put() failed: %v", err)
 	}
 
+	vlog.VI(2).Infof("Accessing the Permission Lists of the root returns a (simulated) list providing default authorization.")
 	acl, etag, err := repostub.GetPermissions(ctx)
-	if verror.ErrorID(err) != verror.ErrNoExist.ID {
-		t.Fatalf("GetPermissions should have failed with ErrNoExist but was: %v", err)
+	if err != nil {
+		t.Fatalf("GetPermissions should not have failed: %v", err)
 	}
 	if got, want := etag, ""; got != want {
 		t.Fatalf("GetPermissions got %v, want %v", got, want)
 	}
-	if acl != nil {
-		t.Fatalf("GetPermissions got %v, expected %v", acl, nil)
+	expected := access.Permissions{
+		"Admin": access.AccessList{
+			In:    []security.BlessingPattern{"root/$", "root/self/$", "root/self/child"},
+			NotIn: []string(nil)},
+		"Read": access.AccessList{
+			In:    []security.BlessingPattern{"root/$", "root/self/$", "root/self/child"},
+			NotIn: []string(nil)},
+		"Write": access.AccessList{
+			In:    []security.BlessingPattern{"root/$", "root/self/$", "root/self/child"},
+			NotIn: []string(nil)},
+		"Debug": access.AccessList{
+			In:    []security.BlessingPattern{"root/$", "root/self/$", "root/self/child"},
+			NotIn: []string(nil)},
+		"Resolve": access.AccessList{
+			In:    []security.BlessingPattern{"root/$", "root/self/$", "root/self/child"},
+			NotIn: []string(nil)}}
+	if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+		t.Errorf("got %#v, exected %#v ", got, expected)
 	}
 
 	vlog.VI(2).Infof("self attempting to give other permission to update application")
@@ -146,7 +163,7 @@
 	if err != nil {
 		t.Fatalf("GetPermissions should not have failed: %v", err)
 	}
-	expected := newAccessList
+	expected = newAccessList
 	if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
 		t.Errorf("got %#v, exected %#v ", got, expected)
 	}
@@ -246,20 +263,37 @@
 	if err := v2stub.Put(ctx, []string{"base"}, envelopeV1); err != nil {
 		t.Fatalf("Put() failed: %v", err)
 	}
+	v3stub := repository.ApplicationClient("repo/naps/v1")
+	if err := v3stub.Put(ctx, []string{"base"}, envelopeV1); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
 
 	vlog.VI(2).Info("Self can access.AccessLists but other can't.")
-	for _, path := range []string{"repo/search", "repo/search/v1", "repo/search/v2"} {
+	expectedSelfPermissions := access.Permissions{
+		"Admin": access.AccessList{
+			In:    []security.BlessingPattern{"root/$", "root/self"},
+			NotIn: []string{}},
+		"Read": access.AccessList{In: []security.BlessingPattern{"root/$", "root/self"},
+			NotIn: []string{}},
+		"Write": access.AccessList{In: []security.BlessingPattern{"root/$", "root/self"},
+			NotIn: []string{}},
+		"Debug": access.AccessList{In: []security.BlessingPattern{"root/$", "root/self"},
+			NotIn: []string{}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{"root/$", "root/self"},
+			NotIn: []string{}}}
+
+	for _, path := range []string{"repo/search", "repo/search/v1", "repo/search/v2", "repo/naps", "repo/naps/v1"} {
 		stub := repository.ApplicationClient(path)
-		acl, etag, err := stub.GetPermissions(ctx)
-		if verror.ErrorID(err) != verror.ErrNoExist.ID {
-			t.Fatalf("GetPermissions should have failed with ErrNoExist but was: %v", err)
+		acl, _, err := stub.GetPermissions(ctx)
+		if err != nil {
+			t.Fatalf("Newly uploaded envelopes failed to receive permission lists: %v", err)
 		}
-		if got, want := etag, ""; got != want {
-			t.Fatalf("GetPermissions got %v, want %v", got, want)
+
+		if got := acl; !reflect.DeepEqual(expectedSelfPermissions.Normalize(), got.Normalize()) {
+			t.Errorf("got %#v, expected %#v ", got, expectedSelfPermissions)
 		}
-		if acl != nil {
-			t.Fatalf("GetPermissions got %v, expected %v", acl, nil)
-		}
+
+		// But otherCtx doesn't have admin permissions so has no access.
 		if _, _, err := stub.GetPermissions(otherCtx); err == nil {
 			t.Fatalf("GetPermissions didn't fail for other when it should have.")
 		}
@@ -280,50 +314,72 @@
 		t.Fatalf("GetPermissions should have failed")
 	}
 
-	vlog.VI(2).Infof("Self gives other full access only to repo/search/v1.")
-	newAccessList = make(access.Permissions)
-	for _, tag := range access.AllTypicalTags() {
-		newAccessList.Add("root/other", string(tag))
-	}
-	if err := v1stub.SetPermissions(ctx, newAccessList, ""); err != nil {
-		t.Fatalf("SetPermissions failed: %v", err)
-	}
-
-	vlog.VI(2).Infof("Other can now access this location.")
-	acl, _, err := v1stub.GetPermissions(otherCtx)
+	vlog.VI(2).Infof("Self gives other full access to repo/search/...")
+	newAccessList, etag, err := v1stub.GetPermissions(ctx)
 	if err != nil {
 		t.Fatalf("GetPermissions should not have failed: %v", err)
 	}
-	expected := access.Permissions{
-		"Admin": access.AccessList{
-			In:    []security.BlessingPattern{"root/other"},
-			NotIn: []string{}},
-		"Read": access.AccessList{In: []security.BlessingPattern{"root/other"},
-			NotIn: []string{}},
-		"Write": access.AccessList{In: []security.BlessingPattern{"root/other"},
-			NotIn: []string{}},
-		"Debug": access.AccessList{In: []security.BlessingPattern{"root/other"},
-			NotIn: []string{}},
-		"Resolve": access.AccessList{In: []security.BlessingPattern{"root/other"},
-			NotIn: []string{}}}
-	if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
-		t.Errorf("got %#v, exected %#v ", got, expected)
+	for _, tag := range access.AllTypicalTags() {
+		newAccessList.Add("root/other", string(tag))
 	}
-	vlog.VI(2).Infof("Self can too thanks to hierarchical auth.")
-	if _, _, err = v1stub.GetPermissions(ctx); err != nil {
-		t.Fatalf("GetPermissions should not have failed: %v", err)
+	if err := v1stub.SetPermissions(ctx, newAccessList, etag); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
 	}
 
-	// But other locations should be unaffected and other cannot access.
-	for _, path := range []string{"repo/search", "repo/search/v2"} {
+	expected := access.Permissions{
+		"Resolve": access.AccessList{In: []security.BlessingPattern{
+			"root/$",
+			"root/other",
+			"root/self"},
+			NotIn: []string(nil)},
+		"Admin": access.AccessList{In: []security.BlessingPattern{
+			"root/$",
+			"root/other",
+			"root/self"},
+			NotIn: []string(nil)},
+		"Read": access.AccessList{In: []security.BlessingPattern{
+			"root/$",
+			"root/other",
+			"root/self"},
+			NotIn: []string(nil)},
+		"Write": access.AccessList{In: []security.BlessingPattern{
+			"root/$",
+			"root/other",
+			"root/self"},
+			NotIn: []string(nil)},
+		"Debug": access.AccessList{In: []security.BlessingPattern{
+			"root/$",
+			"root/other", "root/self"},
+			NotIn: []string(nil)},
+	}
+
+	for _, path := range []string{"repo/search", "repo/search/v1", "repo/search/v2"} {
+		stub := repository.ApplicationClient(path)
+		vlog.VI(2).Infof("Other can now access this app independent of version.")
+		acl, _, err := stub.GetPermissions(otherCtx)
+		if err != nil {
+			t.Fatalf("GetPermissions should not have failed: %v", err)
+		}
+
+		if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+			t.Errorf("got %#v, expected %#v ", got, expected)
+		}
+		vlog.VI(2).Infof("Self can also access thanks to hierarchical auth.")
+		if _, _, err = stub.GetPermissions(ctx); err != nil {
+			t.Fatalf("GetPermissions should not have failed: %v", err)
+		}
+	}
+
+	vlog.VI(2).Infof("But other locations are unaffected and other cannot access.")
+	for _, path := range []string{"repo/naps", "repo/naps/v1"} {
 		stub := repository.ApplicationClient(path)
 		if _, _, err := stub.GetPermissions(otherCtx); err == nil {
-			t.Fatalf("GetPermissions didn't fail for other when it should have.")
+			t.Fatalf("GetPermissions didn't fail when it should have.")
 		}
 	}
 
 	// Self gives other write perms on base.
-	acl, etag, err := repostub.GetPermissions(ctx)
+	newAccessList, etag, err = repostub.GetPermissions(ctx)
 	if err != nil {
 		t.Fatalf("GetPermissions should not have failed: %v", err)
 	}
@@ -339,11 +395,23 @@
 		}
 	}
 
+	// But because application search already exists, the ACLs do not change.
+	for _, path := range []string{"repo/search", "repo/search/v1", "repo/search/v2"} {
+		stub := repository.ApplicationClient(path)
+		acl, _, err := stub.GetPermissions(otherCtx)
+		if err != nil {
+			t.Fatalf("GetPermissions should not have failed: %v", err)
+		}
+		if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+			t.Errorf("got %#v, expected %#v ", got, expected)
+		}
+	}
+
 	// But self didn't give other AccessList modification permissions.
 	for _, path := range []string{"repo/search", "repo/search/v2"} {
 		stub := repository.ApplicationClient(path)
-		if _, _, err := stub.GetPermissions(otherCtx); err == nil {
-			t.Fatalf("GetPermissions didn't fail for other when it should have.")
+		if _, _, err := stub.GetPermissions(otherCtx); err != nil {
+			t.Fatalf("GetPermissions failed when it should not have for same application: %v", err)
 		}
 	}
 }
diff --git a/services/application/applicationd/dispatcher.go b/services/application/applicationd/dispatcher.go
index 4c5aa1c..6a9becd 100644
--- a/services/application/applicationd/dispatcher.go
+++ b/services/application/applicationd/dispatcher.go
@@ -35,9 +35,14 @@
 }
 
 func (d *dispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
+	name, _, err := parse(nil, suffix)
+	if err != nil {
+		return nil, nil, err
+	}
+
 	auth, err := acls.NewHierarchicalAuthorizer(
 		naming.Join("/acls", "data"),
-		naming.Join("/acls", suffix, "data"),
+		naming.Join("/acls", name, "data"),
 		(*applicationAccessListStore)(d.store))
 	if err != nil {
 		return nil, nil, err
diff --git a/services/application/applicationd/service.go b/services/application/applicationd/service.go
index 9632cdf..07313b5 100644
--- a/services/application/applicationd/service.go
+++ b/services/application/applicationd/service.go
@@ -11,8 +11,10 @@
 	"v.io/x/ref/services/mgmt/lib/fs"
 	"v.io/x/ref/services/repository"
 
+	"v.io/v23/context"
 	"v.io/v23/naming"
 	"v.io/v23/rpc"
+	"v.io/v23/security"
 	"v.io/v23/security/access"
 	"v.io/v23/services/application"
 	"v.io/v23/verror"
@@ -37,7 +39,7 @@
 var (
 	ErrInvalidSuffix   = verror.Register(pkgPath+".InvalidSuffix", verror.NoRetry, "{1:}{2:} invalid suffix{:_}")
 	ErrOperationFailed = verror.Register(pkgPath+".OperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
-	ErrInvalidBlessing = verror.Register(pkgPath+".InvalidBlessing", verror.NoRetry, "{1:}{2:} invalid blessing{:_}")
+	ErrNotAuthorized   = verror.Register(pkgPath+".errNotAuthorized", verror.NoRetry, "{1:}{2:} none of the client's blessings are valid {:_}")
 )
 
 // NewApplicationService returns a new Application service implementation.
@@ -45,7 +47,7 @@
 	return &appRepoService{store: store, storeRoot: storeRoot, suffix: suffix}
 }
 
-func parse(call rpc.ServerCall, suffix string) (string, string, error) {
+func parse(ctx *context.T, suffix string) (string, string, error) {
 	tokens := strings.Split(suffix, "/")
 	switch len(tokens) {
 	case 2:
@@ -53,14 +55,14 @@
 	case 1:
 		return tokens[0], "", nil
 	default:
-		return "", "", verror.New(ErrInvalidSuffix, call.Context())
+		return "", "", verror.New(ErrInvalidSuffix, ctx)
 	}
 }
 
 func (i *appRepoService) Match(call rpc.ServerCall, profiles []string) (application.Envelope, error) {
 	vlog.VI(0).Infof("%v.Match(%v)", i.suffix, profiles)
 	empty := application.Envelope{}
-	name, version, err := parse(call, i.suffix)
+	name, version, err := parse(call.Context(), i.suffix)
 	if err != nil {
 		return empty, err
 	}
@@ -88,7 +90,7 @@
 
 func (i *appRepoService) Put(call rpc.ServerCall, profiles []string, envelope application.Envelope) error {
 	vlog.VI(0).Infof("%v.Put(%v, %v)", i.suffix, profiles, envelope)
-	name, version, err := parse(call, i.suffix)
+	name, version, err := parse(call.Context(), i.suffix)
 	if err != nil {
 		return err
 	}
@@ -103,6 +105,22 @@
 		return err
 	}
 
+	// Only add a Permission list value if there is not already one
+	// present.
+	apath := naming.Join("/acls", name, "data")
+	aobj := i.store.BindObject(apath)
+	if _, err := aobj.Get(call); verror.ErrorID(err) == fs.ErrNotInMemStore.ID {
+		rb, _ := security.RemoteBlessingNames(call.Context())
+		if len(rb) == 0 {
+			// None of the client's blessings are valid.
+			return verror.New(ErrNotAuthorized, call.Context())
+		}
+		newacls := acls.PermissionsForBlessings(rb)
+		if _, err := aobj.Put(nil, newacls); err != nil {
+			return err
+		}
+	}
+
 	for _, profile := range profiles {
 		path := naming.Join(tname, "/applications", name, profile, version)
 
@@ -120,7 +138,7 @@
 
 func (i *appRepoService) Remove(call rpc.ServerCall, profile string) error {
 	vlog.VI(0).Infof("%v.Remove(%v)", i.suffix, profile)
-	name, version, err := parse(call, i.suffix)
+	name, version, err := parse(call.Context(), i.suffix)
 	if err != nil {
 		return err
 	}
@@ -231,16 +249,30 @@
 }
 
 func (i *appRepoService) GetPermissions(call rpc.ServerCall) (acl access.Permissions, etag string, err error) {
+	name, _, err := parse(call.Context(), i.suffix)
+	if err != nil {
+		return nil, "", err
+	}
 	i.store.Lock()
 	defer i.store.Unlock()
-	path := naming.Join("/acls", i.suffix, "data")
-	return getAccessList(i.store, path)
+	path := naming.Join("/acls", name, "data")
+
+	acl, etag, err = getAccessList(i.store, path)
+	if verror.ErrorID(err) == verror.ErrNoExist.ID {
+		return acls.NilAuthPermissions(call), "", nil
+	}
+
+	return acl, etag, err
 }
 
 func (i *appRepoService) SetPermissions(call rpc.ServerCall, acl access.Permissions, etag string) error {
+	name, _, err := parse(call.Context(), i.suffix)
+	if err != nil {
+		return err
+	}
 	i.store.Lock()
 	defer i.store.Unlock()
-	path := naming.Join("/acls", i.suffix, "data")
+	path := naming.Join("/acls", name, "data")
 	return setAccessList(i.store, path, acl, etag)
 }
 
diff --git a/services/binary/binarylib/service.go b/services/binary/binarylib/service.go
index f0b2b7a..c73eb0d 100644
--- a/services/binary/binarylib/service.go
+++ b/services/binary/binarylib/service.go
@@ -376,18 +376,7 @@
 
 	if os.IsNotExist(err) {
 		// No AccessList file found which implies a nil authorizer. This results in default authorization.
-		// Therefore we return an AccessList that mimics the default authorization policy (i.e., the AccessList
-		// is matched by all blessings that are either extensions of one of the local blessings or
-		// can be extended to form one of the local blessings.)
-		tam := make(access.Permissions)
-
-		lb := security.LocalBlessingNames(call.Context())
-		for _, p := range acls.PrefixPatterns(lb) {
-			for _, tag := range access.AllTypicalTags() {
-				tam.Add(p, string(tag))
-			}
-		}
-		return tam, "", nil
+		return acls.NilAuthPermissions(call), "", nil
 	}
 	return acl, etag, err
 }
diff --git a/services/mgmt/lib/acls/aclaccess.go b/services/mgmt/lib/acls/aclaccess.go
index 62b0896..42d4154 100644
--- a/services/mgmt/lib/acls/aclaccess.go
+++ b/services/mgmt/lib/acls/aclaccess.go
@@ -14,6 +14,7 @@
 	"path/filepath"
 	"sync"
 
+	"v.io/v23/rpc"
 	"v.io/v23/security"
 	"v.io/v23/security/access"
 	"v.io/v23/verror"
@@ -198,3 +199,18 @@
 	}
 	return tam
 }
+
+// NilAuthPermissions creates an AccessList that mimics the default
+// authorization policy (i.e., the AccessList is matched by all blessings
+// that are either extensions of one of the local blessings or can be
+// extended to form one of the local blessings.)
+func NilAuthPermissions(call rpc.ServerCall) access.Permissions {
+	tam := make(access.Permissions)
+	lb := security.LocalBlessingNames(call.Context())
+	for _, p := range PrefixPatterns(lb) {
+		for _, tag := range access.AllTypicalTags() {
+			tam.Add(p, string(tag))
+		}
+	}
+	return tam
+}