services/mgmt/lib/acls: restructure the hierarchical authorizer for use by debug

The debug implementation uses the same authorizer over the lifetime of
the service. Modify the hierarchical authorizer to support monitoing
permission lists dynamically instead of requiring the creation of a
new Authorizer in each Lookup call.

Re-land with a modification: now makes the same number of attempts to
access the actual Permissions storage file as the upstream code. As a
result, this fixes a timing error in the Darwin integration tests.

Change-Id: Ib9defaa67554f95f7e64404f11fe36d4d9e39727
diff --git a/services/mgmt/lib/acls/hierarchical_authorizer.go b/services/mgmt/lib/acls/hierarchical_authorizer.go
index 142b59d..5b5e921 100644
--- a/services/mgmt/lib/acls/hierarchical_authorizer.go
+++ b/services/mgmt/lib/acls/hierarchical_authorizer.go
@@ -9,13 +9,15 @@
 	"v.io/v23/security"
 	"v.io/v23/security/access"
 	"v.io/x/lib/vlog"
+
+	"v.io/x/ref/profiles/internal/rpc"
 )
 
-// hierarchicalAuthorizer manages a pair of authorizers for two-level
-// inheritance of AccessLists.
+// hierarchicalAuthorizer contains the state needed to implement
+// hierarchical authorization in the Authorize method.
 type hierarchicalAuthorizer struct {
-	child          security.Authorizer
-	rootAccessList access.AccessList
+	rootDir, childDir string
+	get               TAMGetter
 }
 
 // TAMGetter defines an abstract interface that a customer of
@@ -38,48 +40,55 @@
 	return rootAuth, nil
 }
 
-// NewHierarchicalAuthorizer creates a new hierarchicalAuthorizer
+// NewHierarchicalAuthorizer creates a new hierarchicalAuthorizer: one
+// that implements a "root" like concept: admin rights at the root of
+// a server can invoke RPCs regardless of permissions set on child objects.
 func NewHierarchicalAuthorizer(rootDir, childDir string, get TAMGetter) (security.Authorizer, error) {
-	rootTam, intentionallyEmpty, err := get.TAMForPath(rootDir)
-	if err != nil {
-		return nil, err
-	} else if intentionallyEmpty {
-		vlog.VI(2).Infof("TAMForPath(%s) is intentionally empty", rootDir)
-		return nil, nil
-	}
-
-	// We are at the root so exit early.
-	if rootDir == childDir {
-		return mkRootAuth(rootTam)
-	}
-
-	// This is not fatal: the childDir may not exist if we are invoking
-	// a Create() method so we only use the root AccessList.
-	childTam, intentionallyEmpty, err := get.TAMForPath(childDir)
-	if err != nil {
-		return nil, err
-	} else if intentionallyEmpty {
-		return mkRootAuth(rootTam)
-	}
-
-	childAuth, err := access.PermissionsAuthorizer(childTam, access.TypicalTagType())
-	if err != nil {
-		vlog.Errorf("Successfully obtained an AccessList from the filesystem but PermissionsAuthorizer couldn't use it: %v", err)
-		return nil, err
-	}
-
 	return &hierarchicalAuthorizer{
-		child:          childAuth,
-		rootAccessList: rootTam[string(access.Admin)],
+		rootDir:  rootDir,
+		childDir: childDir,
+		get:      get,
 	}, nil
 }
 
-// Authorize provides two-levels of authorization. Admin permission
-// on the root provides a "superuser"-like power for administering the
-// server using an instance of hierarchicalAuthorizer. Otherwise, the
-// default permissions of the named path apply.
 func (ha *hierarchicalAuthorizer) Authorize(ctx *context.T) error {
-	childErr := ha.child.Authorize(ctx)
+	rootPerms, intentionallyEmpty, err := ha.get.TAMForPath(ha.rootDir)
+	if err != nil {
+		return err
+	} else if intentionallyEmpty {
+		vlog.VI(2).Infof("TAMForPath(%s) is intentionally empty", ha.rootDir)
+		return defaultAuthorizer(ctx)
+	}
+
+	// We are at the root so exit early.
+	if ha.rootDir == ha.childDir {
+		a, err := mkRootAuth(rootPerms)
+		if err != nil {
+			return err
+		}
+		return a.Authorize(ctx)
+	}
+
+	// This is not fatal: the childDir may not exist if we are invoking
+	// a Create() method so we only use the root Permissions.
+	childPerms, intentionallyEmpty, err := ha.get.TAMForPath(ha.childDir)
+	if err != nil {
+		return err
+	} else if intentionallyEmpty {
+		a, err := mkRootAuth(rootPerms)
+		if err != nil {
+			return err
+		}
+		return a.Authorize(ctx)
+	}
+
+	childAuth, err := access.PermissionsAuthorizer(childPerms, access.TypicalTagType())
+	if err != nil {
+		vlog.Errorf("Successfully obtained a Permissions from the filesystem but PermissionsAuthorizer couldn't use it: %v", err)
+		return err
+	}
+
+	childErr := childAuth.Authorize(ctx)
 	if childErr == nil {
 		return nil
 	}
@@ -87,9 +96,35 @@
 	// Maybe the invoking principal can invoke this method because
 	// it has root permissions.
 	names, _ := security.RemoteBlessingNames(ctx)
-	if len(names) > 0 && ha.rootAccessList.Includes(names...) {
+	if len(names) > 0 && rootPerms[string(access.Admin)].Includes(names...) {
 		return nil
 	}
 
 	return childErr
 }
+
+// defaultAuthorizer implements an authorization policy that requires one end
+// of the RPC to have a blessing that makes it a delegate of the other.
+// TODO(rjkroege): Remove this when the defaultAuthorizer becomes public.
+func defaultAuthorizer(ctx *context.T) error {
+	var (
+		localNames             = security.LocalBlessingNames(ctx)
+		remoteNames, remoteErr = security.RemoteBlessingNames(ctx)
+	)
+	// Authorize if any element in localNames is a "delegate of" (i.e., has been
+	// blessed by) any element in remoteNames, OR vice-versa.
+	for _, l := range localNames {
+		if security.BlessingPattern(l).MatchedBy(remoteNames...) {
+			// l is a delegate of an element in remote.
+			return nil
+		}
+	}
+	for _, r := range remoteNames {
+		if security.BlessingPattern(r).MatchedBy(localNames...) {
+			// r is a delegate of an element in localNames.
+			return nil
+		}
+	}
+
+	return rpc.NewErrInvalidBlessings(nil, remoteNames, remoteErr, localNames)
+}