mirror of
https://github.com/coder/coder.git
synced 2025-06-28 04:33:02 +00:00
Some checks failed
Deploy PR / check_pr (push) Has been cancelled
Deploy PR / get_info (push) Has been cancelled
Deploy PR / comment-pr (push) Has been cancelled
Deploy PR / build (push) Has been cancelled
Deploy PR / deploy (push) Has been cancelled
Cherry-picked fix: add org role read permissions to site wide template admins and auditors (#16733) resolves coder/internal#388 Since site-wide admins and auditors are able to access the members page of any org, they should have read access to org roles Co-authored-by: Jaayden Halko <jaayden.halko@gmail.com>
1018 lines
37 KiB
Go
1018 lines
37 KiB
Go
package rbac_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/coderd/rbac"
|
|
"github.com/coder/coder/v2/coderd/rbac/policy"
|
|
)
|
|
|
|
type hasAuthSubjects interface {
|
|
Subjects() []authSubject
|
|
}
|
|
|
|
type authSubjectSet []authSubject
|
|
|
|
func (a authSubjectSet) Subjects() []authSubject { return a }
|
|
|
|
type authSubject struct {
|
|
// Name is helpful for test assertions
|
|
Name string
|
|
Actor rbac.Subject
|
|
}
|
|
|
|
func (a authSubject) Subjects() []authSubject { return []authSubject{a} }
|
|
|
|
// TestBuiltInRoles makes sure our built-in roles are valid by our own policy
|
|
// rules. If this is incorrect, that is a mistake.
|
|
func TestBuiltInRoles(t *testing.T) {
|
|
t.Parallel()
|
|
for _, r := range rbac.SiteRoles() {
|
|
r := r
|
|
t.Run(r.Identifier.String(), func(t *testing.T) {
|
|
t.Parallel()
|
|
require.NoError(t, r.Valid(), "invalid role")
|
|
})
|
|
}
|
|
|
|
for _, r := range rbac.OrganizationRoles(uuid.New()) {
|
|
r := r
|
|
t.Run(r.Identifier.String(), func(t *testing.T) {
|
|
t.Parallel()
|
|
require.NoError(t, r.Valid(), "invalid role")
|
|
})
|
|
}
|
|
}
|
|
|
|
//nolint:tparallel,paralleltest
|
|
func TestOwnerExec(t *testing.T) {
|
|
owner := rbac.Subject{
|
|
ID: uuid.NewString(),
|
|
Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleOwner()},
|
|
Scope: rbac.ScopeAll,
|
|
}
|
|
|
|
t.Run("NoExec", func(t *testing.T) {
|
|
rbac.ReloadBuiltinRoles(&rbac.RoleOptions{
|
|
NoOwnerWorkspaceExec: true,
|
|
})
|
|
t.Cleanup(func() { rbac.ReloadBuiltinRoles(nil) })
|
|
|
|
auth := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
|
|
// Exec a random workspace
|
|
err := auth.Authorize(context.Background(), owner, policy.ActionSSH,
|
|
rbac.ResourceWorkspace.WithID(uuid.New()).InOrg(uuid.New()).WithOwner(uuid.NewString()))
|
|
require.ErrorAsf(t, err, &rbac.UnauthorizedError{}, "expected unauthorized error")
|
|
})
|
|
|
|
t.Run("Exec", func(t *testing.T) {
|
|
rbac.ReloadBuiltinRoles(&rbac.RoleOptions{
|
|
NoOwnerWorkspaceExec: false,
|
|
})
|
|
t.Cleanup(func() { rbac.ReloadBuiltinRoles(nil) })
|
|
|
|
auth := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
|
|
|
|
// Exec a random workspace
|
|
err := auth.Authorize(context.Background(), owner, policy.ActionSSH,
|
|
rbac.ResourceWorkspace.WithID(uuid.New()).InOrg(uuid.New()).WithOwner(uuid.NewString()))
|
|
require.NoError(t, err, "expected owner can")
|
|
})
|
|
}
|
|
|
|
// nolint:tparallel,paralleltest // subtests share a map, just run sequentially.
|
|
func TestRolePermissions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
crud := []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}
|
|
|
|
auth := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
|
|
|
|
// currentUser is anything that references "me", "mine", or "my".
|
|
currentUser := uuid.New()
|
|
adminID := uuid.New()
|
|
templateAdminID := uuid.New()
|
|
userAdminID := uuid.New()
|
|
auditorID := uuid.New()
|
|
orgID := uuid.New()
|
|
otherOrg := uuid.New()
|
|
workspaceID := uuid.New()
|
|
templateID := uuid.New()
|
|
fileID := uuid.New()
|
|
groupID := uuid.New()
|
|
apiKeyID := uuid.New()
|
|
|
|
// 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)}}}
|
|
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}
|
|
|
|
// 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,
|
|
templateAdmin, userAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
}
|
|
|
|
testCases := []struct {
|
|
// Name the test case to better locate the failing test case.
|
|
Name string
|
|
Resource rbac.Object
|
|
Actions []policy.Action
|
|
// AuthorizeMap must cover all subjects in 'requiredSubjects'.
|
|
// This map will run an Authorize() check with the resource, action,
|
|
// and subjects. The subjects are split into 2 categories, "true" and
|
|
// "false".
|
|
// true: Subjects who Authorize should return no error
|
|
// false: Subjects who Authorize should return forbidden.
|
|
AuthorizeMap map[bool][]hasAuthSubjects
|
|
}{
|
|
{
|
|
Name: "MyUser",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceUserObject(currentUser),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {orgMemberMe, owner, memberMe, templateAdmin, userAdmin, orgUserAdmin, otherOrgAdmin, otherOrgUserAdmin, orgAdmin},
|
|
false: {
|
|
orgTemplateAdmin, orgAuditor,
|
|
otherOrgMember, otherOrgAuditor, otherOrgTemplateAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "AUser",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceUser,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, userAdmin},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "ReadMyWorkspaceInOrg",
|
|
// When creating the WithID won't be set, but it does not change the result.
|
|
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},
|
|
false: {setOtherOrg, memberMe, userAdmin, orgAuditor, orgUserAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "UpdateMyWorkspaceInOrg",
|
|
// When creating the WithID won't be set, but it does not change the result.
|
|
Actions: []policy.Action{policy.ActionUpdate},
|
|
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},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateDeleteMyWorkspaceInOrg",
|
|
// When creating the WithID won't be set, but it does not change the result.
|
|
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},
|
|
},
|
|
},
|
|
{
|
|
Name: "MyWorkspaceInOrgExecution",
|
|
// When creating the WithID won't be set, but it does not change the result.
|
|
Actions: []policy.Action{policy.ActionSSH},
|
|
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgMemberMe},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "MyWorkspaceInOrgAppConnect",
|
|
// When creating the WithID won't be set, but it does not change the result.
|
|
Actions: []policy.Action{policy.ActionApplicationConnect},
|
|
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgMemberMe},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "Templates",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceTemplate.WithID(templateID).InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin},
|
|
false: {setOtherOrg, orgUserAdmin, orgAuditor, memberMe, orgMemberMe, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "ReadTemplates",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionViewInsights},
|
|
Resource: rbac.ResourceTemplate.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAuditor, orgAdmin, templateAdmin, orgTemplateAdmin},
|
|
false: {setOtherOrg, orgUserAdmin, memberMe, userAdmin, orgMemberMe},
|
|
},
|
|
},
|
|
{
|
|
Name: "UseTemplates",
|
|
Actions: []policy.Action{policy.ActionUse},
|
|
Resource: rbac.ResourceTemplate.InOrg(orgID).WithGroupACL(map[string][]policy.Action{
|
|
groupID.String(): {policy.ActionUse},
|
|
}),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin, groupMemberMe},
|
|
false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, userAdmin, orgMemberMe},
|
|
},
|
|
},
|
|
{
|
|
Name: "Files",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceFile.WithID(fileID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
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},
|
|
},
|
|
},
|
|
{
|
|
Name: "MyFile",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead},
|
|
Resource: rbac.ResourceFile.WithID(fileID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, memberMe, orgMemberMe, templateAdmin},
|
|
false: {setOtherOrg, setOrgNotMe, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateOrganizations",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceOrganization,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "Organizations",
|
|
Actions: []policy.Action{policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceOrganization.WithID(orgID).InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin},
|
|
false: {setOtherOrg, orgTemplateAdmin, orgUserAdmin, orgAuditor, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "ReadOrganizations",
|
|
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},
|
|
false: {setOtherOrg, memberMe},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateUpdateDeleteCustomRole",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceAssignOrgRole,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, userAdmin, orgMemberMe, memberMe, templateAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "RoleAssignment",
|
|
Actions: []policy.Action{policy.ActionAssign, policy.ActionUnassign},
|
|
Resource: rbac.ResourceAssignRole,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, userAdmin},
|
|
false: {setOtherOrg, setOrgNotMe, orgMemberMe, memberMe, templateAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "ReadRoleAssignment",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceAssignRole,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {setOtherOrg, setOrgNotMe, owner, orgMemberMe, memberMe, templateAdmin, userAdmin},
|
|
false: {},
|
|
},
|
|
},
|
|
{
|
|
Name: "OrgRoleAssignment",
|
|
Actions: []policy.Action{policy.ActionAssign, policy.ActionUnassign},
|
|
Resource: rbac.ResourceAssignOrgRole.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, userAdmin, orgUserAdmin},
|
|
false: {setOtherOrg, orgMemberMe, memberMe, templateAdmin, orgTemplateAdmin, orgAuditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateOrgRoleAssignment",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate},
|
|
Resource: rbac.ResourceAssignOrgRole.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin},
|
|
false: {setOtherOrg, orgUserAdmin, orgTemplateAdmin, orgAuditor, orgMemberMe, memberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "ReadOrgRoleAssignment",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceAssignOrgRole.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, setOrgNotMe, orgMemberMe, userAdmin, templateAdmin},
|
|
false: {setOtherOrg, memberMe},
|
|
},
|
|
},
|
|
{
|
|
Name: "APIKey",
|
|
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},
|
|
false: {setOtherOrg, setOrgNotMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "UserData",
|
|
Actions: []policy.Action{policy.ActionReadPersonal, policy.ActionUpdatePersonal},
|
|
Resource: rbac.ResourceUserObject(currentUser),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgMemberMe, memberMe, userAdmin},
|
|
false: {setOtherOrg, setOrgNotMe, templateAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "ManageOrgMember",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
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},
|
|
},
|
|
},
|
|
{
|
|
Name: "ReadOrgMember",
|
|
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},
|
|
false: {memberMe, setOtherOrg},
|
|
},
|
|
},
|
|
{
|
|
Name: "AllUsersGroupACL",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceTemplate.WithID(templateID).InOrg(orgID).WithGroupACL(
|
|
map[string][]policy.Action{
|
|
orgID.String(): {policy.ActionRead},
|
|
}),
|
|
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, orgMemberMe, templateAdmin, orgUserAdmin, orgTemplateAdmin, orgAuditor},
|
|
false: {setOtherOrg, memberMe, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "Groups",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionDelete, policy.ActionUpdate},
|
|
Resource: rbac.ResourceGroup.WithID(groupID).InOrg(orgID).WithGroupACL(map[string][]policy.Action{
|
|
groupID.String(): {
|
|
policy.ActionRead,
|
|
},
|
|
}),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, userAdmin, orgUserAdmin},
|
|
false: {setOtherOrg, memberMe, orgMemberMe, templateAdmin, orgTemplateAdmin, groupMemberMe, orgAuditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "GroupsRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceGroup.WithID(groupID).InOrg(orgID).WithGroupACL(map[string][]policy.Action{
|
|
groupID.String(): {
|
|
policy.ActionRead,
|
|
},
|
|
}),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, groupMemberMe, orgAuditor},
|
|
false: {setOtherOrg, memberMe, orgMemberMe},
|
|
},
|
|
},
|
|
{
|
|
Name: "GroupMemberMeRead",
|
|
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},
|
|
false: {setOtherOrg, memberMe},
|
|
},
|
|
},
|
|
{
|
|
Name: "GroupMemberOtherRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
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},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceDormant",
|
|
Actions: append(crud, policy.ActionWorkspaceStop),
|
|
Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {orgMemberMe, orgAdmin, owner},
|
|
false: {setOtherOrg, userAdmin, memberMe, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceDormantUse",
|
|
Actions: []policy.Action{policy.ActionWorkspaceStart, policy.ActionApplicationConnect, policy.ActionSSH},
|
|
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},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceBuild",
|
|
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},
|
|
false: {setOtherOrg, userAdmin, templateAdmin, memberMe, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
|
},
|
|
},
|
|
// Some admin style resources
|
|
{
|
|
Name: "Licenses",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete},
|
|
Resource: rbac.ResourceLicense,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "DeploymentStats",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceDeploymentStats,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "DeploymentConfig",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceDeploymentConfig,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "DebugInfo",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceDebugInfo,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "Replicas",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceReplicas,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "TailnetCoordinator",
|
|
Actions: crud,
|
|
Resource: rbac.ResourceTailnetCoordinator,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "AuditLogs",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionCreate},
|
|
Resource: rbac.ResourceAuditLog,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "ProvisionerDaemons",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceProvisionerDaemon.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, templateAdmin, orgAdmin, orgTemplateAdmin},
|
|
false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, orgMemberMe, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "ProvisionerDaemonsRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceProvisionerDaemon.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, templateAdmin, setOrgNotMe, orgMemberMe},
|
|
false: {setOtherOrg, memberMe, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "UserProvisionerDaemons",
|
|
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},
|
|
false: {setOtherOrg, memberMe, userAdmin, orgUserAdmin, orgAuditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "ProvisionerJobs",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceProvisionerJobs.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgTemplateAdmin, orgAdmin},
|
|
false: {setOtherOrg, memberMe, orgMemberMe, templateAdmin, userAdmin, orgUserAdmin, orgAuditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "System",
|
|
Actions: crud,
|
|
Resource: rbac.ResourceSystem,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "Oauth2App",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceOauth2App,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "Oauth2AppRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceOauth2App,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, setOrgNotMe, setOtherOrg, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
false: {},
|
|
},
|
|
},
|
|
{
|
|
Name: "Oauth2AppSecret",
|
|
Actions: crud,
|
|
Resource: rbac.ResourceOauth2AppSecret,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOrgNotMe, setOtherOrg, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "Oauth2Token",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete},
|
|
Resource: rbac.ResourceOauth2AppCodeToken,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOrgNotMe, setOtherOrg, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceProxy",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceWorkspaceProxy,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOrgNotMe, setOtherOrg, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceProxyRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceWorkspaceProxy,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, setOrgNotMe, setOtherOrg, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
false: {},
|
|
},
|
|
},
|
|
{
|
|
// Any owner/admin across may access any users' preferences
|
|
// Members may not access other members' preferences
|
|
Name: "NotificationPreferencesOwn",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceNotificationPreference.WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {memberMe, orgMemberMe, owner},
|
|
false: {
|
|
userAdmin, orgUserAdmin, templateAdmin,
|
|
orgAuditor, orgTemplateAdmin,
|
|
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
orgAdmin, otherOrgAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Any owner/admin may access notification templates
|
|
Name: "NotificationTemplates",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceNotificationTemplate,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {
|
|
memberMe, orgMemberMe, userAdmin, orgUserAdmin, templateAdmin,
|
|
orgAuditor, orgTemplateAdmin,
|
|
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
orgAdmin, otherOrgAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "NotificationMessages",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceNotificationMessage,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {
|
|
memberMe, orgMemberMe, otherOrgMember,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Notification preferences are currently not organization-scoped
|
|
// Any owner/admin may access any users' preferences
|
|
// Members may not access other members' preferences
|
|
Name: "NotificationPreferencesOtherUser",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceNotificationPreference.WithOwner(uuid.NewString()), // some other user
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {
|
|
memberMe, templateAdmin, orgUserAdmin, userAdmin,
|
|
orgAdmin, orgAuditor, orgTemplateAdmin,
|
|
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
otherOrgAdmin, orgMemberMe,
|
|
},
|
|
},
|
|
},
|
|
// AnyOrganization tests
|
|
{
|
|
Name: "CreateOrgMember",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceOrganizationMember.AnyOrganization(),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, userAdmin, orgAdmin, otherOrgAdmin, orgUserAdmin, otherOrgUserAdmin},
|
|
false: {
|
|
memberMe, templateAdmin,
|
|
orgTemplateAdmin, orgMemberMe, orgAuditor,
|
|
otherOrgMember, otherOrgAuditor, otherOrgTemplateAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateTemplateAnyOrg",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceTemplate.AnyOrganization(),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, orgAdmin, otherOrgAdmin},
|
|
false: {
|
|
userAdmin, memberMe,
|
|
orgMemberMe, orgAuditor, orgUserAdmin,
|
|
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateWorkspaceAnyOrg",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceWorkspace.AnyOrganization().WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, otherOrgAdmin, orgMemberMe},
|
|
false: {
|
|
memberMe, userAdmin, templateAdmin,
|
|
orgAuditor, orgUserAdmin, orgTemplateAdmin,
|
|
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "CryptoKeys",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete, policy.ActionRead},
|
|
Resource: rbac.ResourceCryptoKey,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "IDPSyncSettings",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceIdpsyncSettings.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, orgUserAdmin, userAdmin},
|
|
false: {
|
|
orgMemberMe, otherOrgAdmin,
|
|
memberMe, templateAdmin,
|
|
orgAuditor, orgTemplateAdmin,
|
|
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "OrganizationIDPSyncSettings",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceIdpsyncSettings,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, userAdmin},
|
|
false: {
|
|
orgAdmin, orgUserAdmin,
|
|
orgMemberMe, otherOrgAdmin,
|
|
memberMe, templateAdmin,
|
|
orgAuditor, orgTemplateAdmin,
|
|
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "ResourceMonitor",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionCreate, policy.ActionUpdate},
|
|
Resource: rbac.ResourceWorkspaceAgentResourceMonitor,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {
|
|
memberMe, orgMemberMe, otherOrgMember,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// We expect every permission to be tested above.
|
|
remainingPermissions := make(map[string]map[policy.Action]bool)
|
|
for rtype, perms := range policy.RBACPermissions {
|
|
remainingPermissions[rtype] = make(map[policy.Action]bool)
|
|
for action := range perms.Actions {
|
|
remainingPermissions[rtype][action] = true
|
|
}
|
|
}
|
|
|
|
passed := true
|
|
// nolint:tparallel,paralleltest
|
|
for _, c := range testCases {
|
|
c := c
|
|
// nolint:tparallel,paralleltest // These share the same remainingPermissions map
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
remainingSubjs := make(map[string]struct{})
|
|
for _, subj := range requiredSubjects {
|
|
remainingSubjs[subj.Name] = struct{}{}
|
|
}
|
|
|
|
for _, action := range c.Actions {
|
|
err := c.Resource.ValidAction(action)
|
|
ok := assert.NoError(t, err, "%q is not a valid action for type %q", action, c.Resource.Type)
|
|
if !ok {
|
|
passed = passed && assert.NoError(t, err, "%q is not a valid action for type %q", action, c.Resource.Type)
|
|
continue
|
|
}
|
|
|
|
for result, sets := range c.AuthorizeMap {
|
|
subjs := make([]authSubject, 0)
|
|
for _, set := range sets {
|
|
subjs = append(subjs, set.Subjects()...)
|
|
}
|
|
used := make(map[string]bool)
|
|
|
|
for _, subj := range subjs {
|
|
if _, ok := used[subj.Name]; ok {
|
|
assert.False(t, true, "duplicate subject %q", subj.Name)
|
|
}
|
|
used[subj.Name] = true
|
|
|
|
delete(remainingSubjs, subj.Name)
|
|
msg := fmt.Sprintf("%s as %q doing %q on %q", c.Name, subj.Name, action, c.Resource.Type)
|
|
// TODO: scopey
|
|
actor := subj.Actor
|
|
// Actor is missing some fields
|
|
if actor.Scope == nil {
|
|
actor.Scope = rbac.ScopeAll
|
|
}
|
|
|
|
delete(remainingPermissions[c.Resource.Type], action)
|
|
err := auth.Authorize(context.Background(), actor, action, c.Resource)
|
|
if result {
|
|
passed = passed && assert.NoError(t, err, fmt.Sprintf("Should pass: %s", msg))
|
|
} else {
|
|
passed = passed && assert.ErrorContains(t, err, "forbidden", fmt.Sprintf("Should fail: %s", msg))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
require.Empty(t, remainingSubjs, "test should cover all subjects")
|
|
})
|
|
}
|
|
|
|
// Only run these if the tests on top passed. Otherwise, the error output is too noisy.
|
|
if passed {
|
|
for rtype, v := range remainingPermissions {
|
|
// nolint:tparallel,paralleltest // Making a subtest for easier diagnosing failures.
|
|
t.Run(fmt.Sprintf("%s-AllActions", rtype), func(t *testing.T) {
|
|
if len(v) > 0 {
|
|
assert.Equal(t, map[policy.Action]bool{}, v, "remaining permissions should be empty for type %q", rtype)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsOrgRole(t *testing.T) {
|
|
t.Parallel()
|
|
randomUUID, err := uuid.Parse("cad8c09d-c099-4ec7-9263-7d52b1a3997a")
|
|
require.NoError(t, err)
|
|
|
|
testCases := []struct {
|
|
Identifier rbac.RoleIdentifier
|
|
OrgRole bool
|
|
OrgID uuid.UUID
|
|
}{
|
|
// Not org roles
|
|
{Identifier: rbac.RoleOwner()},
|
|
{Identifier: rbac.RoleMember()},
|
|
{Identifier: rbac.RoleAuditor()},
|
|
{
|
|
Identifier: rbac.RoleIdentifier{},
|
|
OrgRole: false,
|
|
},
|
|
|
|
// Org roles
|
|
{
|
|
Identifier: rbac.ScopedRoleOrgAdmin(randomUUID),
|
|
OrgRole: true,
|
|
OrgID: randomUUID,
|
|
},
|
|
{
|
|
Identifier: rbac.ScopedRoleOrgMember(randomUUID),
|
|
OrgRole: true,
|
|
OrgID: randomUUID,
|
|
},
|
|
}
|
|
|
|
// nolint:paralleltest
|
|
for _, c := range testCases {
|
|
c := c
|
|
t.Run(c.Identifier.String(), func(t *testing.T) {
|
|
t.Parallel()
|
|
ok := c.Identifier.IsOrgRole()
|
|
require.Equal(t, c.OrgRole, ok, "match expected org role")
|
|
require.Equal(t, c.OrgID, c.Identifier.OrganizationID, "match expected org id")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListRoles(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
siteRoles := rbac.SiteRoles()
|
|
siteRoleNames := make([]string, 0, len(siteRoles))
|
|
for _, role := range siteRoles {
|
|
siteRoleNames = append(siteRoleNames, role.Identifier.Name)
|
|
}
|
|
|
|
// If this test is ever failing, just update the list to the roles
|
|
// expected from the builtin set.
|
|
// Always use constant strings, as if the names change, we need to write
|
|
// a SQL migration to change the name on the backend.
|
|
require.ElementsMatch(t, []string{
|
|
"owner",
|
|
"member",
|
|
"auditor",
|
|
"template-admin",
|
|
"user-admin",
|
|
},
|
|
siteRoleNames)
|
|
|
|
orgID := uuid.New()
|
|
orgRoles := rbac.OrganizationRoles(orgID)
|
|
orgRoleNames := make([]string, 0, len(orgRoles))
|
|
for _, role := range orgRoles {
|
|
orgRoleNames = append(orgRoleNames, role.Identifier.String())
|
|
}
|
|
|
|
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()),
|
|
fmt.Sprintf("organization-workspace-creation-ban:%s", orgID.String()),
|
|
},
|
|
orgRoleNames)
|
|
}
|
|
|
|
func TestChangeSet(t *testing.T) {
|
|
t.Parallel()
|
|
testCases := []struct {
|
|
Name string
|
|
From []string
|
|
To []string
|
|
ExpAdd []string
|
|
ExpRemove []string
|
|
}{
|
|
{
|
|
Name: "Empty",
|
|
},
|
|
{
|
|
Name: "Same",
|
|
From: []string{"a", "b", "c"},
|
|
To: []string{"a", "b", "c"},
|
|
ExpAdd: []string{},
|
|
ExpRemove: []string{},
|
|
},
|
|
{
|
|
Name: "AllRemoved",
|
|
From: []string{"a", "b", "c"},
|
|
ExpRemove: []string{"a", "b", "c"},
|
|
},
|
|
{
|
|
Name: "AllAdded",
|
|
To: []string{"a", "b", "c"},
|
|
ExpAdd: []string{"a", "b", "c"},
|
|
},
|
|
{
|
|
Name: "AddAndRemove",
|
|
From: []string{"a", "b", "c"},
|
|
To: []string{"a", "b", "d", "e"},
|
|
ExpAdd: []string{"d", "e"},
|
|
ExpRemove: []string{"c"},
|
|
},
|
|
}
|
|
|
|
convert := func(s []string) rbac.RoleIdentifiers {
|
|
tmp := make([]rbac.RoleIdentifier, 0, len(s))
|
|
for _, e := range s {
|
|
tmp = append(tmp, rbac.RoleIdentifier{Name: e})
|
|
}
|
|
return tmp
|
|
}
|
|
|
|
for _, c := range testCases {
|
|
c := c
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
add, remove := rbac.ChangeRoleSet(convert(c.From), convert(c.To))
|
|
require.ElementsMatch(t, convert(c.ExpAdd), add, "expect added")
|
|
require.ElementsMatch(t, convert(c.ExpRemove), remove, "expect removed")
|
|
})
|
|
}
|
|
}
|