blob: 4810b34d095fd4705d8e13c9bfe0b37499b03344 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pathperms
import (
"v.io/v23/context"
"v.io/v23/security"
"v.io/v23/security/access"
)
// hierarchicalAuthorizer contains the state needed to implement
// hierarchical authorization in the Authorize method.
type hierarchicalAuthorizer struct {
rootDir, childDir string
get PermsGetter
}
// PermsGetter defines an abstract interface that a customer of
// NewHierarchicalAuthorizer can use to obtain the PermissionsAuthorizer
// instances that it needs to construct a hierarchicalAuthorizer.
type PermsGetter interface {
// PermsForPath has two successful outcomes: either returning a valid
// Permissions object or a boolean status true indicating that the
// Permissions object is intentionally not present. Finally, it returns an
// error if anything has gone wrong.
PermsForPath(ctx *context.T, path string) (access.Permissions, bool, error)
}
// 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.
//
// If the root permissions are not set, the authorizer behaves like the
// DefaultAuthorizer.
//
// If the child permissions are not set, the authorizer uses the permissions set
// on the root to restrict access to the child (including the admin override
// described above).
func NewHierarchicalAuthorizer(rootDir, childDir string, get PermsGetter) (security.Authorizer, error) {
return &hierarchicalAuthorizer{
rootDir: rootDir,
childDir: childDir,
get: get,
}, nil
}
func (ha *hierarchicalAuthorizer) Authorize(ctx *context.T, call security.Call) error {
rootPerms, intentionallyEmpty, err := ha.get.PermsForPath(ctx, ha.rootDir)
if err != nil {
return err
} else if intentionallyEmpty {
ctx.VI(2).Infof("PermsForPath(%s) is intentionally empty", ha.rootDir)
return security.DefaultAuthorizer().Authorize(ctx, call)
}
// We are at the root so exit early.
if ha.rootDir == ha.childDir {
return adminCheckAuth(ctx, call, access.TypicalTagTypePermissionsAuthorizer(rootPerms), rootPerms)
}
// This is not fatal: the childDir may not exist if we are invoking
// a method creating the object, so we only use the root Permissions.
childPerms, intentionallyEmpty, err := ha.get.PermsForPath(ctx, ha.childDir)
if err != nil {
return err
} else if intentionallyEmpty {
return adminCheckAuth(ctx, call, access.TypicalTagTypePermissionsAuthorizer(rootPerms), rootPerms)
}
return adminCheckAuth(ctx, call, access.TypicalTagTypePermissionsAuthorizer(childPerms), rootPerms)
}
func adminCheckAuth(ctx *context.T, call security.Call, auth security.Authorizer, perms access.Permissions) error {
err := auth.Authorize(ctx, call)
if err == nil {
return nil
}
// Maybe the invoking principal can invoke this method because
// it has Admin permissions.
names, _ := security.RemoteBlessingNames(ctx, call)
if len(names) > 0 && perms[string(access.Admin)].Includes(names...) {
return nil
}
return err
}