mirror of https://github.com/coder/coder.git
1167 lines
36 KiB
Go
1167 lines
36 KiB
Go
package rbac
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/v2/coderd/rbac/regosql"
|
|
"github.com/coder/coder/v2/testutil"
|
|
)
|
|
|
|
type fakeObject struct {
|
|
Owner uuid.UUID
|
|
OrgOwner uuid.UUID
|
|
Type string
|
|
Allowed bool
|
|
}
|
|
|
|
func (w fakeObject) RBACObject() Object {
|
|
return Object{
|
|
Owner: w.Owner.String(),
|
|
OrgID: w.OrgOwner.String(),
|
|
Type: w.Type,
|
|
}
|
|
}
|
|
|
|
// objectBomb is a wrapper around an Objecter that calls a function when
|
|
// RBACObject is called.
|
|
type objectBomb struct {
|
|
Objecter
|
|
bomb func()
|
|
}
|
|
|
|
func (o *objectBomb) RBACObject() Object {
|
|
o.bomb()
|
|
return o.Objecter.RBACObject()
|
|
}
|
|
|
|
func TestFilterError(t *testing.T) {
|
|
t.Parallel()
|
|
_ = objectBomb{}
|
|
|
|
t.Run("DifferentResourceTypes", func(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, subject, ActionRead, []Object{ResourceUser, ResourceWorkspace})
|
|
require.ErrorContains(t, err, "object types must be uniform")
|
|
})
|
|
|
|
t.Run("CancelledContext", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
auth := &MockAuthorizer{
|
|
AuthorizeFunc: func(ctx context.Context, subject Subject, action Action, object Object) error {
|
|
// Authorize func always returns nil, unless the context is canceled.
|
|
return ctx.Err()
|
|
},
|
|
}
|
|
|
|
subject := Subject{
|
|
ID: uuid.NewString(),
|
|
Roles: RoleNames{
|
|
RoleOwner(),
|
|
},
|
|
Groups: []string{},
|
|
Scope: ScopeAll,
|
|
}
|
|
|
|
t.Run("SmallSet", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
objects := []Objecter{
|
|
ResourceUser,
|
|
ResourceUser,
|
|
&objectBomb{
|
|
Objecter: ResourceUser,
|
|
bomb: cancel,
|
|
},
|
|
ResourceUser,
|
|
}
|
|
|
|
_, err := Filter(ctx, auth, subject, ActionRead, objects)
|
|
require.ErrorIs(t, err, context.Canceled)
|
|
})
|
|
|
|
// Triggers Prepared Authorize
|
|
t.Run("LargeSet", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
objects := make([]Objecter, 100)
|
|
for i := 0; i < 100; i++ {
|
|
objects[i] = ResourceUser
|
|
}
|
|
objects[20] = &objectBomb{
|
|
Objecter: ResourceUser,
|
|
bomb: cancel,
|
|
}
|
|
|
|
_, err := Filter(ctx, auth, subject, ActionRead, objects)
|
|
require.ErrorIs(t, err, context.Canceled)
|
|
})
|
|
})
|
|
}
|
|
|
|
// 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 Authorize function.
|
|
func TestFilter(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
orgIDs := make([]uuid.UUID, 10)
|
|
userIDs := make([]uuid.UUID, len(orgIDs))
|
|
for i := range orgIDs {
|
|
orgIDs[i] = uuid.New()
|
|
userIDs[i] = uuid.New()
|
|
}
|
|
objects := make([]fakeObject, 0, len(userIDs)*len(orgIDs))
|
|
for i := range userIDs {
|
|
for j := range orgIDs {
|
|
objects = append(objects, fakeObject{
|
|
Owner: userIDs[i],
|
|
OrgOwner: orgIDs[j],
|
|
Type: ResourceWorkspace.Type,
|
|
Allowed: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
Actor Subject
|
|
Action Action
|
|
ObjectType string
|
|
}{
|
|
{
|
|
Name: "NoRoles",
|
|
Actor: Subject{
|
|
ID: userIDs[0].String(),
|
|
Roles: RoleNames{},
|
|
},
|
|
ObjectType: ResourceWorkspace.Type,
|
|
Action: ActionRead,
|
|
},
|
|
{
|
|
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",
|
|
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",
|
|
Actor: Subject{
|
|
ID: userIDs[0].String(),
|
|
Roles: RoleNames{RoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
|
|
},
|
|
ObjectType: ResourceWorkspace.Type,
|
|
Action: ActionRead,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
actor := tc.Actor
|
|
|
|
localObjects := make([]fakeObject, len(objects))
|
|
copy(localObjects, objects)
|
|
|
|
auth := NewAuthorizer(prometheus.NewRegistry())
|
|
|
|
if actor.Scope == nil {
|
|
// Default to ScopeAll
|
|
actor.Scope = ScopeAll
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
defer cancel()
|
|
|
|
// Run auth 1 by 1
|
|
var allowedCount int
|
|
for i, obj := range localObjects {
|
|
obj.Type = tc.ObjectType
|
|
err := auth.Authorize(ctx, actor, ActionRead, obj.RBACObject())
|
|
obj.Allowed = err == nil
|
|
if err == nil {
|
|
allowedCount++
|
|
}
|
|
localObjects[i] = obj
|
|
}
|
|
|
|
// Run by filter
|
|
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 {
|
|
require.True(t, obj.Allowed, "expected allowed")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestAuthorizeDomain test the very basic roles that are commonly used.
|
|
func TestAuthorizeDomain(t *testing.T) {
|
|
t.Parallel()
|
|
defOrg := uuid.New()
|
|
unuseID := uuid.New()
|
|
allUsersGroup := "Everyone"
|
|
|
|
user := Subject{
|
|
ID: "me",
|
|
Scope: must(ExpandScope(ScopeAll)),
|
|
Groups: []string{allUsersGroup},
|
|
Roles: Roles{
|
|
must(RoleByName(RoleMember())),
|
|
must(RoleByName(RoleOrgMember(defOrg))),
|
|
},
|
|
}
|
|
|
|
testAuthorize(t, "UserACLList", user, []authTestCase{
|
|
{
|
|
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithACLUserList(map[string][]Action{
|
|
user.ID: AllActions(),
|
|
}),
|
|
actions: AllActions(),
|
|
allow: true,
|
|
},
|
|
{
|
|
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithACLUserList(map[string][]Action{
|
|
user.ID: {WildcardSymbol},
|
|
}),
|
|
actions: AllActions(),
|
|
allow: true,
|
|
},
|
|
{
|
|
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithACLUserList(map[string][]Action{
|
|
user.ID: {ActionRead, ActionUpdate},
|
|
}),
|
|
actions: []Action{ActionCreate, ActionDelete},
|
|
allow: false,
|
|
},
|
|
{
|
|
// By default users cannot update templates
|
|
resource: ResourceTemplate.InOrg(defOrg).WithACLUserList(map[string][]Action{
|
|
user.ID: {ActionUpdate},
|
|
}),
|
|
actions: []Action{ActionUpdate},
|
|
allow: true,
|
|
},
|
|
})
|
|
|
|
testAuthorize(t, "GroupACLList", user, []authTestCase{
|
|
{
|
|
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(defOrg).WithGroupACL(map[string][]Action{
|
|
allUsersGroup: AllActions(),
|
|
}),
|
|
actions: AllActions(),
|
|
allow: true,
|
|
},
|
|
{
|
|
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(defOrg).WithGroupACL(map[string][]Action{
|
|
allUsersGroup: {WildcardSymbol},
|
|
}),
|
|
actions: AllActions(),
|
|
allow: true,
|
|
},
|
|
{
|
|
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(defOrg).WithGroupACL(map[string][]Action{
|
|
allUsersGroup: {ActionRead, ActionUpdate},
|
|
}),
|
|
actions: []Action{ActionCreate, ActionDelete},
|
|
allow: false,
|
|
},
|
|
{
|
|
// By default users cannot update templates
|
|
resource: ResourceTemplate.InOrg(defOrg).WithGroupACL(map[string][]Action{
|
|
allUsersGroup: {ActionUpdate},
|
|
}),
|
|
actions: []Action{ActionUpdate},
|
|
allow: true,
|
|
},
|
|
})
|
|
|
|
testAuthorize(t, "Member", user, []authTestCase{
|
|
// Org + me
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: AllActions(), allow: true},
|
|
{resource: ResourceWorkspace.InOrg(defOrg), actions: AllActions(), allow: false},
|
|
|
|
{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.ID), actions: AllActions(), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(unuseID), actions: AllActions(), allow: false},
|
|
|
|
// Other org + other user
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me"), actions: AllActions(), allow: false},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), actions: AllActions(), allow: false},
|
|
|
|
// Other org + other us
|
|
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner("not-me"), actions: AllActions(), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(unuseID), actions: AllActions(), allow: false},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), actions: AllActions(), allow: false},
|
|
})
|
|
|
|
user = Subject{
|
|
ID: "me",
|
|
Scope: must(ExpandScope(ScopeAll)),
|
|
Roles: Roles{{
|
|
Name: "deny-all",
|
|
// List out deny permissions explicitly
|
|
Site: []Permission{
|
|
{
|
|
Negate: true,
|
|
ResourceType: WildcardSymbol,
|
|
Action: WildcardSymbol,
|
|
},
|
|
},
|
|
}},
|
|
}
|
|
|
|
testAuthorize(t, "DeletedMember", user, []authTestCase{
|
|
// Org + me
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: AllActions(), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(defOrg), 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.ID), actions: AllActions(), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(unuseID), actions: AllActions(), allow: false},
|
|
|
|
// Other org + other user
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me"), actions: AllActions(), allow: false},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), actions: AllActions(), allow: false},
|
|
|
|
// Other org + other use
|
|
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner("not-me"), actions: AllActions(), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(unuseID), actions: AllActions(), allow: false},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), actions: AllActions(), allow: false},
|
|
})
|
|
|
|
user = Subject{
|
|
ID: "me",
|
|
Scope: must(ExpandScope(ScopeAll)),
|
|
Roles: Roles{
|
|
must(RoleByName(RoleOrgAdmin(defOrg))),
|
|
must(RoleByName(RoleMember())),
|
|
},
|
|
}
|
|
|
|
testAuthorize(t, "OrgAdmin", user, []authTestCase{
|
|
// Org + me
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: AllActions(), allow: true},
|
|
{resource: ResourceWorkspace.InOrg(defOrg), 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.ID), actions: AllActions(), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(unuseID), actions: AllActions(), allow: false},
|
|
|
|
// Other org + other user
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me"), actions: AllActions(), allow: true},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), actions: AllActions(), allow: false},
|
|
|
|
// Other org + other use
|
|
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner("not-me"), actions: AllActions(), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(unuseID), actions: AllActions(), allow: false},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), actions: AllActions(), allow: false},
|
|
})
|
|
|
|
user = Subject{
|
|
ID: "me",
|
|
Scope: must(ExpandScope(ScopeAll)),
|
|
Roles: Roles{
|
|
must(RoleByName(RoleOwner())),
|
|
must(RoleByName(RoleMember())),
|
|
},
|
|
}
|
|
|
|
testAuthorize(t, "SiteAdmin", user, []authTestCase{
|
|
// Org + me
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: AllActions(), allow: true},
|
|
{resource: ResourceWorkspace.InOrg(defOrg), 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.ID), actions: AllActions(), allow: true},
|
|
{resource: ResourceWorkspace.InOrg(unuseID), actions: AllActions(), allow: true},
|
|
|
|
// Other org + other user
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me"), actions: AllActions(), allow: true},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), actions: AllActions(), allow: true},
|
|
|
|
// Other org + other use
|
|
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner("not-me"), actions: AllActions(), allow: true},
|
|
{resource: ResourceWorkspace.InOrg(unuseID), actions: AllActions(), allow: true},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), actions: AllActions(), allow: true},
|
|
})
|
|
|
|
user = Subject{
|
|
ID: "me",
|
|
Scope: must(ExpandScope(ScopeApplicationConnect)),
|
|
Roles: Roles{
|
|
must(RoleByName(RoleOrgMember(defOrg))),
|
|
must(RoleByName(RoleMember())),
|
|
},
|
|
}
|
|
|
|
testAuthorize(t, "ApplicationToken", user,
|
|
// Create (connect) Actions
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = []Action{ActionCreate}
|
|
return c
|
|
}, []authTestCase{
|
|
// Org + me
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.ID), allow: true},
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg), allow: false},
|
|
|
|
{resource: ResourceWorkspaceApplicationConnect.WithOwner(user.ID), allow: true},
|
|
|
|
{resource: ResourceWorkspaceApplicationConnect.All(), allow: false},
|
|
|
|
// Other org + me
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID).WithOwner(user.ID), allow: false},
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID), allow: false},
|
|
|
|
// Other org + other user
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner("not-me"), allow: false},
|
|
|
|
{resource: ResourceWorkspaceApplicationConnect.WithOwner("not-me"), allow: false},
|
|
|
|
// Other org + other use
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID).WithOwner("not-me"), allow: false},
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID), allow: false},
|
|
|
|
{resource: ResourceWorkspaceApplicationConnect.WithOwner("not-me"), allow: false},
|
|
}),
|
|
// Not create actions
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = []Action{ActionRead, ActionUpdate, ActionDelete}
|
|
c.allow = false
|
|
return c
|
|
}, []authTestCase{
|
|
// Org + me
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg)},
|
|
|
|
{resource: ResourceWorkspaceApplicationConnect.WithOwner(user.ID)},
|
|
|
|
{resource: ResourceWorkspaceApplicationConnect.All()},
|
|
|
|
// Other org + me
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID)},
|
|
|
|
// Other org + other user
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner("not-me")},
|
|
|
|
{resource: ResourceWorkspaceApplicationConnect.WithOwner("not-me")},
|
|
|
|
// Other org + other use
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID).WithOwner("not-me")},
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(unuseID)},
|
|
|
|
{resource: ResourceWorkspaceApplicationConnect.WithOwner("not-me")},
|
|
}),
|
|
// Other Objects
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = []Action{ActionCreate, ActionRead, ActionUpdate, ActionDelete}
|
|
c.allow = false
|
|
return c
|
|
}, []authTestCase{
|
|
// Org + me
|
|
{resource: ResourceTemplate.InOrg(defOrg).WithOwner(user.ID)},
|
|
{resource: ResourceTemplate.InOrg(defOrg)},
|
|
|
|
{resource: ResourceTemplate.WithOwner(user.ID)},
|
|
|
|
{resource: ResourceTemplate.All()},
|
|
|
|
// Other org + me
|
|
{resource: ResourceTemplate.InOrg(unuseID).WithOwner(user.ID)},
|
|
{resource: ResourceTemplate.InOrg(unuseID)},
|
|
|
|
// Other org + other user
|
|
{resource: ResourceTemplate.InOrg(defOrg).WithOwner("not-me")},
|
|
|
|
{resource: ResourceTemplate.WithOwner("not-me")},
|
|
|
|
// Other org + other use
|
|
{resource: ResourceTemplate.InOrg(unuseID).WithOwner("not-me")},
|
|
{resource: ResourceTemplate.InOrg(unuseID)},
|
|
|
|
{resource: ResourceTemplate.WithOwner("not-me")},
|
|
}),
|
|
)
|
|
|
|
// In practice this is a token scope on a regular subject
|
|
user = Subject{
|
|
ID: "me",
|
|
Scope: must(ExpandScope(ScopeAll)),
|
|
Roles: Roles{
|
|
{
|
|
Name: "ReadOnlyOrgAndUser",
|
|
Site: []Permission{},
|
|
Org: map[string][]Permission{
|
|
defOrg.String(): {{
|
|
Negate: false,
|
|
ResourceType: "*",
|
|
Action: ActionRead,
|
|
}},
|
|
},
|
|
User: []Permission{
|
|
{
|
|
Negate: false,
|
|
ResourceType: "*",
|
|
Action: ActionRead,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
testAuthorize(t, "ReadOnly", user,
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = []Action{ActionRead}
|
|
return c
|
|
}, []authTestCase{
|
|
// Read
|
|
// Org + me
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), allow: true},
|
|
{resource: ResourceWorkspace.InOrg(defOrg), allow: true},
|
|
|
|
{resource: ResourceWorkspace.WithOwner(user.ID), allow: true},
|
|
|
|
{resource: ResourceWorkspace.All(), allow: false},
|
|
|
|
// Other org + me
|
|
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.ID), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(unuseID), allow: false},
|
|
|
|
// Other org + other user
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me"), allow: true},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), allow: false},
|
|
|
|
// Other org + other use
|
|
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner("not-me"), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(unuseID), allow: false},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), allow: false},
|
|
}),
|
|
|
|
// Pass non-read actions
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = []Action{ActionCreate, ActionUpdate, ActionDelete}
|
|
c.allow = false
|
|
return c
|
|
}, []authTestCase{
|
|
// Read
|
|
// Org + me
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(defOrg)},
|
|
|
|
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
|
|
|
{resource: ResourceWorkspace.All()},
|
|
|
|
// Other org + me
|
|
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(unuseID)},
|
|
|
|
// Other org + other user
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me")},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
|
|
// Other org + other use
|
|
{resource: ResourceWorkspace.InOrg(unuseID).WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.InOrg(unuseID)},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
}))
|
|
}
|
|
|
|
// TestAuthorizeLevels ensures level overrides are acting appropriately
|
|
func TestAuthorizeLevels(t *testing.T) {
|
|
t.Parallel()
|
|
defOrg := uuid.New()
|
|
unusedID := uuid.New()
|
|
|
|
user := Subject{
|
|
ID: "me",
|
|
Scope: must(ExpandScope(ScopeAll)),
|
|
Roles: Roles{
|
|
must(RoleByName(RoleOwner())),
|
|
{
|
|
Name: "org-deny:" + defOrg.String(),
|
|
Org: map[string][]Permission{
|
|
defOrg.String(): {
|
|
{
|
|
Negate: true,
|
|
ResourceType: "*",
|
|
Action: "*",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "user-deny-all",
|
|
// List out deny permissions explicitly
|
|
User: []Permission{
|
|
{
|
|
Negate: true,
|
|
ResourceType: WildcardSymbol,
|
|
Action: WildcardSymbol,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
testAuthorize(t, "AdminAlwaysAllow", user,
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = AllActions()
|
|
c.allow = true
|
|
return c
|
|
}, []authTestCase{
|
|
// Org + me
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(defOrg)},
|
|
|
|
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
|
|
|
{resource: ResourceWorkspace.All()},
|
|
|
|
// Other org + me
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(unusedID)},
|
|
|
|
// Other org + other user
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me")},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
|
|
// Other org + other use
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.InOrg(unusedID)},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
}))
|
|
|
|
user = Subject{
|
|
ID: "me",
|
|
Scope: must(ExpandScope(ScopeAll)),
|
|
Roles: Roles{
|
|
{
|
|
Name: "site-noise",
|
|
Site: []Permission{
|
|
{
|
|
Negate: true,
|
|
ResourceType: "random",
|
|
Action: WildcardSymbol,
|
|
},
|
|
},
|
|
},
|
|
must(RoleByName(RoleOrgAdmin(defOrg))),
|
|
{
|
|
Name: "user-deny-all",
|
|
// List out deny permissions explicitly
|
|
User: []Permission{
|
|
{
|
|
Negate: true,
|
|
ResourceType: WildcardSymbol,
|
|
Action: WildcardSymbol,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
testAuthorize(t, "OrgAllowAll", user,
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = AllActions()
|
|
return c
|
|
}, []authTestCase{
|
|
// Org + me
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), allow: true},
|
|
{resource: ResourceWorkspace.InOrg(defOrg), allow: true},
|
|
|
|
{resource: ResourceWorkspace.WithOwner(user.ID), allow: false},
|
|
|
|
{resource: ResourceWorkspace.All(), allow: false},
|
|
|
|
// Other org + me
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(unusedID), allow: false},
|
|
|
|
// Other org + other user
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me"), allow: true},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), allow: false},
|
|
|
|
// Other org + other use
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me"), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(unusedID), allow: false},
|
|
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), allow: false},
|
|
}))
|
|
}
|
|
|
|
func TestAuthorizeScope(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
defOrg := uuid.New()
|
|
unusedID := uuid.New()
|
|
user := Subject{
|
|
ID: "me",
|
|
Roles: Roles{must(RoleByName(RoleOwner()))},
|
|
Scope: must(ExpandScope(ScopeApplicationConnect)),
|
|
}
|
|
|
|
testAuthorize(t, "Admin_ScopeApplicationConnect", user,
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = []Action{ActionRead, ActionUpdate, ActionDelete}
|
|
return c
|
|
}, []authTestCase{
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(defOrg), allow: false},
|
|
{resource: ResourceWorkspace.WithOwner(user.ID), allow: false},
|
|
{resource: ResourceWorkspace.All(), 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},
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me"), allow: false},
|
|
{resource: ResourceWorkspace.InOrg(unusedID), allow: false},
|
|
{resource: ResourceWorkspace.WithOwner("not-me"), allow: false},
|
|
}),
|
|
// Allowed by scope:
|
|
[]authTestCase{
|
|
{resource: ResourceWorkspaceApplicationConnect.InOrg(defOrg).WithOwner("not-me"), 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{
|
|
ID: "me",
|
|
Roles: Roles{
|
|
must(RoleByName(RoleMember())),
|
|
must(RoleByName(RoleOrgMember(defOrg))),
|
|
},
|
|
Scope: must(ExpandScope(ScopeApplicationConnect)),
|
|
}
|
|
|
|
testAuthorize(t, "User_ScopeApplicationConnect", user,
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = []Action{ActionRead, ActionUpdate, ActionDelete}
|
|
c.allow = false
|
|
return c
|
|
}, []authTestCase{
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(defOrg)},
|
|
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.All()},
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(unusedID)},
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.InOrg(unusedID)},
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
}),
|
|
// Allowed by scope:
|
|
[]authTestCase{
|
|
{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{
|
|
ID: "me",
|
|
Roles: Roles{
|
|
must(RoleByName(RoleMember())),
|
|
must(RoleByName(RoleOrgMember(defOrg))),
|
|
},
|
|
Scope: Scope{
|
|
Role: Role{
|
|
Name: "workspace_agent",
|
|
DisplayName: "Workspace Agent",
|
|
Site: Permissions(map[string][]Action{
|
|
// Only read access for workspaces.
|
|
ResourceWorkspace.Type: {ActionRead},
|
|
}),
|
|
Org: map[string][]Permission{},
|
|
User: []Permission{},
|
|
},
|
|
AllowIDList: []string{workspaceID.String()},
|
|
},
|
|
}
|
|
|
|
testAuthorize(t, "User_WorkspaceAgent", user,
|
|
// Test cases without ID
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = []Action{ActionCreate, ActionUpdate, ActionDelete}
|
|
c.allow = false
|
|
return c
|
|
}, []authTestCase{
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(defOrg)},
|
|
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.All()},
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(unusedID)},
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.InOrg(unusedID)},
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
}),
|
|
|
|
// Test all cases with the workspace id
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = []Action{ActionCreate, ActionUpdate, ActionDelete}
|
|
c.allow = false
|
|
c.resource.WithID(workspaceID)
|
|
return c
|
|
}, []authTestCase{
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(defOrg)},
|
|
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.All()},
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(unusedID)},
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.InOrg(unusedID)},
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
}),
|
|
// Test cases with random ids. These should always fail from the scope.
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = []Action{ActionRead, ActionCreate, ActionUpdate, ActionDelete}
|
|
c.allow = false
|
|
c.resource.WithID(uuid.New())
|
|
return c
|
|
}, []authTestCase{
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(defOrg)},
|
|
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.All()},
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(unusedID)},
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.InOrg(unusedID)},
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
}),
|
|
// Allowed by scope:
|
|
[]authTestCase{
|
|
{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},
|
|
},
|
|
)
|
|
|
|
// This scope can only create workspaces
|
|
user = Subject{
|
|
ID: "me",
|
|
Roles: Roles{
|
|
must(RoleByName(RoleMember())),
|
|
must(RoleByName(RoleOrgMember(defOrg))),
|
|
},
|
|
Scope: Scope{
|
|
Role: Role{
|
|
Name: "create_workspace",
|
|
DisplayName: "Create Workspace",
|
|
Site: Permissions(map[string][]Action{
|
|
// Only read access for workspaces.
|
|
ResourceWorkspace.Type: {ActionCreate},
|
|
}),
|
|
Org: map[string][]Permission{},
|
|
User: []Permission{},
|
|
},
|
|
// Empty string allow_list is allowed for actions like 'create'
|
|
AllowIDList: []string{""},
|
|
},
|
|
}
|
|
|
|
testAuthorize(t, "CreatWorkspaceScope", user,
|
|
// All these cases will fail because a resource ID is set.
|
|
cases(func(c authTestCase) authTestCase {
|
|
c.actions = []Action{ActionCreate, ActionRead, ActionUpdate, ActionDelete}
|
|
c.allow = false
|
|
c.resource.ID = uuid.NewString()
|
|
return c
|
|
}, []authTestCase{
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(defOrg)},
|
|
{resource: ResourceWorkspace.WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.All()},
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID)},
|
|
{resource: ResourceWorkspace.InOrg(unusedID)},
|
|
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me")},
|
|
{resource: ResourceWorkspace.InOrg(unusedID)},
|
|
{resource: ResourceWorkspace.WithOwner("not-me")},
|
|
}),
|
|
|
|
// Test create allowed by scope:
|
|
[]authTestCase{
|
|
{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},
|
|
},
|
|
)
|
|
}
|
|
|
|
// cases applies a given function to all test cases. This makes generalities easier to create.
|
|
func cases(opt func(c authTestCase) authTestCase, cases []authTestCase) []authTestCase {
|
|
if opt == nil {
|
|
return cases
|
|
}
|
|
for i := range cases {
|
|
cases[i] = opt(cases[i])
|
|
}
|
|
return cases
|
|
}
|
|
|
|
type authTestCase struct {
|
|
resource Object
|
|
actions []Action
|
|
allow bool
|
|
}
|
|
|
|
func testAuthorize(t *testing.T, name string, subject Subject, sets ...[]authTestCase) {
|
|
t.Helper()
|
|
authorizer := NewAuthorizer(prometheus.NewRegistry())
|
|
for _, cases := range sets {
|
|
for i, c := range cases {
|
|
c := c
|
|
caseName := fmt.Sprintf("%s/%d", name, i)
|
|
t.Run(caseName, func(t *testing.T) {
|
|
t.Parallel()
|
|
for _, a := range c.actions {
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
|
t.Cleanup(cancel)
|
|
|
|
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": authSubject{
|
|
ID: subject.ID,
|
|
Roles: must(subject.Roles.Expand()),
|
|
Groups: subject.Groups,
|
|
Scope: must(subject.Scope.Expand()),
|
|
},
|
|
"object": c.resource,
|
|
"action": a,
|
|
})
|
|
|
|
// Logging only
|
|
t.Logf("input: %s", string(d))
|
|
if authError != nil {
|
|
var uerr *UnauthorizedError
|
|
xerrors.As(authError, &uerr)
|
|
t.Logf("internal error: %+v", uerr.Internal().Error())
|
|
t.Logf("output: %+v", uerr.Output())
|
|
}
|
|
|
|
if c.allow {
|
|
assert.NoError(t, authError, "expected no error for testcase action %s", a)
|
|
} else {
|
|
assert.Error(t, authError, "expected unauthorized")
|
|
}
|
|
|
|
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)
|
|
require.NoError(t, err, "compile prepared authorizer")
|
|
|
|
// Also check the rego policy can form a valid partial query result.
|
|
// This ensures we can convert the queries into SQL WHERE clauses in the future.
|
|
// If this function returns 'Support' sections, then we cannot convert the query into SQL.
|
|
for _, q := range partialAuthz.partialQueries.Queries {
|
|
t.Logf("query: %+v", q.String())
|
|
}
|
|
for _, s := range partialAuthz.partialQueries.Support {
|
|
t.Logf("support: %+v", s.String())
|
|
}
|
|
|
|
require.Equal(t, 0, len(partialAuthz.partialQueries.Support), "expected 0 support rules in scope authorizer")
|
|
|
|
partialErr := partialAuthz.Authorize(ctx, c.resource)
|
|
if authError != nil {
|
|
assert.Error(t, partialErr, "partial allowed invalid request (false positive)")
|
|
} else {
|
|
assert.NoError(t, partialErr, "partial error blocked valid request (false negative)")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func must[T any](value T, err error) T {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return value
|
|
}
|
|
|
|
type MockAuthorizer struct {
|
|
AuthorizeFunc func(context.Context, Subject, Action, Object) error
|
|
}
|
|
|
|
var _ Authorizer = (*MockAuthorizer)(nil)
|
|
|
|
func (d *MockAuthorizer) Authorize(ctx context.Context, s Subject, a Action, o Object) error {
|
|
return d.AuthorizeFunc(ctx, s, a, o)
|
|
}
|
|
|
|
func (d *MockAuthorizer) Prepare(_ context.Context, subject Subject, action Action, _ string) (PreparedAuthorized, error) {
|
|
return &mockPreparedAuthorizer{
|
|
Original: d,
|
|
Subject: subject,
|
|
Action: action,
|
|
}, nil
|
|
}
|
|
|
|
var _ PreparedAuthorized = (*mockPreparedAuthorizer)(nil)
|
|
|
|
// fakePreparedAuthorizer is the prepared version of a FakeAuthorizer. It will
|
|
// return the same error as the original FakeAuthorizer.
|
|
type mockPreparedAuthorizer struct {
|
|
sync.RWMutex
|
|
Original *MockAuthorizer
|
|
Subject Subject
|
|
Action Action
|
|
}
|
|
|
|
func (f *mockPreparedAuthorizer) Authorize(ctx context.Context, object Object) error {
|
|
return f.Original.Authorize(ctx, f.Subject, f.Action, object)
|
|
}
|
|
|
|
// CompileToSQL returns a compiled version of the authorizer that will work for
|
|
// in memory databases. This fake version will not work against a SQL database.
|
|
func (*mockPreparedAuthorizer) CompileToSQL(_ context.Context, _ regosql.ConvertConfig) (string, error) {
|
|
return "not a valid sql string", nil
|
|
}
|