blob: 4fd7c604ad12336ce9ea69f98cac16fe08dd9d8d [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 (
"fmt"
"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
emptyChildPermsMethods map[string]bool
}
// 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), provided that the method being invoked is among the subset
// specified (empty set means all methods). If the method is not among the
// subset and the child permissions are not set, the request is rejected.
func NewHierarchicalAuthorizer(rootDir, childDir string, get PermsGetter, emptyChildPermissionsMethods []string) (security.Authorizer, error) {
emptyChildPermsMethods := make(map[string]bool)
for _, m := range emptyChildPermissionsMethods {
emptyChildPermsMethods[m] = true
}
return &hierarchicalAuthorizer{
rootDir: rootDir,
childDir: childDir,
get: get,
emptyChildPermsMethods: emptyChildPermsMethods,
}, 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 {
if len(ha.emptyChildPermsMethods) == 0 || ha.emptyChildPermsMethods[call.Method()] {
return adminCheckAuth(ctx, call, access.TypicalTagTypePermissionsAuthorizer(rootPerms), rootPerms)
}
return fmt.Errorf("access disallowed for method %v: no permissions specified on object", call.Method())
}
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
}