mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: Implement standard rbac.Subject to be reused everywhere (#5881)
* chore: Implement standard rbac.Subject to be reused everywhere An rbac subject is created in multiple spots because of the way we expand roles, scopes, etc. This difference in use creates a list of arguments which is unwieldy. Use of the expander interface lets us conform to a single subject in every case
This commit is contained in:
+72
-70
@@ -17,24 +17,34 @@ import (
|
||||
"github.com/coder/coder/coderd/tracing"
|
||||
)
|
||||
|
||||
// ExpandableRoles is any type that can be expanded into a []Role. This is implemented
|
||||
// as an interface so we can have RoleNames for user defined roles, and implement
|
||||
// custom ExpandableRoles for system type users (eg autostart/autostop system role).
|
||||
// We want a clear divide between the two types of roles so users have no codepath
|
||||
// to interact or assign system roles.
|
||||
//
|
||||
// Note: We may also want to do the same thing with scopes to allow custom scope
|
||||
// support unavailable to the user. Eg: Scope to a single resource.
|
||||
type ExpandableRoles interface {
|
||||
Expand() ([]Role, error)
|
||||
// Names is for logging and tracing purposes, we want to know the human
|
||||
// names of the expanded roles.
|
||||
Names() []string
|
||||
// Subject is a struct that contains all the elements of a subject in an rbac
|
||||
// authorize.
|
||||
type Subject struct {
|
||||
ID string
|
||||
Roles ExpandableRoles
|
||||
Groups []string
|
||||
Scope ExpandableScope
|
||||
}
|
||||
|
||||
// SafeScopeName prevent nil pointer dereference.
|
||||
func (s Subject) SafeScopeName() string {
|
||||
if s.Scope == nil {
|
||||
return "no-scope"
|
||||
}
|
||||
return s.Scope.Name()
|
||||
}
|
||||
|
||||
// SafeRoleNames prevent nil pointer dereference.
|
||||
func (s Subject) SafeRoleNames() []string {
|
||||
if s.Roles == nil {
|
||||
return []string{}
|
||||
}
|
||||
return s.Roles.Names()
|
||||
}
|
||||
|
||||
type Authorizer interface {
|
||||
ByRoleName(ctx context.Context, subjectID string, roleNames ExpandableRoles, scope ScopeName, groups []string, action Action, object Object) error
|
||||
PrepareByRoleName(ctx context.Context, subjectID string, roleNames ExpandableRoles, scope ScopeName, groups []string, action Action, objectType string) (PreparedAuthorized, error)
|
||||
Authorize(ctx context.Context, subject Subject, action Action, object Object) error
|
||||
Prepare(ctx context.Context, subject Subject, action Action, objectType string) (PreparedAuthorized, error)
|
||||
}
|
||||
|
||||
type PreparedAuthorized interface {
|
||||
@@ -48,7 +58,7 @@ type PreparedAuthorized interface {
|
||||
//
|
||||
// Ideally the 'CompileToSQL' is used instead for large sets. This cost scales
|
||||
// linearly with the number of objects passed in.
|
||||
func Filter[O Objecter](ctx context.Context, auth Authorizer, subjID string, subjRoles ExpandableRoles, scope ScopeName, groups []string, action Action, objects []O) ([]O, error) {
|
||||
func Filter[O Objecter](ctx context.Context, auth Authorizer, subject Subject, action Action, objects []O) ([]O, error) {
|
||||
if len(objects) == 0 {
|
||||
// Nothing to filter
|
||||
return objects, nil
|
||||
@@ -60,9 +70,9 @@ func Filter[O Objecter](ctx context.Context, auth Authorizer, subjID string, sub
|
||||
// objects, then the span is not interesting. It would just add excessive
|
||||
// 0 time spans that provide no insight.
|
||||
ctx, span := tracing.StartSpan(ctx,
|
||||
rbacTraceAttributes(subjRoles.Names(), len(groups), scope, action, objectType,
|
||||
rbacTraceAttributes(subject, action, objectType,
|
||||
// For filtering, we are only measuring the total time for the entire
|
||||
// set of objects. This and the 'PrepareByRoleName' span time
|
||||
// set of objects. This and the 'Prepare' span time
|
||||
// is all that is required to measure the performance of this
|
||||
// function on a per-object basis.
|
||||
attribute.Int("num_objects", len(objects)),
|
||||
@@ -71,8 +81,8 @@ func Filter[O Objecter](ctx context.Context, auth Authorizer, subjID string, sub
|
||||
defer span.End()
|
||||
|
||||
// Running benchmarks on this function, it is **always** faster to call
|
||||
// auth.ByRoleName on <10 objects. This is because the overhead of
|
||||
// 'PrepareByRoleName'. Once we cross 10 objects, then it starts to become
|
||||
// auth.Authorize on <10 objects. This is because the overhead of
|
||||
// 'Prepare'. Once we cross 10 objects, then it starts to become
|
||||
// faster
|
||||
if len(objects) < 10 {
|
||||
for _, o := range objects {
|
||||
@@ -80,7 +90,7 @@ func Filter[O Objecter](ctx context.Context, auth Authorizer, subjID string, sub
|
||||
if rbacObj.Type != objectType {
|
||||
return nil, xerrors.Errorf("object types must be uniform across the set (%s), found %s", objectType, rbacObj)
|
||||
}
|
||||
err := auth.ByRoleName(ctx, subjID, subjRoles, scope, groups, action, o.RBACObject())
|
||||
err := auth.Authorize(ctx, subject, action, o.RBACObject())
|
||||
if err == nil {
|
||||
filtered = append(filtered, o)
|
||||
}
|
||||
@@ -88,7 +98,7 @@ func Filter[O Objecter](ctx context.Context, auth Authorizer, subjID string, sub
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
prepared, err := auth.PrepareByRoleName(ctx, subjID, subjRoles, scope, groups, action, objectType)
|
||||
prepared, err := auth.Prepare(ctx, subject, action, objectType)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("prepare: %w", err)
|
||||
}
|
||||
@@ -191,14 +201,15 @@ type authSubject struct {
|
||||
Scope Scope `json:"scope"`
|
||||
}
|
||||
|
||||
// ByRoleName will expand all roleNames into roles before calling Authorize().
|
||||
// This is the function intended to be used outside this package.
|
||||
// The role is fetched from the builtin map located in memory.
|
||||
func (a RegoAuthorizer) ByRoleName(ctx context.Context, subjectID string, roleNames ExpandableRoles, scope ScopeName, groups []string, action Action, object Object) error {
|
||||
// Authorize is the intended function to be used outside this package.
|
||||
// It returns `nil` if the subject is authorized to perform the action on
|
||||
// the object.
|
||||
// If an error is returned, the authorization is denied.
|
||||
func (a RegoAuthorizer) Authorize(ctx context.Context, subject Subject, action Action, object Object) error {
|
||||
start := time.Now()
|
||||
ctx, span := tracing.StartSpan(ctx,
|
||||
trace.WithTimestamp(start), // Reuse the time.Now for metric and trace
|
||||
rbacTraceAttributes(roleNames.Names(), len(groups), scope, action, object.Type,
|
||||
rbacTraceAttributes(subject, action, object.Type,
|
||||
// For authorizing a single object, this data is useful to know how
|
||||
// complex our objects are getting.
|
||||
attribute.Int("object_num_groups", len(object.ACLGroupList)),
|
||||
@@ -207,18 +218,9 @@ func (a RegoAuthorizer) ByRoleName(ctx context.Context, subjectID string, roleNa
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
roles, err := roleNames.Expand()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err := a.authorize(ctx, subject, action, object)
|
||||
span.SetAttributes(attribute.Bool("authorized", err == nil))
|
||||
|
||||
scopeRole, err := ExpandScope(scope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = a.Authorize(ctx, subjectID, roles, scopeRole, groups, action, object)
|
||||
span.AddEvent("authorized", trace.WithAttributes(attribute.Bool("authorized", err == nil)))
|
||||
dur := time.Since(start)
|
||||
if err != nil {
|
||||
a.authorizeHist.WithLabelValues("false").Observe(dur.Seconds())
|
||||
@@ -229,15 +231,34 @@ func (a RegoAuthorizer) ByRoleName(ctx context.Context, subjectID string, roleNa
|
||||
return nil
|
||||
}
|
||||
|
||||
// Authorize allows passing in custom Roles.
|
||||
// This is really helpful for unit testing, as we can create custom roles to exercise edge cases.
|
||||
func (a RegoAuthorizer) Authorize(ctx context.Context, subjectID string, roles []Role, scope Scope, groups []string, action Action, object Object) error {
|
||||
// authorize is the internal function that does the actual authorization.
|
||||
// It is a different function so the exported one can add tracing + metrics.
|
||||
// That code tends to clutter up the actual logic, so it's separated out.
|
||||
// nolint:revive
|
||||
func (a RegoAuthorizer) authorize(ctx context.Context, subject Subject, action Action, object Object) error {
|
||||
if subject.Roles == nil {
|
||||
return xerrors.Errorf("subject must have roles")
|
||||
}
|
||||
if subject.Scope == nil {
|
||||
return xerrors.Errorf("subject must have a scope")
|
||||
}
|
||||
|
||||
subjRoles, err := subject.Roles.Expand()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("expand roles: %w", err)
|
||||
}
|
||||
|
||||
subjScope, err := subject.Scope.Expand()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("expand scope: %w", err)
|
||||
}
|
||||
|
||||
input := map[string]interface{}{
|
||||
"subject": authSubject{
|
||||
ID: subjectID,
|
||||
Roles: roles,
|
||||
Groups: groups,
|
||||
Scope: scope,
|
||||
ID: subject.ID,
|
||||
Roles: subjRoles,
|
||||
Groups: subject.Groups,
|
||||
Scope: subjScope,
|
||||
},
|
||||
"object": object,
|
||||
"action": action,
|
||||
@@ -254,27 +275,19 @@ func (a RegoAuthorizer) Authorize(ctx context.Context, subjectID string, roles [
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a RegoAuthorizer) PrepareByRoleName(ctx context.Context, subjectID string, roleNames ExpandableRoles, scope ScopeName, groups []string, action Action, objectType string) (PreparedAuthorized, error) {
|
||||
// Prepare will partially execute the rego policy leaving the object fields unknown (except for the type).
|
||||
// This will vastly speed up performance if batch authorization on the same type of objects is needed.
|
||||
func (a RegoAuthorizer) Prepare(ctx context.Context, subject Subject, action Action, objectType string) (PreparedAuthorized, error) {
|
||||
start := time.Now()
|
||||
ctx, span := tracing.StartSpan(ctx,
|
||||
trace.WithTimestamp(start),
|
||||
rbacTraceAttributes(roleNames.Names(), len(groups), scope, action, objectType),
|
||||
rbacTraceAttributes(subject, action, objectType),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
roles, err := roleNames.Expand()
|
||||
prepared, err := newPartialAuthorizer(ctx, subject, action, objectType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scopeRole, err := ExpandScope(scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prepared, err := a.Prepare(ctx, subjectID, roles, scopeRole, groups, action, objectType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, xerrors.Errorf("new partial authorizer: %w", err)
|
||||
}
|
||||
|
||||
// Add attributes of the Prepare results. This will help understand the
|
||||
@@ -287,14 +300,3 @@ func (a RegoAuthorizer) PrepareByRoleName(ctx context.Context, subjectID string,
|
||||
a.prepareHist.Observe(time.Since(start).Seconds())
|
||||
return prepared, nil
|
||||
}
|
||||
|
||||
// Prepare will partially execute the rego policy leaving the object fields unknown (except for the type).
|
||||
// This will vastly speed up performance if batch authorization on the same type of objects is needed.
|
||||
func (RegoAuthorizer) Prepare(ctx context.Context, subjectID string, roles []Role, scope Scope, groups []string, action Action, objectType string) (*PartialAuthorizer, error) {
|
||||
auth, err := newPartialAuthorizer(ctx, subjectID, roles, scope, groups, action, objectType)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("new partial authorizer: %w", err)
|
||||
}
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
+194
-177
@@ -15,16 +15,6 @@ import (
|
||||
"github.com/coder/coder/testutil"
|
||||
)
|
||||
|
||||
type subject struct {
|
||||
UserID string `json:"id"`
|
||||
// For the unit test we want to pass in the roles directly, instead of just
|
||||
// by name. This allows us to test custom roles that do not exist in the product,
|
||||
// but test edge cases of the implementation.
|
||||
Roles []Role `json:"roles"`
|
||||
Groups []string `json:"groups"`
|
||||
Scope Scope `json:"scope"`
|
||||
}
|
||||
|
||||
type fakeObject struct {
|
||||
Owner uuid.UUID
|
||||
OrgOwner uuid.UUID
|
||||
@@ -43,14 +33,20 @@ func (w fakeObject) RBACObject() Object {
|
||||
func TestFilterError(t *testing.T) {
|
||||
t.Parallel()
|
||||
auth := NewAuthorizer(prometheus.NewRegistry())
|
||||
subject := Subject{
|
||||
ID: uuid.NewString(),
|
||||
Roles: RoleNames{},
|
||||
Groups: []string{},
|
||||
Scope: ScopeAll,
|
||||
}
|
||||
|
||||
_, err := Filter(context.Background(), auth, uuid.NewString(), RoleNames{}, ScopeAll, []string{}, ActionRead, []Object{ResourceUser, ResourceWorkspace})
|
||||
_, err := Filter(context.Background(), auth, subject, ActionRead, []Object{ResourceUser, ResourceWorkspace})
|
||||
require.ErrorContains(t, err, "object types must be uniform")
|
||||
}
|
||||
|
||||
// TestFilter ensures the filter acts the same as an individual authorize.
|
||||
// It generates a random set of objects, then runs the Filter batch function
|
||||
// against the singular ByRoleName function.
|
||||
// against the singular Authorize function.
|
||||
func TestFilter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -74,78 +70,92 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
SubjectID string
|
||||
Roles RoleNames
|
||||
Actor Subject
|
||||
Action Action
|
||||
Scope ScopeName
|
||||
ObjectType string
|
||||
}{
|
||||
{
|
||||
Name: "NoRoles",
|
||||
SubjectID: userIDs[0].String(),
|
||||
Roles: []string{},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "Admin",
|
||||
SubjectID: userIDs[0].String(),
|
||||
Roles: []string{RoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "OrgAdmin",
|
||||
SubjectID: userIDs[0].String(),
|
||||
Roles: []string{RoleOrgMember(orgIDs[0]), RoleOrgAdmin(orgIDs[0]), RoleMember()},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "OrgMember",
|
||||
SubjectID: userIDs[0].String(),
|
||||
Roles: []string{RoleOrgMember(orgIDs[0]), RoleOrgMember(orgIDs[1]), RoleMember()},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "ManyRoles",
|
||||
SubjectID: userIDs[0].String(),
|
||||
Roles: []string{
|
||||
RoleOrgMember(orgIDs[0]), RoleOrgAdmin(orgIDs[0]),
|
||||
RoleOrgMember(orgIDs[1]), RoleOrgAdmin(orgIDs[1]),
|
||||
RoleOrgMember(orgIDs[2]), RoleOrgAdmin(orgIDs[2]),
|
||||
RoleOrgMember(orgIDs[4]),
|
||||
RoleOrgMember(orgIDs[5]),
|
||||
RoleMember(),
|
||||
Name: "NoRoles",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "SiteMember",
|
||||
SubjectID: userIDs[0].String(),
|
||||
Roles: []string{RoleMember()},
|
||||
Name: "Admin",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{RoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "OrgAdmin",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{RoleOrgMember(orgIDs[0]), RoleOrgAdmin(orgIDs[0]), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "OrgMember",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{RoleOrgMember(orgIDs[0]), RoleOrgMember(orgIDs[1]), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "ManyRoles",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{
|
||||
RoleOrgMember(orgIDs[0]), RoleOrgAdmin(orgIDs[0]),
|
||||
RoleOrgMember(orgIDs[1]), RoleOrgAdmin(orgIDs[1]),
|
||||
RoleOrgMember(orgIDs[2]), RoleOrgAdmin(orgIDs[2]),
|
||||
RoleOrgMember(orgIDs[4]),
|
||||
RoleOrgMember(orgIDs[5]),
|
||||
RoleMember(),
|
||||
},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "SiteMember",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceUser.Type,
|
||||
Action: ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "ReadOrgs",
|
||||
SubjectID: userIDs[0].String(),
|
||||
Roles: []string{
|
||||
RoleOrgMember(orgIDs[0]),
|
||||
RoleOrgMember(orgIDs[1]),
|
||||
RoleOrgMember(orgIDs[2]),
|
||||
RoleOrgMember(orgIDs[3]),
|
||||
RoleMember(),
|
||||
Name: "ReadOrgs",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{
|
||||
RoleOrgMember(orgIDs[0]),
|
||||
RoleOrgMember(orgIDs[1]),
|
||||
RoleOrgMember(orgIDs[2]),
|
||||
RoleOrgMember(orgIDs[3]),
|
||||
RoleMember(),
|
||||
},
|
||||
},
|
||||
ObjectType: ResourceOrganization.Type,
|
||||
Action: ActionRead,
|
||||
},
|
||||
{
|
||||
Name: "ScopeApplicationConnect",
|
||||
SubjectID: userIDs[0].String(),
|
||||
Roles: []string{RoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
|
||||
Name: "ScopeApplicationConnect",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleNames{RoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: ActionRead,
|
||||
},
|
||||
@@ -155,6 +165,7 @@ func TestFilter(t *testing.T) {
|
||||
tc := tc
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
actor := tc.Actor
|
||||
|
||||
localObjects := make([]fakeObject, len(objects))
|
||||
copy(localObjects, objects)
|
||||
@@ -163,16 +174,16 @@ func TestFilter(t *testing.T) {
|
||||
defer cancel()
|
||||
auth := NewAuthorizer(prometheus.NewRegistry())
|
||||
|
||||
scope := ScopeAll
|
||||
if tc.Scope != "" {
|
||||
scope = tc.Scope
|
||||
if actor.Scope == nil {
|
||||
// Default to ScopeAll
|
||||
actor.Scope = ScopeAll
|
||||
}
|
||||
|
||||
// Run auth 1 by 1
|
||||
var allowedCount int
|
||||
for i, obj := range localObjects {
|
||||
obj.Type = tc.ObjectType
|
||||
err := auth.ByRoleName(ctx, tc.SubjectID, tc.Roles, scope, []string{}, ActionRead, obj.RBACObject())
|
||||
err := auth.Authorize(ctx, actor, ActionRead, obj.RBACObject())
|
||||
obj.Allowed = err == nil
|
||||
if err == nil {
|
||||
allowedCount++
|
||||
@@ -181,7 +192,7 @@ func TestFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
// Run by filter
|
||||
list, err := Filter(ctx, auth, tc.SubjectID, tc.Roles, scope, []string{}, tc.Action, localObjects)
|
||||
list, err := Filter(ctx, auth, actor, tc.Action, localObjects)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, allowedCount, len(list), "expected number of allowed")
|
||||
for _, obj := range list {
|
||||
@@ -198,11 +209,11 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
unuseID := uuid.New()
|
||||
allUsersGroup := "Everyone"
|
||||
|
||||
user := subject{
|
||||
UserID: "me",
|
||||
user := Subject{
|
||||
ID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Groups: []string{allUsersGroup},
|
||||
Roles: []Role{
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleMember())),
|
||||
must(RoleByName(RoleOrgMember(defOrg))),
|
||||
},
|
||||
@@ -211,21 +222,21 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
testAuthorize(t, "UserACLList", user, []authTestCase{
|
||||
{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithACLUserList(map[string][]Action{
|
||||
user.UserID: allActions(),
|
||||
user.ID: allActions(),
|
||||
}),
|
||||
actions: allActions(),
|
||||
allow: true,
|
||||
},
|
||||
{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithACLUserList(map[string][]Action{
|
||||
user.UserID: {WildcardSymbol},
|
||||
user.ID: {WildcardSymbol},
|
||||
}),
|
||||
actions: allActions(),
|
||||
allow: true,
|
||||
},
|
||||
{
|
||||
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithACLUserList(map[string][]Action{
|
||||
user.UserID: {ActionRead, ActionUpdate},
|
||||
user.ID: {ActionRead, ActionUpdate},
|
||||
}),
|
||||
actions: []Action{ActionCreate, ActionDelete},
|
||||
allow: false,
|
||||
@@ -233,7 +244,7 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
{
|
||||
// By default users cannot update templates
|
||||
resource: ResourceTemplate.InOrg(defOrg).WithACLUserList(map[string][]Action{
|
||||
user.UserID: {ActionUpdate},
|
||||
user.ID: {ActionUpdate},
|
||||
}),
|
||||
actions: []Action{ActionUpdate},
|
||||
allow: true,
|
||||
@@ -274,15 +285,15 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
|
||||
testAuthorize(t, "Member", user, []authTestCase{
|
||||
// Org + me
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID), actions: allActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: allActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg), actions: allActions(), allow: false},
|
||||
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID), actions: allActions(), allow: true},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID), actions: allActions(), allow: true},
|
||||
|
||||
{resource: ResourceWorkspace.All(), actions: allActions(), allow: false},
|
||||
|
||||
// Other org + me
|
||||
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.UserID), actions: allActions(), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.ID), actions: allActions(), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unuseID), actions: allActions(), allow: false},
|
||||
|
||||
// Other org + other user
|
||||
@@ -297,10 +308,10 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
{resource: ResourceWorkspace.WithOwner("not-me"), actions: allActions(), allow: false},
|
||||
})
|
||||
|
||||
user = subject{
|
||||
UserID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: []Role{{
|
||||
user = Subject{
|
||||
ID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: Roles{{
|
||||
Name: "deny-all",
|
||||
// List out deny permissions explicitly
|
||||
Site: []Permission{
|
||||
@@ -315,15 +326,15 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
|
||||
testAuthorize(t, "DeletedMember", user, []authTestCase{
|
||||
// Org + me
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID), actions: allActions(), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: allActions(), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg), actions: allActions(), allow: false},
|
||||
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID), actions: allActions(), allow: false},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID), actions: allActions(), allow: false},
|
||||
|
||||
{resource: ResourceWorkspace.All(), actions: allActions(), allow: false},
|
||||
|
||||
// Other org + me
|
||||
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.UserID), actions: allActions(), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.ID), actions: allActions(), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unuseID), actions: allActions(), allow: false},
|
||||
|
||||
// Other org + other user
|
||||
@@ -338,10 +349,10 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
{resource: ResourceWorkspace.WithOwner("not-me"), actions: allActions(), allow: false},
|
||||
})
|
||||
|
||||
user = subject{
|
||||
UserID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: []Role{
|
||||
user = Subject{
|
||||
ID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleOrgAdmin(defOrg))),
|
||||
must(RoleByName(RoleMember())),
|
||||
},
|
||||
@@ -349,15 +360,15 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
|
||||
testAuthorize(t, "OrgAdmin", user, []authTestCase{
|
||||
// Org + me
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID), actions: allActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: allActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg), actions: allActions(), allow: true},
|
||||
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID), actions: allActions(), allow: true},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID), actions: allActions(), allow: true},
|
||||
|
||||
{resource: ResourceWorkspace.All(), actions: allActions(), allow: false},
|
||||
|
||||
// Other org + me
|
||||
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.UserID), actions: allActions(), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.ID), actions: allActions(), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unuseID), actions: allActions(), allow: false},
|
||||
|
||||
// Other org + other user
|
||||
@@ -372,10 +383,10 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
{resource: ResourceWorkspace.WithOwner("not-me"), actions: allActions(), allow: false},
|
||||
})
|
||||
|
||||
user = subject{
|
||||
UserID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: []Role{
|
||||
user = Subject{
|
||||
ID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleOwner())),
|
||||
must(RoleByName(RoleMember())),
|
||||
},
|
||||
@@ -383,15 +394,15 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
|
||||
testAuthorize(t, "SiteAdmin", user, []authTestCase{
|
||||
// Org + me
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID), actions: allActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: allActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg), actions: allActions(), allow: true},
|
||||
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID), actions: allActions(), allow: true},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID), actions: allActions(), allow: true},
|
||||
|
||||
{resource: ResourceWorkspace.All(), actions: allActions(), allow: true},
|
||||
|
||||
// Other org + me
|
||||
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.UserID), actions: allActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.ID), actions: allActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(unuseID), actions: allActions(), allow: true},
|
||||
|
||||
// Other org + other user
|
||||
@@ -406,10 +417,10 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
{resource: ResourceWorkspace.WithOwner("not-me"), actions: allActions(), allow: true},
|
||||
})
|
||||
|
||||
user = subject{
|
||||
UserID: "me",
|
||||
Scope: must(ExpandScope(ScopeApplicationConnect)),
|
||||
Roles: []Role{
|
||||
user = Subject{
|
||||
ID: "me",
|
||||
Scope: must(ExpandScope(ScopeApplicationConnect)),
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleOrgMember(defOrg))),
|
||||
must(RoleByName(RoleMember())),
|
||||
},
|
||||
@@ -422,15 +433,15 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
return c
|
||||
}, []authTestCase{
|
||||
// Org + me
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.UserID), allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.ID), allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg), allow: false},
|
||||
|
||||
{resource: ResourceWorkspaceApplicationConnect.WithOwner(user.UserID), allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.WithOwner(user.ID), allow: true},
|
||||
|
||||
{resource: ResourceWorkspaceApplicationConnect.All(), allow: false},
|
||||
|
||||
// Other org + me
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID).WithOwner(user.UserID), allow: false},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID).WithOwner(user.ID), allow: false},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID), allow: false},
|
||||
|
||||
// Other org + other user
|
||||
@@ -451,15 +462,15 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
return c
|
||||
}, []authTestCase{
|
||||
// Org + me
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg)},
|
||||
|
||||
{resource: ResourceWorkspaceApplicationConnect.WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspaceApplicationConnect.WithOwner(user.ID)},
|
||||
|
||||
{resource: ResourceWorkspaceApplicationConnect.All()},
|
||||
|
||||
// Other org + me
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID)},
|
||||
|
||||
// Other org + other user
|
||||
@@ -480,15 +491,15 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
return c
|
||||
}, []authTestCase{
|
||||
// Org + me
|
||||
{resource: ResourceTemplate.InOrg(defOrg).WithOwner(user.UserID)},
|
||||
{resource: ResourceTemplate.InOrg(defOrg).WithOwner(user.ID)},
|
||||
{resource: ResourceTemplate.InOrg(defOrg)},
|
||||
|
||||
{resource: ResourceTemplate.WithOwner(user.UserID)},
|
||||
{resource: ResourceTemplate.WithOwner(user.ID)},
|
||||
|
||||
{resource: ResourceTemplate.All()},
|
||||
|
||||
// Other org + me
|
||||
{resource: ResourceTemplate.InOrg(unuseID).WithOwner(user.UserID)},
|
||||
{resource: ResourceTemplate.InOrg(unuseID).WithOwner(user.ID)},
|
||||
{resource: ResourceTemplate.InOrg(unuseID)},
|
||||
|
||||
// Other org + other user
|
||||
@@ -505,10 +516,10 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
)
|
||||
|
||||
// In practice this is a token scope on a regular subject
|
||||
user = subject{
|
||||
UserID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: []Role{
|
||||
user = Subject{
|
||||
ID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: Roles{
|
||||
{
|
||||
Name: "ReadOnlyOrgAndUser",
|
||||
Site: []Permission{},
|
||||
@@ -537,15 +548,15 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
}, []authTestCase{
|
||||
// Read
|
||||
// Org + me
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg), allow: true},
|
||||
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID), allow: true},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID), allow: true},
|
||||
|
||||
{resource: ResourceWorkspace.All(), allow: false},
|
||||
|
||||
// Other org + me
|
||||
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.UserID), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.ID), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unuseID), allow: false},
|
||||
|
||||
// Other org + other user
|
||||
@@ -568,15 +579,15 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
}, []authTestCase{
|
||||
// Read
|
||||
// Org + me
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg)},
|
||||
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
||||
|
||||
{resource: ResourceWorkspace.All()},
|
||||
|
||||
// Other org + me
|
||||
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(unuseID)},
|
||||
|
||||
// Other org + other user
|
||||
@@ -598,10 +609,10 @@ func TestAuthorizeLevels(t *testing.T) {
|
||||
defOrg := uuid.New()
|
||||
unusedID := uuid.New()
|
||||
|
||||
user := subject{
|
||||
UserID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: []Role{
|
||||
user := Subject{
|
||||
ID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleOwner())),
|
||||
{
|
||||
Name: "org-deny:" + defOrg.String(),
|
||||
@@ -636,15 +647,15 @@ func TestAuthorizeLevels(t *testing.T) {
|
||||
return c
|
||||
}, []authTestCase{
|
||||
// Org + me
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg)},
|
||||
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
||||
|
||||
{resource: ResourceWorkspace.All()},
|
||||
|
||||
// Other org + me
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID)},
|
||||
|
||||
// Other org + other user
|
||||
@@ -659,10 +670,10 @@ func TestAuthorizeLevels(t *testing.T) {
|
||||
{resource: ResourceWorkspace.WithOwner("not-me")},
|
||||
}))
|
||||
|
||||
user = subject{
|
||||
UserID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: []Role{
|
||||
user = Subject{
|
||||
ID: "me",
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: Roles{
|
||||
{
|
||||
Name: "site-noise",
|
||||
Site: []Permission{
|
||||
@@ -694,15 +705,15 @@ func TestAuthorizeLevels(t *testing.T) {
|
||||
return c
|
||||
}, []authTestCase{
|
||||
// Org + me
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg), allow: true},
|
||||
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID), allow: false},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID), allow: false},
|
||||
|
||||
{resource: ResourceWorkspace.All(), allow: false},
|
||||
|
||||
// Other org + me
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.UserID), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID), allow: false},
|
||||
|
||||
// Other org + other user
|
||||
@@ -723,10 +734,10 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
|
||||
defOrg := uuid.New()
|
||||
unusedID := uuid.New()
|
||||
user := subject{
|
||||
UserID: "me",
|
||||
Roles: []Role{must(RoleByName(RoleOwner()))},
|
||||
Scope: must(ExpandScope(ScopeApplicationConnect)),
|
||||
user := Subject{
|
||||
ID: "me",
|
||||
Roles: Roles{must(RoleByName(RoleOwner()))},
|
||||
Scope: must(ExpandScope(ScopeApplicationConnect)),
|
||||
}
|
||||
|
||||
testAuthorize(t, "Admin_ScopeApplicationConnect", user,
|
||||
@@ -734,11 +745,11 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
c.actions = []Action{ActionRead, ActionUpdate, ActionDelete}
|
||||
return c
|
||||
}, []authTestCase{
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg), allow: false},
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID), allow: false},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID), allow: false},
|
||||
{resource: ResourceWorkspace.All(), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.UserID), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID), allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me"), allow: false},
|
||||
{resource: ResourceWorkspace.WithOwner("not-me"), allow: false},
|
||||
@@ -749,14 +760,14 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
// Allowed by scope:
|
||||
[]authTestCase{
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner("not-me"), actions: []Action{ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.UserID), actions: []Action{ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.ID), actions: []Action{ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(unusedID).WithOwner("not-me"), actions: []Action{ActionCreate}, allow: true},
|
||||
},
|
||||
)
|
||||
|
||||
user = subject{
|
||||
UserID: "me",
|
||||
Roles: []Role{
|
||||
user = Subject{
|
||||
ID: "me",
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleMember())),
|
||||
must(RoleByName(RoleOrgMember(defOrg))),
|
||||
},
|
||||
@@ -769,11 +780,11 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
c.allow = false
|
||||
return c
|
||||
}, []authTestCase{
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg)},
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.All()},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me")},
|
||||
{resource: ResourceWorkspace.WithOwner("not-me")},
|
||||
@@ -783,16 +794,16 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
}),
|
||||
// Allowed by scope:
|
||||
[]authTestCase{
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.UserID), actions: []Action{ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.ID), actions: []Action{ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner("not-me"), actions: []Action{ActionCreate}, allow: false},
|
||||
{resource: ResourceWorkspaceApplicationConnect.InOrg(unusedID).WithOwner("not-me"), actions: []Action{ActionCreate}, allow: false},
|
||||
},
|
||||
)
|
||||
|
||||
workspaceID := uuid.New()
|
||||
user = subject{
|
||||
UserID: "me",
|
||||
Roles: []Role{
|
||||
user = Subject{
|
||||
ID: "me",
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleMember())),
|
||||
must(RoleByName(RoleOrgMember(defOrg))),
|
||||
},
|
||||
@@ -818,11 +829,11 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
c.allow = false
|
||||
return c
|
||||
}, []authTestCase{
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg)},
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.All()},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me")},
|
||||
{resource: ResourceWorkspace.WithOwner("not-me")},
|
||||
@@ -838,11 +849,11 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
c.resource.WithID(workspaceID)
|
||||
return c
|
||||
}, []authTestCase{
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg)},
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.All()},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me")},
|
||||
{resource: ResourceWorkspace.WithOwner("not-me")},
|
||||
@@ -857,11 +868,11 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
c.resource.WithID(uuid.New())
|
||||
return c
|
||||
}, []authTestCase{
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg)},
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.All()},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me")},
|
||||
{resource: ResourceWorkspace.WithOwner("not-me")},
|
||||
@@ -871,7 +882,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
}),
|
||||
// Allowed by scope:
|
||||
[]authTestCase{
|
||||
{resource: ResourceWorkspace.WithID(workspaceID).InOrg(defOrg).WithOwner(user.UserID), actions: []Action{ActionRead}, allow: true},
|
||||
{resource: ResourceWorkspace.WithID(workspaceID).InOrg(defOrg).WithOwner(user.ID), actions: []Action{ActionRead}, allow: true},
|
||||
// The scope will return true, but the user perms return false for resources not owned by the user.
|
||||
{resource: ResourceWorkspace.WithID(workspaceID).InOrg(defOrg).WithOwner("not-me"), actions: []Action{ActionRead}, allow: false},
|
||||
{resource: ResourceWorkspace.WithID(workspaceID).InOrg(unusedID).WithOwner("not-me"), actions: []Action{ActionRead}, allow: false},
|
||||
@@ -879,9 +890,9 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
)
|
||||
|
||||
// This scope can only create workspaces
|
||||
user = subject{
|
||||
UserID: "me",
|
||||
Roles: []Role{
|
||||
user = Subject{
|
||||
ID: "me",
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleMember())),
|
||||
must(RoleByName(RoleOrgMember(defOrg))),
|
||||
},
|
||||
@@ -909,11 +920,11 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
c.resource.ID = uuid.NewString()
|
||||
return c
|
||||
}, []authTestCase{
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg)},
|
||||
{resource: ResourceWorkspace.WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.All()},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.UserID)},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID)},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID)},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me")},
|
||||
{resource: ResourceWorkspace.WithOwner("not-me")},
|
||||
@@ -924,7 +935,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
|
||||
// Test create allowed by scope:
|
||||
[]authTestCase{
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.UserID), actions: []Action{ActionCreate}, allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: []Action{ActionCreate}, allow: true},
|
||||
// The scope will return true, but the user perms return false for resources not owned by the user.
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me"), actions: []Action{ActionCreate}, allow: false},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me"), actions: []Action{ActionCreate}, allow: false},
|
||||
@@ -949,7 +960,7 @@ type authTestCase struct {
|
||||
allow bool
|
||||
}
|
||||
|
||||
func testAuthorize(t *testing.T, name string, subject subject, sets ...[]authTestCase) {
|
||||
func testAuthorize(t *testing.T, name string, subject Subject, sets ...[]authTestCase) {
|
||||
t.Helper()
|
||||
authorizer := NewAuthorizer(prometheus.NewRegistry())
|
||||
for _, cases := range sets {
|
||||
@@ -962,9 +973,10 @@ func testAuthorize(t *testing.T, name string, subject subject, sets ...[]authTes
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
authError := authorizer.Authorize(ctx, subject.UserID, subject.Roles, subject.Scope, subject.Groups, a, c.resource)
|
||||
authError := authorizer.Authorize(ctx, subject, a, c.resource)
|
||||
|
||||
d, _ := json.Marshal(map[string]interface{}{
|
||||
// This is not perfect marshal, but it is good enough for debugging this test.
|
||||
"subject": subject,
|
||||
"object": c.resource,
|
||||
"action": a,
|
||||
@@ -985,9 +997,14 @@ func testAuthorize(t *testing.T, name string, subject subject, sets ...[]authTes
|
||||
assert.Error(t, authError, "expected unauthorized")
|
||||
}
|
||||
|
||||
partialAuthz, err := authorizer.Prepare(ctx, subject.UserID, subject.Roles, subject.Scope, subject.Groups, a, c.resource.Type)
|
||||
prepared, err := authorizer.Prepare(ctx, subject, a, c.resource.Type)
|
||||
require.NoError(t, err, "make prepared authorizer")
|
||||
|
||||
// For unit testing logging and assertions, we want the PartialAuthorizer
|
||||
// struct.
|
||||
partialAuthz, ok := prepared.(*PartialAuthorizer)
|
||||
require.True(t, ok, "prepared authorizer is partial")
|
||||
|
||||
// Ensure the partial can compile to a SQL clause.
|
||||
// This does not guarantee that the clause is valid SQL.
|
||||
_, err = Compile(ConfigWithACL(), partialAuthz)
|
||||
|
||||
+53
-44
@@ -12,11 +12,8 @@ import (
|
||||
)
|
||||
|
||||
type benchmarkCase struct {
|
||||
Name string
|
||||
Roles rbac.RoleNames
|
||||
Groups []string
|
||||
UserID uuid.UUID
|
||||
Scope rbac.ScopeName
|
||||
Name string
|
||||
Actor rbac.Subject
|
||||
}
|
||||
|
||||
// benchmarkUserCases builds a set of users with different roles and groups.
|
||||
@@ -36,54 +33,66 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
|
||||
|
||||
benchCases := []benchmarkCase{
|
||||
{
|
||||
Name: "NoRoles",
|
||||
Roles: []string{},
|
||||
UserID: user,
|
||||
Scope: rbac.ScopeAll,
|
||||
Name: "NoRoles",
|
||||
Actor: rbac.Subject{
|
||||
ID: user.String(),
|
||||
Roles: rbac.RoleNames{},
|
||||
Scope: rbac.ScopeAll,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Admin",
|
||||
// Give some extra roles that an admin might have
|
||||
Roles: []string{rbac.RoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
|
||||
UserID: user,
|
||||
Scope: rbac.ScopeAll,
|
||||
Groups: noiseGroups,
|
||||
Actor: rbac.Subject{
|
||||
// Give some extra roles that an admin might have
|
||||
Roles: rbac.RoleNames{rbac.RoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
|
||||
ID: user.String(),
|
||||
Scope: rbac.ScopeAll,
|
||||
Groups: noiseGroups,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "OrgAdmin",
|
||||
Roles: []string{rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgAdmin(orgs[0]), rbac.RoleMember()},
|
||||
UserID: user,
|
||||
Scope: rbac.ScopeAll,
|
||||
Groups: noiseGroups,
|
||||
Name: "OrgAdmin",
|
||||
Actor: rbac.Subject{
|
||||
Roles: rbac.RoleNames{rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgAdmin(orgs[0]), rbac.RoleMember()},
|
||||
ID: user.String(),
|
||||
Scope: rbac.ScopeAll,
|
||||
Groups: noiseGroups,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "OrgMember",
|
||||
// Member of 2 orgs
|
||||
Roles: []string{rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgMember(orgs[1]), rbac.RoleMember()},
|
||||
UserID: user,
|
||||
Scope: rbac.ScopeAll,
|
||||
Groups: noiseGroups,
|
||||
Actor: rbac.Subject{
|
||||
// Member of 2 orgs
|
||||
Roles: rbac.RoleNames{rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgMember(orgs[1]), rbac.RoleMember()},
|
||||
ID: user.String(),
|
||||
Scope: rbac.ScopeAll,
|
||||
Groups: noiseGroups,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ManyRoles",
|
||||
// Admin of many orgs
|
||||
Roles: []string{
|
||||
rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgAdmin(orgs[0]),
|
||||
rbac.RoleOrgMember(orgs[1]), rbac.RoleOrgAdmin(orgs[1]),
|
||||
rbac.RoleOrgMember(orgs[2]), rbac.RoleOrgAdmin(orgs[2]),
|
||||
rbac.RoleMember(),
|
||||
Actor: rbac.Subject{
|
||||
// Admin of many orgs
|
||||
Roles: rbac.RoleNames{
|
||||
rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgAdmin(orgs[0]),
|
||||
rbac.RoleOrgMember(orgs[1]), rbac.RoleOrgAdmin(orgs[1]),
|
||||
rbac.RoleOrgMember(orgs[2]), rbac.RoleOrgAdmin(orgs[2]),
|
||||
rbac.RoleMember(),
|
||||
},
|
||||
ID: user.String(),
|
||||
Scope: rbac.ScopeAll,
|
||||
Groups: noiseGroups,
|
||||
},
|
||||
UserID: user,
|
||||
Scope: rbac.ScopeAll,
|
||||
Groups: noiseGroups,
|
||||
},
|
||||
{
|
||||
Name: "AdminWithScope",
|
||||
// Give some extra roles that an admin might have
|
||||
Roles: []string{rbac.RoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
|
||||
UserID: user,
|
||||
Scope: rbac.ScopeApplicationConnect,
|
||||
Groups: noiseGroups,
|
||||
Actor: rbac.Subject{
|
||||
// Give some extra roles that an admin might have
|
||||
Roles: rbac.RoleNames{rbac.RoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
|
||||
ID: user.String(),
|
||||
Scope: rbac.ScopeApplicationConnect,
|
||||
Groups: noiseGroups,
|
||||
},
|
||||
},
|
||||
}
|
||||
return benchCases, users, orgs
|
||||
@@ -108,7 +117,7 @@ func BenchmarkRBACAuthorize(b *testing.B) {
|
||||
objects := benchmarkSetup(orgs, users, b.N)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
allowed := authorizer.ByRoleName(context.Background(), c.UserID.String(), c.Roles, c.Scope, c.Groups, rbac.ActionRead, objects[b.N%len(objects)])
|
||||
allowed := authorizer.Authorize(context.Background(), c.Actor, rbac.ActionRead, objects[b.N%len(objects)])
|
||||
var _ = allowed
|
||||
}
|
||||
})
|
||||
@@ -136,8 +145,8 @@ func BenchmarkRBACAuthorizeGroups(b *testing.B) {
|
||||
for _, c := range benchCases {
|
||||
b.Run(c.Name+"GroupACL", func(b *testing.B) {
|
||||
userGroupAllow := uuid.NewString()
|
||||
c.Groups = append(c.Groups, userGroupAllow)
|
||||
c.Scope = rbac.ScopeAll
|
||||
c.Actor.Groups = append(c.Actor.Groups, userGroupAllow)
|
||||
c.Actor.Scope = rbac.ScopeAll
|
||||
objects := benchmarkSetup(orgs, users, b.N, func(object rbac.Object) rbac.Object {
|
||||
m := map[string][]rbac.Action{
|
||||
// Add the user's group
|
||||
@@ -149,7 +158,7 @@ func BenchmarkRBACAuthorizeGroups(b *testing.B) {
|
||||
uuid.NewString(): {rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
uuid.NewString(): {rbac.ActionRead, rbac.ActionUpdate},
|
||||
}
|
||||
for _, g := range c.Groups {
|
||||
for _, g := range c.Actor.Groups {
|
||||
// Every group the user is in will be added, but it will not match the perms. This makes the
|
||||
// authorizer look at many groups before finding the one that matches.
|
||||
m[g] = []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete}
|
||||
@@ -160,7 +169,7 @@ func BenchmarkRBACAuthorizeGroups(b *testing.B) {
|
||||
})
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
allowed := authorizer.ByRoleName(context.Background(), c.UserID.String(), c.Roles, c.Scope, c.Groups, neverMatchAction, objects[b.N%len(objects)])
|
||||
allowed := authorizer.Authorize(context.Background(), c.Actor, neverMatchAction, objects[b.N%len(objects)])
|
||||
var _ = allowed
|
||||
}
|
||||
})
|
||||
@@ -184,7 +193,7 @@ func BenchmarkRBACFilter(b *testing.B) {
|
||||
b.Run(c.Name, func(b *testing.B) {
|
||||
objects := benchmarkSetup(orgs, users, b.N)
|
||||
b.ResetTimer()
|
||||
allowed, err := rbac.Filter(context.Background(), authorizer, c.UserID.String(), c.Roles, c.Scope, c.Groups, rbac.ActionRead, objects)
|
||||
allowed, err := rbac.Filter(context.Background(), authorizer, c.Actor, rbac.ActionRead, objects)
|
||||
require.NoError(b, err)
|
||||
var _ = allowed
|
||||
})
|
||||
|
||||
@@ -226,7 +226,10 @@ var (
|
||||
// CanAssignRole is a helper function that returns true if the user can assign
|
||||
// the specified role. This also can be used for removing a role.
|
||||
// This is a simple implementation for now.
|
||||
func CanAssignRole(roles []string, assignedRole string) bool {
|
||||
func CanAssignRole(expandable ExpandableRoles, assignedRole string) bool {
|
||||
// For CanAssignRole, we only care about the names of the roles.
|
||||
roles := expandable.Names()
|
||||
|
||||
assigned, assignedOrg, err := roleSplit(assignedRole)
|
||||
if err != nil {
|
||||
return false
|
||||
|
||||
+16
-13
@@ -15,10 +15,8 @@ import (
|
||||
|
||||
type authSubject struct {
|
||||
// Name is helpful for test assertions
|
||||
Name string
|
||||
UserID string
|
||||
Roles rbac.RoleNames
|
||||
Groups []string
|
||||
Name string
|
||||
Actor rbac.Subject
|
||||
}
|
||||
|
||||
func TestRolePermissions(t *testing.T) {
|
||||
@@ -39,17 +37,17 @@ func TestRolePermissions(t *testing.T) {
|
||||
apiKeyID := uuid.New()
|
||||
|
||||
// Subjects to user
|
||||
memberMe := authSubject{Name: "member_me", UserID: currentUser.String(), Roles: []string{rbac.RoleMember()}}
|
||||
orgMemberMe := authSubject{Name: "org_member_me", UserID: currentUser.String(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(orgID)}}
|
||||
memberMe := authSubject{Name: "member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleNames{rbac.RoleMember()}}}
|
||||
orgMemberMe := authSubject{Name: "org_member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOrgMember(orgID)}}}
|
||||
|
||||
owner := authSubject{Name: "owner", UserID: adminID.String(), Roles: []string{rbac.RoleMember(), rbac.RoleOwner()}}
|
||||
orgAdmin := authSubject{Name: "org_admin", UserID: adminID.String(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(orgID), rbac.RoleOrgAdmin(orgID)}}
|
||||
owner := authSubject{Name: "owner", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOwner()}}}
|
||||
orgAdmin := authSubject{Name: "org_admin", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOrgMember(orgID), rbac.RoleOrgAdmin(orgID)}}}
|
||||
|
||||
otherOrgMember := authSubject{Name: "org_member_other", UserID: uuid.NewString(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(otherOrg)}}
|
||||
otherOrgAdmin := authSubject{Name: "org_admin_other", UserID: uuid.NewString(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(otherOrg), rbac.RoleOrgAdmin(otherOrg)}}
|
||||
otherOrgMember := authSubject{Name: "org_member_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOrgMember(otherOrg)}}}
|
||||
otherOrgAdmin := authSubject{Name: "org_admin_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOrgMember(otherOrg), rbac.RoleOrgAdmin(otherOrg)}}}
|
||||
|
||||
templateAdmin := authSubject{Name: "template-admin", UserID: templateAdminID.String(), Roles: []string{rbac.RoleMember(), rbac.RoleTemplateAdmin()}}
|
||||
userAdmin := authSubject{Name: "user-admin", UserID: templateAdminID.String(), Roles: []string{rbac.RoleMember(), rbac.RoleUserAdmin()}}
|
||||
templateAdmin := authSubject{Name: "template-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleTemplateAdmin()}}}
|
||||
userAdmin := authSubject{Name: "user-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleUserAdmin()}}}
|
||||
|
||||
// requiredSubjects are required to be asserted in each test case. This is
|
||||
// to make sure one is not forgotten.
|
||||
@@ -300,7 +298,12 @@ func TestRolePermissions(t *testing.T) {
|
||||
delete(remainingSubjs, subj.Name)
|
||||
msg := fmt.Sprintf("%s as %q doing %q on %q", c.Name, subj.Name, action, c.Resource.Type)
|
||||
// TODO: scopey
|
||||
err := auth.ByRoleName(context.Background(), subj.UserID, subj.Roles, rbac.ScopeAll, subj.Groups, action, c.Resource)
|
||||
actor := subj.Actor
|
||||
// Actor is missing some fields
|
||||
if actor.Scope == nil {
|
||||
actor.Scope = rbac.ScopeAll
|
||||
}
|
||||
err := auth.Authorize(context.Background(), actor, action, c.Resource)
|
||||
if result {
|
||||
assert.NoError(t, err, fmt.Sprintf("Should pass: %s", msg))
|
||||
} else {
|
||||
|
||||
+20
-3
@@ -121,13 +121,30 @@ EachQueryLoop:
|
||||
return ForbiddenWithInternal(xerrors.Errorf("policy disallows request"), pa.input, nil)
|
||||
}
|
||||
|
||||
func newPartialAuthorizer(ctx context.Context, subjectID string, roles []Role, scope Scope, groups []string, action Action, objectType string) (*PartialAuthorizer, error) {
|
||||
func newPartialAuthorizer(ctx context.Context, subject Subject, action Action, objectType string) (*PartialAuthorizer, error) {
|
||||
if subject.Roles == nil {
|
||||
return nil, xerrors.Errorf("subject must have roles")
|
||||
}
|
||||
if subject.Scope == nil {
|
||||
return nil, xerrors.Errorf("subject must have a scope")
|
||||
}
|
||||
|
||||
roles, err := subject.Roles.Expand()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("expand roles: %w", err)
|
||||
}
|
||||
|
||||
scope, err := subject.Scope.Expand()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("expand scope: %w", err)
|
||||
}
|
||||
|
||||
input := map[string]interface{}{
|
||||
"subject": authSubject{
|
||||
ID: subjectID,
|
||||
ID: subject.ID,
|
||||
Roles: roles,
|
||||
Scope: scope,
|
||||
Groups: groups,
|
||||
Groups: subject.Groups,
|
||||
},
|
||||
"object": map[string]string{
|
||||
"type": objectType,
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
package rbac
|
||||
|
||||
// ExpandableRoles is any type that can be expanded into a []Role. This is implemented
|
||||
// as an interface so we can have RoleNames for user defined roles, and implement
|
||||
// custom ExpandableRoles for system type users (eg autostart/autostop system role).
|
||||
// We want a clear divide between the two types of roles so users have no codepath
|
||||
// to interact or assign system roles.
|
||||
//
|
||||
// Note: We may also want to do the same thing with scopes to allow custom scope
|
||||
// support unavailable to the user. Eg: Scope to a single resource.
|
||||
type ExpandableRoles interface {
|
||||
Expand() ([]Role, error)
|
||||
// Names is for logging and tracing purposes, we want to know the human
|
||||
// names of the expanded roles.
|
||||
Names() []string
|
||||
}
|
||||
|
||||
// Permission is the format passed into the rego.
|
||||
type Permission struct {
|
||||
// Negate makes this a negative permission
|
||||
@@ -27,3 +42,17 @@ type Role struct {
|
||||
Org map[string][]Permission `json:"org"`
|
||||
User []Permission `json:"user"`
|
||||
}
|
||||
|
||||
type Roles []Role
|
||||
|
||||
func (roles Roles) Expand() ([]Role, error) {
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (roles Roles) Names() []string {
|
||||
names := make([]string, 0, len(roles))
|
||||
for _, r := range roles {
|
||||
return append(names, r.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
@@ -6,8 +6,23 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type ExpandableScope interface {
|
||||
Expand() (Scope, error)
|
||||
// Name is for logging and tracing purposes, we want to know the human
|
||||
// name of the scope.
|
||||
Name() string
|
||||
}
|
||||
|
||||
type ScopeName string
|
||||
|
||||
func (name ScopeName) Expand() (Scope, error) {
|
||||
return ExpandScope(name)
|
||||
}
|
||||
|
||||
func (name ScopeName) Name() string {
|
||||
return string(name)
|
||||
}
|
||||
|
||||
// Scope acts the exact same as a Role with the addition that is can also
|
||||
// apply an AllowIDList. Any resource being checked against a Scope will
|
||||
// reject any resource that is not in the AllowIDList.
|
||||
@@ -18,6 +33,14 @@ type Scope struct {
|
||||
AllowIDList []string `json:"allow_list"`
|
||||
}
|
||||
|
||||
func (s Scope) Expand() (Scope, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s Scope) Name() string {
|
||||
return s.Role.Name
|
||||
}
|
||||
|
||||
const (
|
||||
ScopeAll ScopeName = "all"
|
||||
ScopeApplicationConnect ScopeName = "application_connect"
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
|
||||
// rbacTraceAttributes are the attributes that are added to all spans created by
|
||||
// the rbac package. These attributes should help to debug slow spans.
|
||||
func rbacTraceAttributes(roles []string, groupCount int, scope ScopeName, action Action, objectType string, extra ...attribute.KeyValue) trace.SpanStartOption {
|
||||
func rbacTraceAttributes(actor Subject, action Action, objectType string, extra ...attribute.KeyValue) trace.SpanStartOption {
|
||||
return trace.WithAttributes(
|
||||
append(extra,
|
||||
attribute.StringSlice("subject_roles", roles),
|
||||
attribute.Int("num_subject_roles", len(roles)),
|
||||
attribute.Int("num_groups", groupCount),
|
||||
attribute.String("scope", string(scope)),
|
||||
attribute.StringSlice("subject_roles", actor.SafeRoleNames()),
|
||||
attribute.Int("num_subject_roles", len(actor.SafeRoleNames())),
|
||||
attribute.Int("num_groups", len(actor.Groups)),
|
||||
attribute.String("scope", actor.SafeScopeName()),
|
||||
attribute.String("action", string(action)),
|
||||
attribute.String("object_type", objectType),
|
||||
)...)
|
||||
|
||||
Reference in New Issue
Block a user