mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00: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:
@@ -168,7 +168,7 @@ func TestFilter(t *testing.T) {
|
||||
Name: "Admin",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleIdentifiers{ScopedRoleOrgMember(orgIDs[0]), RoleAuditor(), RoleOwner(), RoleMember()},
|
||||
Roles: RoleIdentifiers{RoleAuditor(), RoleOwner(), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: policy.ActionRead,
|
||||
@@ -177,7 +177,7 @@ func TestFilter(t *testing.T) {
|
||||
Name: "OrgAdmin",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleIdentifiers{ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgAdmin(orgIDs[0]), RoleMember()},
|
||||
Roles: RoleIdentifiers{ScopedRoleOrgAdmin(orgIDs[0]), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: policy.ActionRead,
|
||||
@@ -186,7 +186,7 @@ func TestFilter(t *testing.T) {
|
||||
Name: "OrgMember",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleIdentifiers{ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgMember(orgIDs[1]), RoleMember()},
|
||||
Roles: RoleIdentifiers{RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: policy.ActionRead,
|
||||
@@ -196,11 +196,9 @@ func TestFilter(t *testing.T) {
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleIdentifiers{
|
||||
ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgAdmin(orgIDs[0]),
|
||||
ScopedRoleOrgMember(orgIDs[1]), ScopedRoleOrgAdmin(orgIDs[1]),
|
||||
ScopedRoleOrgMember(orgIDs[2]), ScopedRoleOrgAdmin(orgIDs[2]),
|
||||
ScopedRoleOrgMember(orgIDs[4]),
|
||||
ScopedRoleOrgMember(orgIDs[5]),
|
||||
ScopedRoleOrgAdmin(orgIDs[0]),
|
||||
ScopedRoleOrgAdmin(orgIDs[1]),
|
||||
ScopedRoleOrgAdmin(orgIDs[2]),
|
||||
RoleMember(),
|
||||
},
|
||||
},
|
||||
@@ -221,10 +219,6 @@ func TestFilter(t *testing.T) {
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleIdentifiers{
|
||||
ScopedRoleOrgMember(orgIDs[0]),
|
||||
ScopedRoleOrgMember(orgIDs[1]),
|
||||
ScopedRoleOrgMember(orgIDs[2]),
|
||||
ScopedRoleOrgMember(orgIDs[3]),
|
||||
RoleMember(),
|
||||
},
|
||||
},
|
||||
@@ -235,7 +229,7 @@ func TestFilter(t *testing.T) {
|
||||
Name: "ScopeApplicationConnect",
|
||||
Actor: Subject{
|
||||
ID: userIDs[0].String(),
|
||||
Roles: RoleIdentifiers{ScopedRoleOrgMember(orgIDs[0]), RoleAuditor(), RoleOwner(), RoleMember()},
|
||||
Roles: RoleIdentifiers{RoleAuditor(), RoleOwner(), RoleMember()},
|
||||
},
|
||||
ObjectType: ResourceWorkspace.Type,
|
||||
Action: policy.ActionRead,
|
||||
@@ -312,7 +306,7 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
Groups: []string{allUsersGroup},
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleMember())),
|
||||
must(RoleByName(ScopedRoleOrgMember(defOrg))),
|
||||
orgMemberRole(defOrg),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -456,7 +450,7 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
Scope: must(ExpandScope(ScopeAll)),
|
||||
Roles: Roles{
|
||||
must(RoleByName(ScopedRoleOrgAdmin(defOrg))),
|
||||
must(RoleByName(ScopedRoleOrgMember(defOrg))),
|
||||
orgMemberRole(defOrg),
|
||||
must(RoleByName(RoleMember())),
|
||||
},
|
||||
}
|
||||
@@ -502,39 +496,40 @@ func TestAuthorizeDomain(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
siteAdminWorkspaceActions := slice.Omit(ResourceWorkspace.AvailableActions(), policy.ActionShare)
|
||||
testAuthorize(t, "SiteAdmin", user, []authTestCase{
|
||||
// Similar to an orphaned user, but has site level perms
|
||||
{resource: ResourceTemplate.AnyOrganization(), actions: []policy.Action{policy.ActionCreate}, allow: true},
|
||||
|
||||
// Org + me
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: ResourceWorkspace.AvailableActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg), actions: ResourceWorkspace.AvailableActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: siteAdminWorkspaceActions, allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg), actions: siteAdminWorkspaceActions, allow: true},
|
||||
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID), actions: ResourceWorkspace.AvailableActions(), allow: true},
|
||||
{resource: ResourceWorkspace.WithOwner(user.ID), actions: siteAdminWorkspaceActions, allow: true},
|
||||
|
||||
{resource: ResourceWorkspace.All(), actions: ResourceWorkspace.AvailableActions(), allow: true},
|
||||
{resource: ResourceWorkspace.All(), actions: siteAdminWorkspaceActions, allow: true},
|
||||
|
||||
// Other org + me
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID), actions: ResourceWorkspace.AvailableActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID), actions: ResourceWorkspace.AvailableActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner(user.ID), actions: siteAdminWorkspaceActions, allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID), actions: siteAdminWorkspaceActions, allow: true},
|
||||
|
||||
// Other org + other user
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me"), actions: ResourceWorkspace.AvailableActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("not-me"), actions: siteAdminWorkspaceActions, allow: true},
|
||||
|
||||
{resource: ResourceWorkspace.WithOwner("not-me"), actions: ResourceWorkspace.AvailableActions(), allow: true},
|
||||
{resource: ResourceWorkspace.WithOwner("not-me"), actions: siteAdminWorkspaceActions, allow: true},
|
||||
|
||||
// Other org + other use
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me"), actions: ResourceWorkspace.AvailableActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID), actions: ResourceWorkspace.AvailableActions(), allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me"), actions: siteAdminWorkspaceActions, allow: true},
|
||||
{resource: ResourceWorkspace.InOrg(unusedID), actions: siteAdminWorkspaceActions, allow: true},
|
||||
|
||||
{resource: ResourceWorkspace.WithOwner("not-me"), actions: ResourceWorkspace.AvailableActions(), allow: true},
|
||||
{resource: ResourceWorkspace.WithOwner("not-me"), actions: siteAdminWorkspaceActions, allow: true},
|
||||
})
|
||||
|
||||
user = Subject{
|
||||
ID: "me",
|
||||
Scope: must(ExpandScope(ScopeApplicationConnect)),
|
||||
Roles: Roles{
|
||||
must(RoleByName(ScopedRoleOrgMember(defOrg))),
|
||||
orgMemberRole(defOrg),
|
||||
must(RoleByName(RoleMember())),
|
||||
},
|
||||
}
|
||||
@@ -762,7 +757,7 @@ func TestAuthorizeLevels(t *testing.T) {
|
||||
|
||||
testAuthorize(t, "AdminAlwaysAllow", user,
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = ResourceWorkspace.AvailableActions()
|
||||
c.actions = slice.Omit(ResourceWorkspace.AvailableActions(), policy.ActionShare)
|
||||
c.allow = true
|
||||
return c
|
||||
}, []authTestCase{
|
||||
@@ -890,7 +885,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
ID: "me",
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleMember())),
|
||||
must(RoleByName(ScopedRoleOrgMember(defOrg))),
|
||||
orgMemberRole(defOrg),
|
||||
},
|
||||
Scope: must(ExpandScope(ScopeApplicationConnect)),
|
||||
}
|
||||
@@ -926,7 +921,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
ID: "me",
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleMember())),
|
||||
must(RoleByName(ScopedRoleOrgMember(defOrg))),
|
||||
orgMemberRole(defOrg),
|
||||
},
|
||||
Scope: Scope{
|
||||
Role: Role{
|
||||
@@ -1015,7 +1010,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
ID: "me",
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleMember())),
|
||||
must(RoleByName(ScopedRoleOrgMember(defOrg))),
|
||||
orgMemberRole(defOrg),
|
||||
},
|
||||
Scope: Scope{
|
||||
Role: Role{
|
||||
@@ -1070,7 +1065,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
ID: meID.String(),
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleMember())),
|
||||
must(RoleByName(ScopedRoleOrgMember(defOrg))),
|
||||
orgMemberRole(defOrg),
|
||||
},
|
||||
Scope: must(ScopeNoUserData.Expand()),
|
||||
}
|
||||
@@ -1138,7 +1133,7 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
// This is odd behavior, as without this membership role, the test for
|
||||
// the workspace fails. Maybe scopes should just assume the user
|
||||
// is a member.
|
||||
must(RoleByName(ScopedRoleOrgMember(defOrg))),
|
||||
orgMemberRole(defOrg),
|
||||
},
|
||||
Scope: Scope{
|
||||
Role: Role{
|
||||
@@ -1404,6 +1399,28 @@ func testAuthorize(t *testing.T, name string, subject Subject, sets ...[]authTes
|
||||
}
|
||||
}
|
||||
|
||||
// orgMemberRole returns an organization-member role for RBAC-only tests.
|
||||
//
|
||||
// organization-member is now a DB-backed system role (not a built-in role), so
|
||||
// RoleByName won't resolve it here. Assume the default behavior: workspace
|
||||
// sharing enabled.
|
||||
func orgMemberRole(orgID uuid.UUID) Role {
|
||||
workspaceSharingDisabled := false
|
||||
orgPerms, memberPerms := OrgMemberPermissions(workspaceSharingDisabled)
|
||||
return Role{
|
||||
Identifier: ScopedRoleOrgMember(orgID),
|
||||
DisplayName: "",
|
||||
Site: []Permission{},
|
||||
User: []Permission{},
|
||||
ByOrgID: map[string]OrgPermissions{
|
||||
orgID.String(): {
|
||||
Org: orgPerms,
|
||||
Member: memberPerms,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func must[T any](value T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
+126
-37
@@ -229,15 +229,30 @@ func allPermsExcept(excepts ...Objecter) []Permission {
|
||||
// https://github.com/coder/coder/issues/1194
|
||||
var builtInRoles map[string]func(orgID uuid.UUID) Role
|
||||
|
||||
// systemRoles are roles that have migrated from builtInRoles to
|
||||
// database storage. This migration is partial - permissions are still
|
||||
// generated at runtime and reconciled to the database, rather than
|
||||
// the database being the source of truth.
|
||||
var systemRoles = map[string]struct{}{
|
||||
RoleOrgMember(): {},
|
||||
}
|
||||
|
||||
func SystemRoleName(name string) bool {
|
||||
_, ok := systemRoles[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
type RoleOptions struct {
|
||||
NoOwnerWorkspaceExec bool
|
||||
}
|
||||
|
||||
// ReservedRoleName exists because the database should only allow unique role
|
||||
// names, but some roles are built in. So these names are reserved
|
||||
// names, but some roles are built in or generated at runtime. So these names
|
||||
// are reserved
|
||||
func ReservedRoleName(name string) bool {
|
||||
_, ok := builtInRoles[name]
|
||||
return ok
|
||||
_, isBuiltIn := builtInRoles[name]
|
||||
_, isSystem := systemRoles[name]
|
||||
return isBuiltIn || isSystem
|
||||
}
|
||||
|
||||
// ReloadBuiltinRoles loads the static roles into the builtInRoles map.
|
||||
@@ -252,7 +267,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
opts = &RoleOptions{}
|
||||
}
|
||||
|
||||
ownerWorkspaceActions := ResourceWorkspace.AvailableActions()
|
||||
ownerWorkspaceActions := slice.Omit(ResourceWorkspace.AvailableActions(), policy.ActionShare)
|
||||
if opts.NoOwnerWorkspaceExec {
|
||||
// Remove ssh and application connect from the owner role. This
|
||||
// prevents owners from have exec access to all workspaces.
|
||||
@@ -431,39 +446,6 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
// orgMember is an implied role to any member in an organization.
|
||||
orgMember: func(organizationID uuid.UUID) Role {
|
||||
return Role{
|
||||
Identifier: RoleIdentifier{Name: orgMember, OrganizationID: organizationID},
|
||||
DisplayName: "",
|
||||
Site: []Permission{},
|
||||
User: []Permission{},
|
||||
ByOrgID: map[string]OrgPermissions{
|
||||
organizationID.String(): {
|
||||
Org: Permissions(map[string][]policy.Action{
|
||||
// All users can see the provisioner daemons for workspace
|
||||
// creation.
|
||||
ResourceProvisionerDaemon.Type: {policy.ActionRead},
|
||||
// All org members can read the organization
|
||||
ResourceOrganization.Type: {policy.ActionRead},
|
||||
// Can read available roles.
|
||||
ResourceAssignOrgRole.Type: {policy.ActionRead},
|
||||
}),
|
||||
Member: append(allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceUser, ResourceOrganizationMember),
|
||||
Permissions(map[string][]policy.Action{
|
||||
// Reduced permission set on dormant workspaces. No build, ssh, or exec
|
||||
ResourceWorkspaceDormant.Type: {policy.ActionRead, policy.ActionDelete, policy.ActionCreate, policy.ActionUpdate, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent},
|
||||
// Can read their own organization member record
|
||||
ResourceOrganizationMember.Type: {policy.ActionRead},
|
||||
// Users can create provisioner daemons scoped to themselves.
|
||||
ResourceProvisionerDaemon.Type: {policy.ActionRead, policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
||||
})...,
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
orgAuditor: func(organizationID uuid.UUID) Role {
|
||||
return Role{
|
||||
Identifier: RoleIdentifier{Name: orgAuditor, OrganizationID: organizationID},
|
||||
@@ -915,3 +897,110 @@ func DeduplicatePermissions(perms []Permission) []Permission {
|
||||
}
|
||||
return deduped
|
||||
}
|
||||
|
||||
// PermissionsEqual compares two permission slices as sets. Order and
|
||||
// duplicate entries do not matter; it only checks that both slices
|
||||
// contain the same unique permissions.
|
||||
func PermissionsEqual(a, b []Permission) bool {
|
||||
setA := make(map[Permission]struct{}, len(a))
|
||||
for _, p := range a {
|
||||
setA[p] = struct{}{}
|
||||
}
|
||||
|
||||
setB := make(map[Permission]struct{}, len(b))
|
||||
for _, p := range b {
|
||||
if _, ok := setA[p]; !ok {
|
||||
return false
|
||||
}
|
||||
setB[p] = struct{}{}
|
||||
}
|
||||
|
||||
return len(setA) == len(setB)
|
||||
}
|
||||
|
||||
// OrgMemberPermissions returns the permissions for the organization-member
|
||||
// system role. The results are then stored in the database and can vary per
|
||||
// organization based on the workspace_sharing_disabled setting.
|
||||
// This is the source of truth for org-member permissions, used by:
|
||||
// - the startup reconciliation routine, to keep permissions current with
|
||||
// RBAC resources
|
||||
// - the organization workspace sharing setting endpoint, when updating
|
||||
// the setting
|
||||
// - the org creation endpoint, when populating the organization-member
|
||||
// system role created by the DB trigger
|
||||
//
|
||||
//nolint:revive // workspaceSharingDisabled is an org setting
|
||||
func OrgMemberPermissions(workspaceSharingDisabled bool) (
|
||||
orgPerms, memberPerms []Permission,
|
||||
) {
|
||||
// Organization-level permissions that all org members get.
|
||||
orgPermMap := map[string][]policy.Action{
|
||||
// All users can see provisioner daemons for workspace creation.
|
||||
ResourceProvisionerDaemon.Type: {policy.ActionRead},
|
||||
// All org members can read the organization.
|
||||
ResourceOrganization.Type: {policy.ActionRead},
|
||||
// Can read available roles.
|
||||
ResourceAssignOrgRole.Type: {policy.ActionRead},
|
||||
}
|
||||
|
||||
// When workspace sharing is enabled, members need to see other org members
|
||||
// and groups to share workspaces with them.
|
||||
if !workspaceSharingDisabled {
|
||||
orgPermMap[ResourceOrganizationMember.Type] = []policy.Action{policy.ActionRead}
|
||||
orgPermMap[ResourceGroup.Type] = []policy.Action{policy.ActionRead}
|
||||
}
|
||||
|
||||
orgPerms = Permissions(orgPermMap)
|
||||
|
||||
// Member-scoped permissions (resources owned by the member).
|
||||
// Uses allPermsExcept to automatically include permissions for new resources.
|
||||
memberPerms = append(
|
||||
allPermsExcept(
|
||||
ResourceWorkspaceDormant,
|
||||
ResourcePrebuiltWorkspace,
|
||||
ResourceUser,
|
||||
ResourceOrganizationMember,
|
||||
),
|
||||
Permissions(map[string][]policy.Action{
|
||||
// Reduced permission set on dormant workspaces. No build,
|
||||
// ssh, or exec.
|
||||
ResourceWorkspaceDormant.Type: {
|
||||
policy.ActionRead,
|
||||
policy.ActionDelete,
|
||||
policy.ActionCreate,
|
||||
policy.ActionUpdate,
|
||||
policy.ActionWorkspaceStop,
|
||||
policy.ActionCreateAgent,
|
||||
policy.ActionDeleteAgent,
|
||||
},
|
||||
// Can read their own organization member record.
|
||||
ResourceOrganizationMember.Type: {
|
||||
policy.ActionRead,
|
||||
},
|
||||
// Users can create provisioner daemons scoped to themselves.
|
||||
//
|
||||
// TODO(geokat): copied from the original built-in role
|
||||
// verbatim, but seems to be a no-op (not excepted above;
|
||||
// plus no owner is set for the ProvisionerDaemon RBAC
|
||||
// object).
|
||||
ResourceProvisionerDaemon.Type: {
|
||||
policy.ActionRead,
|
||||
policy.ActionCreate,
|
||||
policy.ActionUpdate,
|
||||
},
|
||||
})...,
|
||||
)
|
||||
|
||||
if workspaceSharingDisabled {
|
||||
// Org-level negation blocks sharing on ANY workspace in the
|
||||
// org. This overrides any positive permission from other
|
||||
// roles, including org-admin.
|
||||
orgPerms = append(orgPerms, Permission{
|
||||
Negate: true,
|
||||
ResourceType: ResourceWorkspace.Type,
|
||||
Action: policy.ActionShare,
|
||||
})
|
||||
}
|
||||
|
||||
return orgPerms, memberPerms
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -74,7 +75,7 @@ func TestRegoInputValue(t *testing.T) {
|
||||
// Expand all roles and make sure we have a good copy.
|
||||
// This is because these tests modify the roles, and we don't want to
|
||||
// modify the original roles.
|
||||
roles, err := RoleIdentifiers{ScopedRoleOrgMember(uuid.New()), ScopedRoleOrgAdmin(uuid.New()), RoleMember()}.Expand()
|
||||
roles, err := RoleIdentifiers{ScopedRoleOrgAuditor(uuid.New()), ScopedRoleOrgAdmin(uuid.New()), RoleMember()}.Expand()
|
||||
require.NoError(t, err, "failed to expand roles")
|
||||
for i := range roles {
|
||||
// If all cached values are nil, then the role will not use
|
||||
@@ -224,9 +225,9 @@ func TestRoleByName(t *testing.T) {
|
||||
{Role: builtInRoles[orgAdmin](uuid.New())},
|
||||
{Role: builtInRoles[orgAdmin](uuid.New())},
|
||||
|
||||
{Role: builtInRoles[orgMember](uuid.New())},
|
||||
{Role: builtInRoles[orgMember](uuid.New())},
|
||||
{Role: builtInRoles[orgMember](uuid.New())},
|
||||
{Role: builtInRoles[orgAuditor](uuid.New())},
|
||||
{Role: builtInRoles[orgAuditor](uuid.New())},
|
||||
{Role: builtInRoles[orgAuditor](uuid.New())},
|
||||
}
|
||||
|
||||
for _, c := range testCases {
|
||||
@@ -271,6 +272,62 @@ func TestDeduplicatePermissions(t *testing.T) {
|
||||
require.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func TestPermissionsEqual(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a := []Permission{
|
||||
{ResourceType: ResourceWorkspace.Type, Action: policy.ActionRead},
|
||||
{ResourceType: ResourceTemplate.Type, Action: policy.ActionUpdate},
|
||||
{ResourceType: ResourceWorkspace.Type, Action: policy.ActionShare, Negate: true},
|
||||
}
|
||||
|
||||
t.Run("Order", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
b := []Permission{
|
||||
a[2],
|
||||
a[0],
|
||||
a[1],
|
||||
}
|
||||
require.True(t, PermissionsEqual(a, b))
|
||||
})
|
||||
|
||||
t.Run("SubsetAndSuperset", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.False(t, PermissionsEqual(a, a[:2]))
|
||||
|
||||
b := append(slices.Clone(a), Permission{ResourceType: ResourceWorkspace.Type, Action: policy.ActionUpdate})
|
||||
require.False(t, PermissionsEqual(a, b))
|
||||
})
|
||||
|
||||
t.Run("Negate", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
b := slices.Clone(a)
|
||||
b[0] = Permission{
|
||||
ResourceType: ResourceWorkspace.Type, Action: policy.ActionRead, Negate: true,
|
||||
}
|
||||
require.False(t, PermissionsEqual(a, b))
|
||||
})
|
||||
|
||||
t.Run("Duplicates", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
b := append(slices.Clone(a), a[0])
|
||||
require.True(t, PermissionsEqual(a, b), "equal sets with duplicates should compare equal even without pre-deduplication")
|
||||
})
|
||||
|
||||
t.Run("NilEmpty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var nilSlice []Permission
|
||||
emptySlice := []Permission{}
|
||||
require.True(t, PermissionsEqual(nilSlice, emptySlice))
|
||||
require.True(t, PermissionsEqual(emptySlice, nilSlice))
|
||||
})
|
||||
}
|
||||
|
||||
// equalRoles compares 2 roles for equality.
|
||||
func equalRoles(t *testing.T, a, b Role) {
|
||||
require.Equal(t, a.Identifier, b.Identifier, "role names")
|
||||
|
||||
+168
-110
@@ -3,6 +3,7 @@ package rbac_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -50,6 +51,56 @@ func TestBuiltInRoles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemRolesAreReservedRoleNames(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.True(t, rbac.ReservedRoleName(rbac.RoleOrgMember()))
|
||||
}
|
||||
|
||||
func TestOrgMemberPermissions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("WorkspaceSharingEnabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
orgPerms, _ := rbac.OrgMemberPermissions(false)
|
||||
|
||||
require.True(t, slices.Contains(orgPerms, rbac.Permission{
|
||||
ResourceType: rbac.ResourceOrganizationMember.Type,
|
||||
Action: policy.ActionRead,
|
||||
}))
|
||||
require.True(t, slices.Contains(orgPerms, rbac.Permission{
|
||||
ResourceType: rbac.ResourceGroup.Type,
|
||||
Action: policy.ActionRead,
|
||||
}))
|
||||
require.False(t, slices.Contains(orgPerms, rbac.Permission{
|
||||
Negate: true,
|
||||
ResourceType: rbac.ResourceWorkspace.Type,
|
||||
Action: policy.ActionShare,
|
||||
}))
|
||||
})
|
||||
|
||||
t.Run("WorkspaceSharingDisabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
orgPerms, _ := rbac.OrgMemberPermissions(true)
|
||||
|
||||
require.False(t, slices.Contains(orgPerms, rbac.Permission{
|
||||
ResourceType: rbac.ResourceOrganizationMember.Type,
|
||||
Action: policy.ActionRead,
|
||||
}))
|
||||
require.False(t, slices.Contains(orgPerms, rbac.Permission{
|
||||
ResourceType: rbac.ResourceGroup.Type,
|
||||
Action: policy.ActionRead,
|
||||
}))
|
||||
require.True(t, slices.Contains(orgPerms, rbac.Permission{
|
||||
Negate: true,
|
||||
ResourceType: rbac.ResourceWorkspace.Type,
|
||||
Action: policy.ActionShare,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:tparallel,paralleltest
|
||||
func TestOwnerExec(t *testing.T) {
|
||||
owner := rbac.Subject{
|
||||
@@ -86,6 +137,19 @@ func TestOwnerExec(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// These were "pared down" in https://github.com/coder/coder/pull/21359 to avoid
|
||||
// using the now DB-backed organization-member role. As a result, they no longer
|
||||
// model real-world org-scoped users (who also have organization-member).
|
||||
//
|
||||
// For example, `org_auditor` is now expected to be forbidden for
|
||||
// `assign_org_role:read`, even though in production an org auditor can read
|
||||
// available org roles via the org-member baseline.
|
||||
//
|
||||
// The tests are still useful for unit-testing the built-in roles in isolation.
|
||||
//
|
||||
// TODO(geokat): Add an integration test that includes organization-member to
|
||||
// recover the old test coverage.
|
||||
//
|
||||
// nolint:tparallel,paralleltest // subtests share a map, just run sequentially.
|
||||
func TestRolePermissions(t *testing.T) {
|
||||
t.Parallel()
|
||||
@@ -110,34 +174,30 @@ func TestRolePermissions(t *testing.T) {
|
||||
|
||||
// Subjects to user
|
||||
memberMe := authSubject{Name: "member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember()}}}
|
||||
orgMemberMe := authSubject{Name: "org_member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID)}}}
|
||||
orgMemberMeBanWorkspace := authSubject{Name: "org_member_me_workspace_ban", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID), rbac.ScopedRoleOrgWorkspaceCreationBan(orgID)}}}
|
||||
groupMemberMe := authSubject{Name: "group_member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID)}, Groups: []string{groupID.String()}}}
|
||||
|
||||
owner := authSubject{Name: "owner", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleOwner()}}}
|
||||
templateAdmin := authSubject{Name: "template-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleTemplateAdmin()}}}
|
||||
userAdmin := authSubject{Name: "user-admin", Actor: rbac.Subject{ID: userAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleUserAdmin()}}}
|
||||
auditor := authSubject{Name: "auditor", Actor: rbac.Subject{ID: auditorID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleAuditor()}}}
|
||||
|
||||
orgAdmin := authSubject{Name: "org_admin", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID), rbac.ScopedRoleOrgAdmin(orgID)}}}
|
||||
orgAuditor := authSubject{Name: "org_auditor", Actor: rbac.Subject{ID: auditorID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID), rbac.ScopedRoleOrgAuditor(orgID)}}}
|
||||
orgUserAdmin := authSubject{Name: "org_user_admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID), rbac.ScopedRoleOrgUserAdmin(orgID)}}}
|
||||
orgTemplateAdmin := authSubject{Name: "org_template_admin", Actor: rbac.Subject{ID: userAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID), rbac.ScopedRoleOrgTemplateAdmin(orgID)}}}
|
||||
orgAdmin := authSubject{Name: "org_admin", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgAdmin(orgID)}}}
|
||||
orgAuditor := authSubject{Name: "org_auditor", Actor: rbac.Subject{ID: auditorID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgAuditor(orgID)}}}
|
||||
orgUserAdmin := authSubject{Name: "org_user_admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgUserAdmin(orgID)}}}
|
||||
orgTemplateAdmin := authSubject{Name: "org_template_admin", Actor: rbac.Subject{ID: userAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgTemplateAdmin(orgID)}}}
|
||||
orgAdminBanWorkspace := authSubject{Name: "org_admin_workspace_ban", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgAdmin(orgID), rbac.ScopedRoleOrgWorkspaceCreationBan(orgID)}}}
|
||||
setOrgNotMe := authSubjectSet{orgAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin}
|
||||
|
||||
otherOrgMember := authSubject{Name: "org_member_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg)}}}
|
||||
otherOrgAdmin := authSubject{Name: "org_admin_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg), rbac.ScopedRoleOrgAdmin(otherOrg)}}}
|
||||
otherOrgAuditor := authSubject{Name: "org_auditor_other", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg), rbac.ScopedRoleOrgAuditor(otherOrg)}}}
|
||||
otherOrgUserAdmin := authSubject{Name: "org_user_admin_other", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg), rbac.ScopedRoleOrgUserAdmin(otherOrg)}}}
|
||||
otherOrgTemplateAdmin := authSubject{Name: "org_template_admin_other", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg), rbac.ScopedRoleOrgTemplateAdmin(otherOrg)}}}
|
||||
setOtherOrg := authSubjectSet{otherOrgMember, otherOrgAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin}
|
||||
otherOrgAdmin := authSubject{Name: "org_admin_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgAdmin(otherOrg)}}}
|
||||
otherOrgAuditor := authSubject{Name: "org_auditor_other", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgAuditor(otherOrg)}}}
|
||||
otherOrgUserAdmin := authSubject{Name: "org_user_admin_other", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgUserAdmin(otherOrg)}}}
|
||||
otherOrgTemplateAdmin := authSubject{Name: "org_template_admin_other", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgTemplateAdmin(otherOrg)}}}
|
||||
setOtherOrg := authSubjectSet{otherOrgAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin}
|
||||
|
||||
// requiredSubjects are required to be asserted in each test case. This is
|
||||
// to make sure one is not forgotten.
|
||||
requiredSubjects := []authSubject{
|
||||
memberMe, owner,
|
||||
orgMemberMe, orgAdmin,
|
||||
otherOrgAdmin, otherOrgMember, orgAuditor, orgUserAdmin, orgTemplateAdmin,
|
||||
orgAdmin, otherOrgAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin,
|
||||
templateAdmin, userAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
}
|
||||
|
||||
@@ -159,10 +219,10 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceUserObject(currentUser),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {orgMemberMe, owner, memberMe, templateAdmin, userAdmin, orgUserAdmin, otherOrgAdmin, otherOrgUserAdmin, orgAdmin},
|
||||
true: {owner, memberMe, templateAdmin, userAdmin, orgUserAdmin, otherOrgAdmin, otherOrgUserAdmin, orgAdmin},
|
||||
false: {
|
||||
orgTemplateAdmin, orgAuditor,
|
||||
otherOrgMember, otherOrgAuditor, otherOrgTemplateAdmin,
|
||||
otherOrgAuditor, otherOrgTemplateAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -172,7 +232,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceUser,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -181,7 +241,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgMemberMe, orgAdmin, templateAdmin, orgTemplateAdmin, orgMemberMeBanWorkspace},
|
||||
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin, orgAdminBanWorkspace},
|
||||
false: {setOtherOrg, memberMe, userAdmin, orgAuditor, orgUserAdmin},
|
||||
},
|
||||
},
|
||||
@@ -191,7 +251,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionUpdate},
|
||||
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgMemberMe, orgAdmin},
|
||||
true: {owner, orgAdmin, orgAdminBanWorkspace},
|
||||
false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
||||
},
|
||||
},
|
||||
@@ -201,8 +261,8 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgMemberMe, orgAdmin},
|
||||
false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgMemberMeBanWorkspace},
|
||||
true: {owner, orgAdmin},
|
||||
false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgAdminBanWorkspace},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -211,7 +271,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionSSH},
|
||||
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgMemberMe},
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
@@ -221,7 +281,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionApplicationConnect},
|
||||
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgMemberMe},
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
@@ -230,8 +290,8 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionCreateAgent, policy.ActionDeleteAgent},
|
||||
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgMemberMe, orgAdmin},
|
||||
false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgMemberMeBanWorkspace},
|
||||
true: {owner, orgAdmin},
|
||||
false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgAdminBanWorkspace},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -242,9 +302,9 @@ func TestRolePermissions(t *testing.T) {
|
||||
InOrg(orgID).
|
||||
WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgMemberMe, orgAdmin, orgMemberMeBanWorkspace},
|
||||
true: {orgAdmin, orgAdminBanWorkspace},
|
||||
false: {
|
||||
memberMe, setOtherOrg,
|
||||
owner, memberMe, setOtherOrg,
|
||||
templateAdmin, userAdmin,
|
||||
orgTemplateAdmin, orgUserAdmin, orgAuditor,
|
||||
},
|
||||
@@ -260,10 +320,10 @@ func TestRolePermissions(t *testing.T) {
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {},
|
||||
false: {
|
||||
orgMemberMe, orgAdmin, owner, setOtherOrg,
|
||||
orgAdmin, owner, setOtherOrg,
|
||||
userAdmin, memberMe,
|
||||
templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor,
|
||||
orgMemberMeBanWorkspace,
|
||||
orgAdminBanWorkspace,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -273,7 +333,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceTemplate.WithID(templateID).InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin},
|
||||
false: {setOtherOrg, orgUserAdmin, orgAuditor, memberMe, orgMemberMe, userAdmin},
|
||||
false: {setOtherOrg, orgUserAdmin, orgAuditor, memberMe, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -282,7 +342,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceTemplate.InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAuditor, orgAdmin, templateAdmin, orgTemplateAdmin},
|
||||
false: {setOtherOrg, orgUserAdmin, memberMe, userAdmin, orgMemberMe},
|
||||
false: {setOtherOrg, orgUserAdmin, memberMe, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -292,8 +352,8 @@ func TestRolePermissions(t *testing.T) {
|
||||
groupID.String(): {policy.ActionUse},
|
||||
}),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin, groupMemberMe},
|
||||
false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, userAdmin, orgMemberMe},
|
||||
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin},
|
||||
false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -304,7 +364,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
true: {owner, templateAdmin},
|
||||
// Org template admins can only read org scoped files.
|
||||
// File scope is currently not org scoped :cry:
|
||||
false: {setOtherOrg, orgTemplateAdmin, orgMemberMe, orgAdmin, memberMe, userAdmin, orgAuditor, orgUserAdmin},
|
||||
false: {setOtherOrg, orgTemplateAdmin, orgAdmin, memberMe, userAdmin, orgAuditor, orgUserAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -312,7 +372,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead},
|
||||
Resource: rbac.ResourceFile.WithID(fileID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, memberMe, orgMemberMe, templateAdmin},
|
||||
true: {owner, memberMe, templateAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, userAdmin},
|
||||
},
|
||||
},
|
||||
@@ -322,7 +382,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceOrganization,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -331,7 +391,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceOrganization.WithID(orgID).InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin},
|
||||
false: {setOtherOrg, orgTemplateAdmin, orgUserAdmin, orgAuditor, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, orgTemplateAdmin, orgUserAdmin, orgAuditor, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -339,7 +399,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceOrganization.WithID(orgID).InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, orgMemberMe, templateAdmin, orgTemplateAdmin, auditor, orgAuditor, userAdmin, orgUserAdmin},
|
||||
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin, auditor, orgAuditor, userAdmin, orgUserAdmin},
|
||||
false: {setOtherOrg, memberMe},
|
||||
},
|
||||
},
|
||||
@@ -349,7 +409,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceAssignOrgRole,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, userAdmin, orgMemberMe, memberMe, templateAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, userAdmin, memberMe, templateAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -358,7 +418,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceAssignRole,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, orgMemberMe, memberMe, templateAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -366,7 +426,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceAssignRole,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {setOtherOrg, setOrgNotMe, owner, orgMemberMe, memberMe, templateAdmin, userAdmin},
|
||||
true: {setOtherOrg, setOrgNotMe, owner, memberMe, templateAdmin, userAdmin},
|
||||
false: {},
|
||||
},
|
||||
},
|
||||
@@ -376,7 +436,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceAssignOrgRole.InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, userAdmin, orgUserAdmin},
|
||||
false: {setOtherOrg, orgMemberMe, memberMe, templateAdmin, orgTemplateAdmin, orgAuditor},
|
||||
false: {setOtherOrg, memberMe, templateAdmin, orgTemplateAdmin, orgAuditor},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -385,7 +445,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceAssignOrgRole.InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin},
|
||||
false: {setOtherOrg, orgUserAdmin, orgTemplateAdmin, orgAuditor, orgMemberMe, memberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, orgUserAdmin, orgTemplateAdmin, orgAuditor, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -393,8 +453,8 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceAssignOrgRole.InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, setOrgNotMe, orgMemberMe, userAdmin, templateAdmin},
|
||||
false: {setOtherOrg, memberMe},
|
||||
true: {owner, orgAdmin, orgUserAdmin, userAdmin, templateAdmin},
|
||||
false: {setOtherOrg, memberMe, orgAuditor, orgTemplateAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -402,7 +462,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete, policy.ActionUpdate},
|
||||
Resource: rbac.ResourceApiKey.WithID(apiKeyID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgMemberMe, memberMe},
|
||||
true: {owner, memberMe},
|
||||
false: {setOtherOrg, setOrgNotMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
@@ -413,7 +473,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
Resource: rbac.ResourceInboxNotification.WithID(uuid.New()).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgMemberMe, orgAdmin},
|
||||
true: {owner, orgAdmin},
|
||||
false: {setOtherOrg, orgUserAdmin, orgTemplateAdmin, orgAuditor, templateAdmin, userAdmin, memberMe},
|
||||
},
|
||||
},
|
||||
@@ -422,7 +482,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionReadPersonal, policy.ActionUpdatePersonal},
|
||||
Resource: rbac.ResourceUserObject(currentUser),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgMemberMe, memberMe, userAdmin},
|
||||
true: {owner, memberMe, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, templateAdmin},
|
||||
},
|
||||
},
|
||||
@@ -432,7 +492,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceOrganizationMember.WithID(currentUser).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, userAdmin, orgUserAdmin},
|
||||
false: {setOtherOrg, orgTemplateAdmin, orgAuditor, orgMemberMe, memberMe, templateAdmin},
|
||||
false: {setOtherOrg, orgTemplateAdmin, orgAuditor, memberMe, templateAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -440,7 +500,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceOrganizationMember.WithID(currentUser).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAuditor, orgAdmin, userAdmin, orgMemberMe, templateAdmin, orgUserAdmin, orgTemplateAdmin},
|
||||
true: {owner, orgAuditor, orgAdmin, userAdmin, templateAdmin, orgUserAdmin, orgTemplateAdmin},
|
||||
false: {memberMe, setOtherOrg},
|
||||
},
|
||||
},
|
||||
@@ -453,7 +513,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
}),
|
||||
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, orgMemberMe, templateAdmin, orgUserAdmin, orgTemplateAdmin, orgAuditor},
|
||||
true: {owner, orgAdmin, templateAdmin, orgUserAdmin, orgTemplateAdmin, orgAuditor},
|
||||
false: {setOtherOrg, memberMe, userAdmin},
|
||||
},
|
||||
},
|
||||
@@ -467,7 +527,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
}),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, userAdmin, orgUserAdmin},
|
||||
false: {setOtherOrg, memberMe, orgMemberMe, templateAdmin, orgTemplateAdmin, groupMemberMe, orgAuditor},
|
||||
false: {setOtherOrg, memberMe, templateAdmin, orgTemplateAdmin, orgAuditor},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -479,8 +539,8 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
}),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, groupMemberMe, orgAuditor},
|
||||
false: {setOtherOrg, memberMe, orgMemberMe},
|
||||
true: {owner, orgAdmin, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
||||
false: {setOtherOrg, memberMe},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -488,7 +548,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceGroupMember.WithID(currentUser).InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAuditor, orgAdmin, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgMemberMe, groupMemberMe},
|
||||
true: {owner, orgAuditor, orgAdmin, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin},
|
||||
false: {setOtherOrg, memberMe},
|
||||
},
|
||||
},
|
||||
@@ -498,7 +558,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceGroupMember.WithID(adminID).InOrg(orgID).WithOwner(adminID.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAuditor, orgAdmin, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin},
|
||||
false: {setOtherOrg, memberMe, orgMemberMe, groupMemberMe},
|
||||
false: {setOtherOrg, memberMe},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -506,7 +566,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: append(crud, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent),
|
||||
Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {orgMemberMe, orgAdmin, owner},
|
||||
true: {orgAdmin, owner},
|
||||
false: {setOtherOrg, userAdmin, memberMe, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
||||
},
|
||||
},
|
||||
@@ -516,7 +576,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, userAdmin, orgMemberMe, owner, templateAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, userAdmin, owner, templateAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -524,7 +584,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionWorkspaceStart, policy.ActionWorkspaceStop},
|
||||
Resource: rbac.ResourceWorkspace.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, orgMemberMe},
|
||||
true: {owner, orgAdmin},
|
||||
false: {setOtherOrg, userAdmin, templateAdmin, memberMe, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
||||
},
|
||||
},
|
||||
@@ -534,7 +594,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourcePrebuiltWorkspace.WithID(uuid.New()).InOrg(orgID).WithOwner(database.PrebuildsSystemUserID.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin},
|
||||
false: {setOtherOrg, userAdmin, memberMe, orgUserAdmin, orgAuditor, orgMemberMe},
|
||||
false: {setOtherOrg, userAdmin, memberMe, orgUserAdmin, orgAuditor},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -542,7 +602,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: crud,
|
||||
Resource: rbac.ResourceTask.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, orgMemberMe},
|
||||
true: {owner, orgAdmin},
|
||||
false: {setOtherOrg, userAdmin, templateAdmin, memberMe, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
||||
},
|
||||
},
|
||||
@@ -553,7 +613,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceLicense,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -562,7 +622,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceDeploymentStats,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -571,7 +631,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceDeploymentConfig,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -580,7 +640,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceDebugInfo,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -589,7 +649,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceReplicas,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -598,7 +658,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceTailnetCoordinator,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -607,7 +667,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceAuditLog,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -616,7 +676,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceProvisionerDaemon.InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, templateAdmin, orgAdmin, orgTemplateAdmin},
|
||||
false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, orgMemberMe, userAdmin},
|
||||
false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -624,8 +684,8 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceProvisionerDaemon.InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, templateAdmin, setOrgNotMe, orgMemberMe},
|
||||
false: {setOtherOrg, memberMe, userAdmin},
|
||||
true: {owner, templateAdmin, orgAdmin, orgTemplateAdmin},
|
||||
false: {setOtherOrg, memberMe, userAdmin, orgAuditor, orgUserAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -633,7 +693,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceProvisionerDaemon.WithOwner(currentUser.String()).InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, templateAdmin, orgTemplateAdmin, orgMemberMe, orgAdmin},
|
||||
true: {owner, templateAdmin, orgTemplateAdmin, orgAdmin},
|
||||
false: {setOtherOrg, memberMe, userAdmin, orgUserAdmin, orgAuditor},
|
||||
},
|
||||
},
|
||||
@@ -643,7 +703,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceProvisionerJobs.InOrg(orgID),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgTemplateAdmin, orgAdmin},
|
||||
false: {setOtherOrg, memberMe, orgMemberMe, templateAdmin, userAdmin, orgUserAdmin, orgAuditor},
|
||||
false: {setOtherOrg, memberMe, templateAdmin, userAdmin, orgUserAdmin, orgAuditor},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -652,7 +712,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceSystem,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -661,7 +721,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceOauth2App,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -669,7 +729,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceOauth2App,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, setOrgNotMe, setOtherOrg, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
true: {owner, setOrgNotMe, setOtherOrg, memberMe, templateAdmin, userAdmin},
|
||||
false: {},
|
||||
},
|
||||
},
|
||||
@@ -679,7 +739,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceOauth2AppSecret,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOrgNotMe, setOtherOrg, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOrgNotMe, setOtherOrg, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -688,7 +748,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceOauth2AppCodeToken,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOrgNotMe, setOtherOrg, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOrgNotMe, setOtherOrg, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -697,7 +757,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceWorkspaceProxy,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOrgNotMe, setOtherOrg, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOrgNotMe, setOtherOrg, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -705,7 +765,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceWorkspaceProxy,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, setOrgNotMe, setOtherOrg, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
true: {owner, setOrgNotMe, setOtherOrg, memberMe, templateAdmin, userAdmin},
|
||||
false: {},
|
||||
},
|
||||
},
|
||||
@@ -716,11 +776,11 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
||||
Resource: rbac.ResourceNotificationPreference.WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {memberMe, orgMemberMe, owner},
|
||||
true: {memberMe, owner},
|
||||
false: {
|
||||
userAdmin, orgUserAdmin, templateAdmin,
|
||||
orgAuditor, orgTemplateAdmin,
|
||||
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
},
|
||||
},
|
||||
@@ -733,9 +793,9 @@ func TestRolePermissions(t *testing.T) {
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {
|
||||
memberMe, orgMemberMe, userAdmin, orgUserAdmin, templateAdmin,
|
||||
memberMe, userAdmin, orgUserAdmin, templateAdmin,
|
||||
orgAuditor, orgTemplateAdmin,
|
||||
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
},
|
||||
},
|
||||
@@ -747,7 +807,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {
|
||||
memberMe, orgMemberMe, otherOrgMember,
|
||||
memberMe,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
orgAuditor, otherOrgAuditor,
|
||||
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
||||
@@ -767,8 +827,8 @@ func TestRolePermissions(t *testing.T) {
|
||||
false: {
|
||||
memberMe, templateAdmin, orgUserAdmin, userAdmin,
|
||||
orgAdmin, orgAuditor, orgTemplateAdmin,
|
||||
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
otherOrgAdmin, orgMemberMe,
|
||||
otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
otherOrgAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -778,8 +838,8 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete},
|
||||
Resource: rbac.ResourceWebpushSubscription.WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, memberMe, orgMemberMe},
|
||||
false: {otherOrgMember, orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin},
|
||||
true: {owner, memberMe},
|
||||
false: {orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin},
|
||||
},
|
||||
},
|
||||
// AnyOrganization tests
|
||||
@@ -791,8 +851,8 @@ func TestRolePermissions(t *testing.T) {
|
||||
true: {owner, userAdmin, orgAdmin, otherOrgAdmin, orgUserAdmin, otherOrgUserAdmin},
|
||||
false: {
|
||||
memberMe, templateAdmin,
|
||||
orgTemplateAdmin, orgMemberMe, orgAuditor,
|
||||
otherOrgMember, otherOrgAuditor, otherOrgTemplateAdmin,
|
||||
orgTemplateAdmin, orgAuditor,
|
||||
otherOrgAuditor, otherOrgTemplateAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -804,8 +864,8 @@ func TestRolePermissions(t *testing.T) {
|
||||
true: {owner, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, orgAdmin, otherOrgAdmin},
|
||||
false: {
|
||||
userAdmin, memberMe,
|
||||
orgMemberMe, orgAuditor, orgUserAdmin,
|
||||
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin,
|
||||
orgAuditor, orgUserAdmin,
|
||||
otherOrgAuditor, otherOrgUserAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -814,11 +874,11 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionCreate},
|
||||
Resource: rbac.ResourceWorkspace.AnyOrganization().WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, otherOrgAdmin, orgMemberMe},
|
||||
true: {owner, orgAdmin, otherOrgAdmin},
|
||||
false: {
|
||||
memberMe, userAdmin, templateAdmin,
|
||||
orgAuditor, orgUserAdmin, orgTemplateAdmin,
|
||||
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -828,7 +888,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceCryptoKey,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -838,10 +898,10 @@ func TestRolePermissions(t *testing.T) {
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin, orgUserAdmin, userAdmin},
|
||||
false: {
|
||||
orgMemberMe, otherOrgAdmin,
|
||||
otherOrgAdmin,
|
||||
memberMe, templateAdmin,
|
||||
orgAuditor, orgTemplateAdmin,
|
||||
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -853,10 +913,10 @@ func TestRolePermissions(t *testing.T) {
|
||||
true: {owner, userAdmin},
|
||||
false: {
|
||||
orgAdmin, orgUserAdmin,
|
||||
orgMemberMe, otherOrgAdmin,
|
||||
otherOrgAdmin,
|
||||
memberMe, templateAdmin,
|
||||
orgAuditor, orgTemplateAdmin,
|
||||
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -867,7 +927,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {
|
||||
memberMe, orgMemberMe, otherOrgMember,
|
||||
memberMe,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
orgAuditor, otherOrgAuditor,
|
||||
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
||||
@@ -882,7 +942,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {
|
||||
memberMe, orgMemberMe, otherOrgMember,
|
||||
memberMe,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
orgAuditor, otherOrgAuditor,
|
||||
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
||||
@@ -896,7 +956,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
Resource: rbac.ResourceConnectionLog,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
// Only the user themselves can access their own secrets — no one else.
|
||||
@@ -905,10 +965,10 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceUserSecret.WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {memberMe, orgMemberMe},
|
||||
true: {memberMe},
|
||||
false: {
|
||||
owner, orgAdmin,
|
||||
otherOrgAdmin, otherOrgMember, orgAuditor, orgUserAdmin, orgTemplateAdmin,
|
||||
otherOrgAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin,
|
||||
templateAdmin, userAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
},
|
||||
},
|
||||
@@ -921,7 +981,7 @@ func TestRolePermissions(t *testing.T) {
|
||||
true: {},
|
||||
false: {
|
||||
owner,
|
||||
memberMe, orgMemberMe, otherOrgMember,
|
||||
memberMe,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
orgAuditor, otherOrgAuditor,
|
||||
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
||||
@@ -934,9 +994,8 @@ func TestRolePermissions(t *testing.T) {
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
||||
Resource: rbac.ResourceAibridgeInterception.WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, memberMe, orgMemberMe},
|
||||
true: {owner, memberMe},
|
||||
false: {
|
||||
otherOrgMember,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
orgAuditor, otherOrgAuditor,
|
||||
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
||||
@@ -1096,7 +1155,6 @@ func TestListRoles(t *testing.T) {
|
||||
|
||||
require.ElementsMatch(t, []string{
|
||||
fmt.Sprintf("organization-admin:%s", orgID.String()),
|
||||
fmt.Sprintf("organization-member:%s", orgID.String()),
|
||||
fmt.Sprintf("organization-auditor:%s", orgID.String()),
|
||||
fmt.Sprintf("organization-user-admin:%s", orgID.String()),
|
||||
fmt.Sprintf("organization-template-admin:%s", orgID.String()),
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"cdr.dev/slog/v3"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/coderd/util/syncmap"
|
||||
@@ -83,9 +84,10 @@ func Expand(ctx context.Context, db database.Store, names []rbac.RoleIdentifier)
|
||||
// the expansion. These roles are no-ops. Should we raise some kind of
|
||||
// warning when this happens?
|
||||
dbroles, err := db.CustomRoles(ctx, database.CustomRolesParams{
|
||||
LookupRoles: lookupArgs,
|
||||
ExcludeOrgRoles: false,
|
||||
OrganizationID: uuid.Nil,
|
||||
LookupRoles: lookupArgs,
|
||||
ExcludeOrgRoles: false,
|
||||
OrganizationID: uuid.Nil,
|
||||
IncludeSystemRoles: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("fetch custom roles: %w", err)
|
||||
@@ -105,7 +107,8 @@ func Expand(ctx context.Context, db database.Store, names []rbac.RoleIdentifier)
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func convertPermissions(dbPerms []database.CustomRolePermission) []rbac.Permission {
|
||||
// ConvertDBPermissions converts database permissions to RBAC permissions.
|
||||
func ConvertDBPermissions(dbPerms []database.CustomRolePermission) []rbac.Permission {
|
||||
n := make([]rbac.Permission, 0, len(dbPerms))
|
||||
for _, dbPerm := range dbPerms {
|
||||
n = append(n, rbac.Permission{
|
||||
@@ -117,14 +120,28 @@ func convertPermissions(dbPerms []database.CustomRolePermission) []rbac.Permissi
|
||||
return n
|
||||
}
|
||||
|
||||
// ConvertPermissionsToDB converts RBAC permissions to the database
|
||||
// format.
|
||||
func ConvertPermissionsToDB(perms []rbac.Permission) []database.CustomRolePermission {
|
||||
dbPerms := make([]database.CustomRolePermission, 0, len(perms))
|
||||
for _, perm := range perms {
|
||||
dbPerms = append(dbPerms, database.CustomRolePermission{
|
||||
Negate: perm.Negate,
|
||||
ResourceType: perm.ResourceType,
|
||||
Action: perm.Action,
|
||||
})
|
||||
}
|
||||
return dbPerms
|
||||
}
|
||||
|
||||
// ConvertDBRole should not be used by any human facing apis. It is used
|
||||
// for authz purposes.
|
||||
func ConvertDBRole(dbRole database.CustomRole) (rbac.Role, error) {
|
||||
role := rbac.Role{
|
||||
Identifier: dbRole.RoleIdentifier(),
|
||||
DisplayName: dbRole.DisplayName,
|
||||
Site: convertPermissions(dbRole.SitePermissions),
|
||||
User: convertPermissions(dbRole.UserPermissions),
|
||||
Site: ConvertDBPermissions(dbRole.SitePermissions),
|
||||
User: ConvertDBPermissions(dbRole.UserPermissions),
|
||||
}
|
||||
|
||||
// Org permissions only make sense if an org id is specified.
|
||||
@@ -135,10 +152,158 @@ func ConvertDBRole(dbRole database.CustomRole) (rbac.Role, error) {
|
||||
if dbRole.OrganizationID.UUID != uuid.Nil {
|
||||
role.ByOrgID = map[string]rbac.OrgPermissions{
|
||||
dbRole.OrganizationID.UUID.String(): {
|
||||
Org: convertPermissions(dbRole.OrgPermissions),
|
||||
Org: ConvertDBPermissions(dbRole.OrgPermissions),
|
||||
Member: ConvertDBPermissions(dbRole.MemberPermissions),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
// ReconcileSystemRoles ensures that every organization's org-member
|
||||
// system role in the DB is up-to-date with permissions reflecting
|
||||
// current RBAC resources and the organization's
|
||||
// workspace_sharing_disabled setting. Uses PostgreSQL advisory lock
|
||||
// (LockIDReconcileSystemRoles) to safely handle multi-instance
|
||||
// deployments. Uses set-based comparison to avoid unnecessary
|
||||
// database writes when permissions haven't changed.
|
||||
func ReconcileSystemRoles(ctx context.Context, log slog.Logger, db database.Store) error {
|
||||
return db.InTx(func(tx database.Store) error {
|
||||
// Acquire advisory lock to prevent concurrent updates from
|
||||
// multiple coderd instances. Other instances will block here
|
||||
// until we release the lock (when this transaction commits).
|
||||
err := tx.AcquireLock(ctx, database.LockIDReconcileSystemRoles)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("acquire system roles reconciliation lock: %w", err)
|
||||
}
|
||||
|
||||
orgs, err := tx.GetOrganizations(ctx, database.GetOrganizationsParams{})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch organizations: %w", err)
|
||||
}
|
||||
|
||||
customRoles, err := tx.CustomRoles(ctx, database.CustomRolesParams{
|
||||
LookupRoles: nil,
|
||||
ExcludeOrgRoles: false,
|
||||
OrganizationID: uuid.Nil,
|
||||
IncludeSystemRoles: true,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch custom roles: %w", err)
|
||||
}
|
||||
|
||||
// Find org-member roles and index by organization ID for quick lookup.
|
||||
rolesByOrg := make(map[uuid.UUID]database.CustomRole)
|
||||
for _, role := range customRoles {
|
||||
if role.IsSystem && role.Name == rbac.RoleOrgMember() && role.OrganizationID.Valid {
|
||||
rolesByOrg[role.OrganizationID.UUID] = role
|
||||
}
|
||||
}
|
||||
|
||||
for _, org := range orgs {
|
||||
role, exists := rolesByOrg[org.ID]
|
||||
if !exists {
|
||||
// Something is very wrong: the role should have been created by the
|
||||
// database trigger or migration. Log loudly and try creating it as
|
||||
// a last-ditch effort before giving up.
|
||||
log.Critical(ctx, "missing organization-member system role; trying to re-create",
|
||||
slog.F("organization_id", org.ID))
|
||||
|
||||
if err := CreateOrgMemberRole(ctx, tx, org); err != nil {
|
||||
return xerrors.Errorf("create missing organization-member role for organization %s: %w",
|
||||
org.ID, err)
|
||||
}
|
||||
|
||||
// Nothing more to do; the new role's permissions are up-to-date.
|
||||
continue
|
||||
}
|
||||
|
||||
_, _, err := ReconcileOrgMemberRole(ctx, tx, role, org.WorkspaceSharingDisabled)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("reconcile organization-member role for organization %s: %w",
|
||||
org.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// ReconcileOrgMemberRole ensures passed-in org-member role's perms
|
||||
// are correct (current) and stored in the DB. Uses set-based
|
||||
// comparison to avoid unnecessary database writes when permissions
|
||||
// haven't changed. Returns the correct role and a boolean indicating
|
||||
// whether the reconciliation was necessary.
|
||||
// NOTE: Callers must acquire `database.LockIDReconcileSystemRoles` at
|
||||
// the start of the transaction and hold it for the transaction’s
|
||||
// duration. This prevents concurrent org-member reconciliation from
|
||||
// racing and producing inconsistent writes.
|
||||
func ReconcileOrgMemberRole(
|
||||
ctx context.Context,
|
||||
tx database.Store,
|
||||
in database.CustomRole,
|
||||
workspaceSharingDisabled bool,
|
||||
) (
|
||||
database.CustomRole, bool, error,
|
||||
) {
|
||||
// All fields except OrgPermissions and MemberPermissions will be the same.
|
||||
out := in
|
||||
|
||||
// Paranoia check: we don't use these in custom roles yet.
|
||||
// TODO(geokat): Have these as check constraints in DB for now?
|
||||
out.SitePermissions = database.CustomRolePermissions{}
|
||||
out.UserPermissions = database.CustomRolePermissions{}
|
||||
out.DisplayName = ""
|
||||
|
||||
inOrgPerms := ConvertDBPermissions(in.OrgPermissions)
|
||||
inMemberPerms := ConvertDBPermissions(in.MemberPermissions)
|
||||
|
||||
outOrgPerms, outMemberPerms := rbac.OrgMemberPermissions(workspaceSharingDisabled)
|
||||
|
||||
// Compare using set-based comparison (order doesn't matter).
|
||||
match := rbac.PermissionsEqual(inOrgPerms, outOrgPerms) &&
|
||||
rbac.PermissionsEqual(inMemberPerms, outMemberPerms)
|
||||
|
||||
if !match {
|
||||
out.OrgPermissions = ConvertPermissionsToDB(outOrgPerms)
|
||||
out.MemberPermissions = ConvertPermissionsToDB(outMemberPerms)
|
||||
|
||||
_, err := tx.UpdateCustomRole(ctx, database.UpdateCustomRoleParams{
|
||||
Name: out.Name,
|
||||
OrganizationID: out.OrganizationID,
|
||||
DisplayName: out.DisplayName,
|
||||
SitePermissions: out.SitePermissions,
|
||||
UserPermissions: out.UserPermissions,
|
||||
OrgPermissions: out.OrgPermissions,
|
||||
MemberPermissions: out.MemberPermissions,
|
||||
})
|
||||
if err != nil {
|
||||
return out, !match, xerrors.Errorf("update organization-member custom role for organization %s: %w",
|
||||
in.OrganizationID.UUID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return out, !match, nil
|
||||
}
|
||||
|
||||
// CreateOrgMemberRole creates an org-member system role for an organization.
|
||||
func CreateOrgMemberRole(ctx context.Context, tx database.Store, org database.Organization) error {
|
||||
orgPerms, memberPerms := rbac.OrgMemberPermissions(org.WorkspaceSharingDisabled)
|
||||
|
||||
_, err := tx.InsertCustomRole(ctx, database.InsertCustomRoleParams{
|
||||
Name: rbac.RoleOrgMember(),
|
||||
DisplayName: "",
|
||||
OrganizationID: uuid.NullUUID{UUID: org.ID, Valid: true},
|
||||
SitePermissions: database.CustomRolePermissions{},
|
||||
OrgPermissions: ConvertPermissionsToDB(orgPerms),
|
||||
UserPermissions: database.CustomRolePermissions{},
|
||||
MemberPermissions: ConvertPermissionsToDB(memberPerms),
|
||||
IsSystem: true,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert org-member role: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package rolestore_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cdr.dev/slog/v3"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
@@ -39,3 +41,133 @@ func TestExpandCustomRoleRoles(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, roles, 1, "role found")
|
||||
}
|
||||
|
||||
func TestReconcileOrgMemberRole(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
existing, err := database.ExpectOne(db.CustomRoles(ctx, database.CustomRolesParams{
|
||||
LookupRoles: []database.NameOrganizationPair{
|
||||
{
|
||||
Name: rbac.RoleOrgMember(),
|
||||
OrganizationID: org.ID,
|
||||
},
|
||||
},
|
||||
IncludeSystemRoles: true,
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.UpdateCustomRole(ctx, database.UpdateCustomRoleParams{
|
||||
Name: existing.Name,
|
||||
OrganizationID: uuid.NullUUID{
|
||||
UUID: org.ID,
|
||||
Valid: true,
|
||||
},
|
||||
DisplayName: "",
|
||||
SitePermissions: database.CustomRolePermissions{},
|
||||
UserPermissions: database.CustomRolePermissions{},
|
||||
OrgPermissions: database.CustomRolePermissions{},
|
||||
MemberPermissions: database.CustomRolePermissions{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
stale := existing
|
||||
stale.OrgPermissions = database.CustomRolePermissions{}
|
||||
stale.MemberPermissions = database.CustomRolePermissions{}
|
||||
|
||||
reconciled, didUpdate, err := rolestore.ReconcileOrgMemberRole(ctx, db, stale, org.WorkspaceSharingDisabled)
|
||||
require.NoError(t, err)
|
||||
require.True(t, didUpdate, "expected reconciliation to update stale permissions")
|
||||
|
||||
got, err := database.ExpectOne(db.CustomRoles(ctx, database.CustomRolesParams{
|
||||
LookupRoles: []database.NameOrganizationPair{
|
||||
{
|
||||
Name: rbac.RoleOrgMember(),
|
||||
OrganizationID: org.ID,
|
||||
},
|
||||
},
|
||||
IncludeSystemRoles: true,
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
wantOrg, wantMember := rbac.OrgMemberPermissions(org.WorkspaceSharingDisabled)
|
||||
require.True(t, rbac.PermissionsEqual(rolestore.ConvertDBPermissions(got.OrgPermissions), wantOrg))
|
||||
require.True(t, rbac.PermissionsEqual(rolestore.ConvertDBPermissions(got.MemberPermissions), wantMember))
|
||||
require.True(t, rbac.PermissionsEqual(rolestore.ConvertDBPermissions(reconciled.OrgPermissions), wantOrg))
|
||||
require.True(t, rbac.PermissionsEqual(rolestore.ConvertDBPermissions(reconciled.MemberPermissions), wantMember))
|
||||
|
||||
_, didUpdate, err = rolestore.ReconcileOrgMemberRole(ctx, db, reconciled, org.WorkspaceSharingDisabled)
|
||||
require.NoError(t, err)
|
||||
require.False(t, didUpdate, "expected no-op reconciliation when permissions are already current")
|
||||
}
|
||||
|
||||
func TestReconcileSystemRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var sqlDB *sql.DB
|
||||
db, _, sqlDB := dbtestutil.NewDBWithSQLDB(t)
|
||||
|
||||
// The DB trigger will create system roles for the org.
|
||||
org1 := dbgen.Organization(t, db, database.Organization{})
|
||||
org2 := dbgen.Organization(t, db, database.Organization{})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
_, err := sqlDB.ExecContext(ctx, "UPDATE organizations SET workspace_sharing_disabled = true WHERE id = $1", org2.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Simulate a missing system role by bypassing the application's
|
||||
// safety check in DeleteCustomRole (which prevents deleting
|
||||
// system roles).
|
||||
res, err := sqlDB.ExecContext(ctx,
|
||||
"DELETE FROM custom_roles WHERE name = lower($1) AND organization_id = $2",
|
||||
rbac.RoleOrgMember(),
|
||||
org1.ID,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
affected, err := res.RowsAffected()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), affected)
|
||||
|
||||
// Not using testutil.Logger() here because it would fail on the
|
||||
// CRITICAL log line due to the deleted custom role.
|
||||
err = rolestore.ReconcileSystemRoles(ctx, slog.Make(), db)
|
||||
require.NoError(t, err)
|
||||
|
||||
orgs, err := db.GetOrganizations(ctx, database.GetOrganizationsParams{})
|
||||
require.NoError(t, err)
|
||||
|
||||
orgByID := make(map[uuid.UUID]database.Organization, len(orgs))
|
||||
for _, org := range orgs {
|
||||
orgByID[org.ID] = org
|
||||
}
|
||||
|
||||
assertOrgMemberRole := func(t *testing.T, orgID uuid.UUID) {
|
||||
t.Helper()
|
||||
|
||||
org := orgByID[orgID]
|
||||
got, err := database.ExpectOne(db.CustomRoles(ctx, database.CustomRolesParams{
|
||||
LookupRoles: []database.NameOrganizationPair{
|
||||
{
|
||||
Name: rbac.RoleOrgMember(),
|
||||
OrganizationID: orgID,
|
||||
},
|
||||
},
|
||||
IncludeSystemRoles: true,
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
require.True(t, got.IsSystem)
|
||||
|
||||
wantOrg, wantMember := rbac.OrgMemberPermissions(org.WorkspaceSharingDisabled)
|
||||
require.True(t, rbac.PermissionsEqual(rolestore.ConvertDBPermissions(got.OrgPermissions), wantOrg))
|
||||
require.True(t, rbac.PermissionsEqual(rolestore.ConvertDBPermissions(got.MemberPermissions), wantMember))
|
||||
}
|
||||
|
||||
assertOrgMemberRole(t, org1.ID)
|
||||
assertOrgMemberRole(t, org2.ID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user