mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(coderd/rbac): make organization-member a per-org system custom role (#21359)
Migrated the built-in organization-member role to DB storage so it can be customized per org. Closes https://github.com/coder/internal/issues/1073 (part 1)
This commit is contained in:
@@ -73,6 +73,7 @@ func TestInsertCustomRoles(t *testing.T) {
|
||||
site []codersdk.Permission
|
||||
org []codersdk.Permission
|
||||
user []codersdk.Permission
|
||||
member []codersdk.Permission
|
||||
errorContains string
|
||||
}{
|
||||
{
|
||||
@@ -171,6 +172,16 @@ func TestInsertCustomRoles(t *testing.T) {
|
||||
}),
|
||||
errorContains: "organization roles specify site or user permissions",
|
||||
},
|
||||
{
|
||||
// Not allowing these at this time.
|
||||
name: "member-permissions",
|
||||
organizationID: orgID,
|
||||
subject: merge(canCreateCustomRole),
|
||||
member: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
|
||||
codersdk.ResourceWorkspace: {codersdk.ActionRead},
|
||||
}),
|
||||
errorContains: "non-system roles specify member permissions",
|
||||
},
|
||||
{
|
||||
name: "site-escalation",
|
||||
organizationID: orgID,
|
||||
@@ -213,12 +224,13 @@ func TestInsertCustomRoles(t *testing.T) {
|
||||
ctx = dbauthz.As(ctx, subject)
|
||||
|
||||
_, err := az.InsertCustomRole(ctx, database.InsertCustomRoleParams{
|
||||
Name: "test-role",
|
||||
DisplayName: "",
|
||||
OrganizationID: uuid.NullUUID{UUID: tc.organizationID, Valid: true},
|
||||
SitePermissions: db2sdk.List(tc.site, convertSDKPerm),
|
||||
OrgPermissions: db2sdk.List(tc.org, convertSDKPerm),
|
||||
UserPermissions: db2sdk.List(tc.user, convertSDKPerm),
|
||||
Name: "test-role",
|
||||
DisplayName: "",
|
||||
OrganizationID: uuid.NullUUID{UUID: tc.organizationID, Valid: true},
|
||||
SitePermissions: db2sdk.List(tc.site, convertSDKPerm),
|
||||
OrgPermissions: db2sdk.List(tc.org, convertSDKPerm),
|
||||
UserPermissions: db2sdk.List(tc.user, convertSDKPerm),
|
||||
MemberPermissions: db2sdk.List(tc.member, convertSDKPerm),
|
||||
})
|
||||
if tc.errorContains != "" {
|
||||
require.ErrorContains(t, err, tc.errorContains)
|
||||
@@ -250,3 +262,220 @@ func convertSDKPerm(perm codersdk.Permission) database.CustomRolePermission {
|
||||
Action: policy.Action(perm.Action),
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
orgID := uuid.New()
|
||||
|
||||
canManageOrgRoles := rbac.Role{
|
||||
Identifier: rbac.RoleIdentifier{Name: "can-manage-org-roles"},
|
||||
DisplayName: "",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceAssignOrgRole.Type: {policy.ActionRead, policy.ActionCreate, policy.ActionUpdate},
|
||||
}),
|
||||
}
|
||||
|
||||
canCreateSystem := rbac.Role{
|
||||
Identifier: rbac.RoleIdentifier{Name: "can-create-system"},
|
||||
DisplayName: "",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceSystem.Type: {policy.ActionCreate},
|
||||
}),
|
||||
}
|
||||
|
||||
canUpdateSystem := rbac.Role{
|
||||
Identifier: rbac.RoleIdentifier{Name: "can-update-system"},
|
||||
DisplayName: "",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceSystem.Type: {policy.ActionUpdate},
|
||||
}),
|
||||
}
|
||||
|
||||
userID := uuid.New()
|
||||
subjectNoSystemPerms := rbac.Subject{
|
||||
FriendlyName: "Test user",
|
||||
ID: userID.String(),
|
||||
Roles: rbac.Roles([]rbac.Role{canManageOrgRoles}),
|
||||
Groups: nil,
|
||||
Scope: rbac.ScopeAll,
|
||||
}
|
||||
subjectWithSystemCreatePerms := subjectNoSystemPerms
|
||||
subjectWithSystemCreatePerms.Roles = rbac.Roles([]rbac.Role{canManageOrgRoles, canCreateSystem})
|
||||
subjectWithSystemUpdatePerms := subjectNoSystemPerms
|
||||
subjectWithSystemUpdatePerms.Roles = rbac.Roles([]rbac.Role{canManageOrgRoles, canUpdateSystem})
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
rec := &coderdtest.RecordingAuthorizer{
|
||||
Wrapped: rbac.NewAuthorizer(prometheus.NewRegistry()),
|
||||
}
|
||||
az := dbauthz.New(db, rec, slog.Make(), coderdtest.AccessControlStorePointer())
|
||||
|
||||
t.Run("insert-requires-system-create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
insertParamsTemplate := database.InsertCustomRoleParams{
|
||||
Name: "",
|
||||
OrganizationID: uuid.NullUUID{
|
||||
UUID: orgID,
|
||||
Valid: true,
|
||||
},
|
||||
SitePermissions: database.CustomRolePermissions{},
|
||||
OrgPermissions: database.CustomRolePermissions{},
|
||||
UserPermissions: database.CustomRolePermissions{},
|
||||
MemberPermissions: database.CustomRolePermissions{},
|
||||
IsSystem: true,
|
||||
}
|
||||
|
||||
t.Run("deny-no-system-perms", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
insertParams := insertParamsTemplate
|
||||
insertParams.Name = "test-system-role-" + uuid.NewString()
|
||||
|
||||
ctx = dbauthz.As(ctx, subjectNoSystemPerms)
|
||||
|
||||
_, err := az.InsertCustomRole(ctx, insertParams)
|
||||
require.ErrorContains(t, err, "forbidden")
|
||||
})
|
||||
|
||||
t.Run("deny-update-only", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
insertParams := insertParamsTemplate
|
||||
insertParams.Name = "test-system-role-" + uuid.NewString()
|
||||
|
||||
ctx = dbauthz.As(ctx, subjectWithSystemUpdatePerms)
|
||||
|
||||
_, err := az.InsertCustomRole(ctx, insertParams)
|
||||
require.ErrorContains(t, err, "forbidden")
|
||||
})
|
||||
|
||||
t.Run("allow-create-only", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
insertParams := insertParamsTemplate
|
||||
insertParams.Name = "test-system-role-" + uuid.NewString()
|
||||
|
||||
ctx = dbauthz.As(ctx, subjectWithSystemCreatePerms)
|
||||
|
||||
_, err := az.InsertCustomRole(ctx, insertParams)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("update-requires-system-update", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
ctx = dbauthz.As(ctx, subjectWithSystemCreatePerms)
|
||||
|
||||
// Setup: create the role that we will attempt to update in
|
||||
// subtests. One role for all is fine as we are only testing
|
||||
// authz.
|
||||
role, err := az.InsertCustomRole(ctx, database.InsertCustomRoleParams{
|
||||
Name: "test-system-role-" + uuid.NewString(),
|
||||
OrganizationID: uuid.NullUUID{
|
||||
UUID: orgID,
|
||||
Valid: true,
|
||||
},
|
||||
SitePermissions: database.CustomRolePermissions{},
|
||||
OrgPermissions: database.CustomRolePermissions{},
|
||||
UserPermissions: database.CustomRolePermissions{},
|
||||
MemberPermissions: database.CustomRolePermissions{},
|
||||
IsSystem: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use same params for all updates as we're only testing authz.
|
||||
updateParams := database.UpdateCustomRoleParams{
|
||||
Name: role.Name,
|
||||
OrganizationID: uuid.NullUUID{
|
||||
UUID: orgID,
|
||||
Valid: true,
|
||||
},
|
||||
DisplayName: "",
|
||||
SitePermissions: database.CustomRolePermissions{},
|
||||
OrgPermissions: database.CustomRolePermissions{},
|
||||
UserPermissions: database.CustomRolePermissions{},
|
||||
MemberPermissions: database.CustomRolePermissions{},
|
||||
}
|
||||
|
||||
t.Run("deny-no-system-perms", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
ctx = dbauthz.As(ctx, subjectNoSystemPerms)
|
||||
|
||||
_, err := az.UpdateCustomRole(ctx, updateParams)
|
||||
require.ErrorContains(t, err, "forbidden")
|
||||
})
|
||||
|
||||
t.Run("deny-create-only", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
ctx = dbauthz.As(ctx, subjectWithSystemCreatePerms)
|
||||
|
||||
_, err := az.UpdateCustomRole(ctx, updateParams)
|
||||
require.ErrorContains(t, err, "forbidden")
|
||||
})
|
||||
|
||||
t.Run("allow-update-only", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
ctx = dbauthz.As(ctx, subjectWithSystemUpdatePerms)
|
||||
|
||||
_, err := az.UpdateCustomRole(ctx, updateParams)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("allow-member-permissions", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
ctx = dbauthz.As(ctx, subjectWithSystemCreatePerms)
|
||||
|
||||
_, err := az.InsertCustomRole(ctx, database.InsertCustomRoleParams{
|
||||
Name: "test-system-role-member-perms",
|
||||
OrganizationID: uuid.NullUUID{
|
||||
UUID: orgID,
|
||||
Valid: true,
|
||||
},
|
||||
SitePermissions: database.CustomRolePermissions{},
|
||||
OrgPermissions: database.CustomRolePermissions{},
|
||||
UserPermissions: database.CustomRolePermissions{},
|
||||
MemberPermissions: database.CustomRolePermissions{
|
||||
{
|
||||
ResourceType: rbac.ResourceWorkspace.Type,
|
||||
Action: policy.ActionRead,
|
||||
},
|
||||
},
|
||||
IsSystem: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("allow-negative-permissions", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
ctx = dbauthz.As(ctx, subjectWithSystemCreatePerms)
|
||||
|
||||
_, err := az.InsertCustomRole(ctx, database.InsertCustomRoleParams{
|
||||
Name: "test-system-role-negative",
|
||||
OrganizationID: uuid.NullUUID{
|
||||
UUID: orgID,
|
||||
Valid: true,
|
||||
},
|
||||
SitePermissions: database.CustomRolePermissions{},
|
||||
OrgPermissions: database.CustomRolePermissions{
|
||||
{
|
||||
Negate: true,
|
||||
ResourceType: rbac.ResourceWorkspace.Type,
|
||||
Action: policy.ActionShare,
|
||||
},
|
||||
},
|
||||
UserPermissions: database.CustomRolePermissions{},
|
||||
MemberPermissions: database.CustomRolePermissions{},
|
||||
IsSystem: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1161,13 +1161,18 @@ func (q *querier) canAssignRoles(ctx context.Context, orgID uuid.UUID, added, re
|
||||
|
||||
for _, roleName := range grantedRoles {
|
||||
if _, isCustom := customRolesMap[roleName]; isCustom {
|
||||
// To support a dynamic mapping of what roles can assign what, we need
|
||||
// to store this in the database. For now, just use a static role so
|
||||
// owners and org admins can assign roles.
|
||||
if roleName.IsOrgRole() {
|
||||
roleName = rbac.CustomOrganizationRole(roleName.OrganizationID)
|
||||
} else {
|
||||
roleName = rbac.CustomSiteRole()
|
||||
// System roles are stored in the database but have a fixed, code-defined
|
||||
// meaning. Do not rewrite the name for them so the static "who can assign
|
||||
// what" mapping applies.
|
||||
if !rbac.SystemRoleName(roleName.Name) {
|
||||
// To support a dynamic mapping of what roles can assign what, we need
|
||||
// to store this in the database. For now, just use a static role so
|
||||
// owners and org admins can assign roles.
|
||||
if roleName.IsOrgRole() {
|
||||
roleName = rbac.CustomOrganizationRole(roleName.OrganizationID)
|
||||
} else {
|
||||
roleName = rbac.CustomSiteRole()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1282,33 +1287,39 @@ func (q *querier) customRoleEscalationCheck(ctx context.Context, actor rbac.Subj
|
||||
// - Check custom roles are valid for their resource types + actions
|
||||
// - Check the actor can create the custom role
|
||||
// - Check the custom role does not grant perms the actor does not have
|
||||
// - Prevent negative perms
|
||||
// - Prevent roles with site and org permissions.
|
||||
func (q *querier) customRoleCheck(ctx context.Context, role database.CustomRole) error {
|
||||
// - Prevent negative perms for non-system roles
|
||||
// - Prevent roles that have both organization scoped and non-organization scoped permissions
|
||||
func (q *querier) customRoleCheck(ctx context.Context, role database.CustomRole, action policy.Action) error {
|
||||
act, ok := ActorFromContext(ctx)
|
||||
if !ok {
|
||||
return ErrNoActor
|
||||
}
|
||||
|
||||
// Org permissions require an org role
|
||||
if role.OrganizationID.UUID == uuid.Nil && len(role.OrgPermissions) > 0 {
|
||||
return xerrors.Errorf("organization permissions require specifying an organization id")
|
||||
// Org and org member permissions require an org role.
|
||||
if role.OrganizationID.UUID == uuid.Nil && (len(role.OrgPermissions) > 0 || len(role.MemberPermissions) > 0) {
|
||||
return xerrors.Errorf("organization and member permissions require specifying an organization id")
|
||||
}
|
||||
|
||||
// Org roles can only specify org permissions
|
||||
// Org roles can only specify org permissions; system roles can also specify orgMember ones.
|
||||
if role.OrganizationID.UUID != uuid.Nil && (len(role.SitePermissions) > 0 || len(role.UserPermissions) > 0) {
|
||||
return xerrors.Errorf("organization roles specify site or user permissions")
|
||||
}
|
||||
|
||||
// For now only system roles can specify orgMember permissions.
|
||||
if !role.IsSystem && len(role.MemberPermissions) > 0 {
|
||||
return xerrors.Errorf("non-system roles specify member permissions")
|
||||
}
|
||||
|
||||
// The rbac.Role has a 'Valid()' function on it that will do a lot
|
||||
// of checks.
|
||||
rbacRole, err := rolestore.ConvertDBRole(database.CustomRole{
|
||||
Name: role.Name,
|
||||
DisplayName: role.DisplayName,
|
||||
SitePermissions: role.SitePermissions,
|
||||
OrgPermissions: role.OrgPermissions,
|
||||
UserPermissions: role.UserPermissions,
|
||||
OrganizationID: role.OrganizationID,
|
||||
Name: role.Name,
|
||||
DisplayName: role.DisplayName,
|
||||
SitePermissions: role.SitePermissions,
|
||||
OrgPermissions: role.OrgPermissions,
|
||||
UserPermissions: role.UserPermissions,
|
||||
MemberPermissions: role.MemberPermissions,
|
||||
OrganizationID: role.OrganizationID,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("invalid args: %w", err)
|
||||
@@ -1333,6 +1344,16 @@ func (q *querier) customRoleCheck(ctx context.Context, role database.CustomRole)
|
||||
return xerrors.Errorf("invalid custom role, cannot assign permissions to more than 1 org at a time")
|
||||
}
|
||||
|
||||
// System roles are managed internally and may include permissions
|
||||
// (including negative ones) that user-facing custom role APIs
|
||||
// should reject. Still validate that the role shape and perms
|
||||
// are internally consistent via rbacRole.Valid() above.
|
||||
if role.IsSystem {
|
||||
// Defensive programming: the caller should have checked that
|
||||
// the action is authorized, but we double-check.
|
||||
return q.authorizeContext(ctx, action, rbac.ResourceSystem)
|
||||
}
|
||||
|
||||
// Prevent escalation
|
||||
for _, sitePerm := range rbacRole.Site {
|
||||
err := q.customRoleEscalationCheck(ctx, act, sitePerm, rbac.Object{Type: sitePerm.ResourceType})
|
||||
@@ -4132,21 +4153,33 @@ func (q *querier) InsertCustomRole(ctx context.Context, arg database.InsertCusto
|
||||
if !arg.OrganizationID.Valid || arg.OrganizationID.UUID == uuid.Nil {
|
||||
return database.CustomRole{}, NotAuthorizedError{Err: xerrors.New("custom roles must belong to an organization")}
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceAssignOrgRole.InOrg(arg.OrganizationID.UUID)); err != nil {
|
||||
|
||||
rbacObj := rbac.ResourceAssignOrgRole.InOrg(arg.OrganizationID.UUID)
|
||||
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbacObj); err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
|
||||
if arg.IsSystem {
|
||||
err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceSystem)
|
||||
if err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := q.customRoleCheck(ctx, database.CustomRole{
|
||||
Name: arg.Name,
|
||||
DisplayName: arg.DisplayName,
|
||||
SitePermissions: arg.SitePermissions,
|
||||
OrgPermissions: arg.OrgPermissions,
|
||||
UserPermissions: arg.UserPermissions,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
OrganizationID: arg.OrganizationID,
|
||||
ID: uuid.New(),
|
||||
}); err != nil {
|
||||
Name: arg.Name,
|
||||
DisplayName: arg.DisplayName,
|
||||
SitePermissions: arg.SitePermissions,
|
||||
OrgPermissions: arg.OrgPermissions,
|
||||
UserPermissions: arg.UserPermissions,
|
||||
MemberPermissions: arg.MemberPermissions,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
OrganizationID: arg.OrganizationID,
|
||||
ID: uuid.New(),
|
||||
IsSystem: arg.IsSystem,
|
||||
}, policy.ActionCreate); err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
return q.db.InsertCustomRole(ctx, arg)
|
||||
@@ -4886,21 +4919,48 @@ func (q *querier) UpdateCustomRole(ctx context.Context, arg database.UpdateCusto
|
||||
if !arg.OrganizationID.Valid || arg.OrganizationID.UUID == uuid.Nil {
|
||||
return database.CustomRole{}, NotAuthorizedError{Err: xerrors.New("custom roles must belong to an organization")}
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceAssignOrgRole.InOrg(arg.OrganizationID.UUID)); err != nil {
|
||||
|
||||
rbacObj := rbac.ResourceAssignOrgRole.InOrg(arg.OrganizationID.UUID)
|
||||
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbacObj); err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
|
||||
existing, err := database.ExpectOne(q.db.CustomRoles(ctx, database.CustomRolesParams{
|
||||
LookupRoles: []database.NameOrganizationPair{
|
||||
{
|
||||
Name: arg.Name,
|
||||
OrganizationID: arg.OrganizationID.UUID,
|
||||
},
|
||||
},
|
||||
ExcludeOrgRoles: false,
|
||||
OrganizationID: uuid.Nil,
|
||||
IncludeSystemRoles: true,
|
||||
}))
|
||||
if err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
|
||||
if existing.IsSystem {
|
||||
err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem)
|
||||
if err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := q.customRoleCheck(ctx, database.CustomRole{
|
||||
Name: arg.Name,
|
||||
DisplayName: arg.DisplayName,
|
||||
SitePermissions: arg.SitePermissions,
|
||||
OrgPermissions: arg.OrgPermissions,
|
||||
UserPermissions: arg.UserPermissions,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
OrganizationID: arg.OrganizationID,
|
||||
ID: uuid.New(),
|
||||
}); err != nil {
|
||||
Name: arg.Name,
|
||||
DisplayName: arg.DisplayName,
|
||||
SitePermissions: arg.SitePermissions,
|
||||
OrgPermissions: arg.OrgPermissions,
|
||||
UserPermissions: arg.UserPermissions,
|
||||
MemberPermissions: arg.MemberPermissions,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
OrganizationID: arg.OrganizationID,
|
||||
ID: uuid.New(),
|
||||
IsSystem: existing.IsSystem,
|
||||
}, policy.ActionUpdate); err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
return q.db.UpdateCustomRole(ctx, arg)
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/coderd/rbac/rolestore"
|
||||
)
|
||||
|
||||
// nolint:tparallel
|
||||
@@ -109,8 +110,12 @@ func TestGroupsAuth(t *testing.T) {
|
||||
{
|
||||
Name: "GroupMember",
|
||||
Subject: rbac.Subject{
|
||||
ID: users[0].ID.String(),
|
||||
Roles: rbac.Roles(must(rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(org.ID)}.Expand())),
|
||||
ID: users[0].ID.String(),
|
||||
Roles: must(rolestore.Expand(
|
||||
context.Background(),
|
||||
store,
|
||||
[]rbac.RoleIdentifier{rbac.RoleMember(), rbac.ScopedRoleOrgMember(org.ID)},
|
||||
)),
|
||||
Groups: []string{
|
||||
group.ID.String(),
|
||||
},
|
||||
|
||||
@@ -105,12 +105,51 @@ func (s *MethodTestSuite) TearDownSuite() {
|
||||
|
||||
var testActorID = uuid.New()
|
||||
|
||||
type includeSystemRolesMatcher struct{}
|
||||
|
||||
func (includeSystemRolesMatcher) Matches(x any) bool {
|
||||
p, ok := x.(database.CustomRolesParams)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return p.IncludeSystemRoles
|
||||
}
|
||||
|
||||
func (includeSystemRolesMatcher) String() string {
|
||||
return "CustomRolesParams with IncludeSystemRoles=true"
|
||||
}
|
||||
|
||||
// Mocked runs a subtest with a mocked database. Removing the overhead of a real
|
||||
// postgres database resulting in much faster tests.
|
||||
func (s *MethodTestSuite) Mocked(testCaseF func(dmb *dbmock.MockStore, faker *gofakeit.Faker, check *expects)) func() {
|
||||
t := s.T()
|
||||
mDB := dbmock.NewMockStore(gomock.NewController(t))
|
||||
mDB.EXPECT().Wrappers().Return([]string{}).AnyTimes()
|
||||
// dbauthz now expands DB-backed system roles (e.g. organization-member)
|
||||
// during role-assignment validation, which triggers a CustomRoles lookup
|
||||
// with IncludeSystemRoles=true.
|
||||
mDB.EXPECT().CustomRoles(gomock.Any(), includeSystemRolesMatcher{}).DoAndReturn(func(_ context.Context, arg database.CustomRolesParams) ([]database.CustomRole, error) {
|
||||
if len(arg.LookupRoles) == 0 {
|
||||
return []database.CustomRole{}, nil
|
||||
}
|
||||
|
||||
out := make([]database.CustomRole, 0, len(arg.LookupRoles))
|
||||
|
||||
for _, pair := range arg.LookupRoles {
|
||||
// Minimal set of fields that the tested code uses.
|
||||
out = append(out, database.CustomRole{
|
||||
Name: pair.Name,
|
||||
OrganizationID: uuid.NullUUID{
|
||||
UUID: pair.OrganizationID,
|
||||
Valid: pair.OrganizationID != uuid.Nil,
|
||||
},
|
||||
IsSystem: rbac.SystemRoleName(pair.Name),
|
||||
ID: uuid.New(),
|
||||
})
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}).AnyTimes()
|
||||
|
||||
// Use a constant seed to prevent flakes from random data generation.
|
||||
faker := gofakeit.New(0)
|
||||
|
||||
Reference in New Issue
Block a user