chore: remove organization_id suffix from org_member roles in database (#13473)

Organization member's table is already scoped to an organization.
Rolename should avoid having the org_id appended.

Wipes all existing organization role assignments, which should not be used anyway.
This commit is contained in:
Steven Masley
2024-06-05 11:25:02 -05:00
committed by GitHub
parent fade8ba759
commit 8f62311f00
38 changed files with 200 additions and 118 deletions
+1 -1
View File
@@ -222,7 +222,7 @@ func (r *RootCmd) newCreateAdminUserCommand() *serpent.Command {
UserID: newUser.ID,
CreatedAt: dbtime.Now(),
UpdatedAt: dbtime.Now(),
Roles: []string{rbac.RoleOrgAdmin(org.ID)},
Roles: []string{rbac.ScopedRoleOrgAdmin(org.ID)},
})
if err != nil {
return xerrors.Errorf("insert organization member: %w", err)
+1 -1
View File
@@ -71,7 +71,7 @@ func TestServerCreateAdminUser(t *testing.T) {
orgIDs2 := make(map[uuid.UUID]struct{}, len(orgMemberships))
for _, membership := range orgMemberships {
orgIDs2[membership.OrganizationID] = struct{}{}
assert.Equal(t, []string{rbac.RoleOrgAdmin(membership.OrganizationID)}, membership.Roles, "user is not org admin")
assert.Equal(t, []string{rbac.ScopedRoleOrgAdmin(membership.OrganizationID)}, membership.Roles, "user is not org admin")
}
require.Equal(t, orgIDs, orgIDs2, "user is not in all orgs")
+3
View File
@@ -11458,6 +11458,9 @@ const docTemplate = `{
},
"name": {
"type": "string"
},
"organization_id": {
"type": "string"
}
}
},
+3
View File
@@ -10341,6 +10341,9 @@
},
"name": {
"type": "string"
},
"organization_id": {
"type": "string"
}
}
},
+1 -1
View File
@@ -27,7 +27,7 @@ func TestCheckPermissions(t *testing.T) {
memberClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID)
memberUser, err := memberClient.User(ctx, codersdk.Me)
require.NoError(t, err)
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID, rbac.RoleOrgAdmin(adminUser.OrganizationID))
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID, rbac.ScopedRoleOrgAdmin(adminUser.OrganizationID))
orgAdminUser, err := orgAdminClient.User(ctx, codersdk.Me)
require.NoError(t, err)
+1 -1
View File
@@ -177,7 +177,7 @@ func setupDeps(t *testing.T, store database.Store, ps pubsub.Pubsub) deps {
_, err := store.InsertOrganizationMember(context.Background(), database.InsertOrganizationMemberParams{
OrganizationID: org.ID,
UserID: user.ID,
Roles: []string{rbac.RoleOrgMember(org.ID)},
Roles: []string{rbac.ScopedRoleOrgMember(org.ID)},
})
require.NoError(t, err)
tv := dbgen.TemplateVersion(t, store, database.TemplateVersion{
+4 -1
View File
@@ -663,6 +663,7 @@ func CreateFirstUser(t testing.TB, client *codersdk.Client) codersdk.CreateFirst
}
// CreateAnotherUser creates and authenticates a new user.
// Roles can include org scoped roles with 'roleName:<organization_id>'
func CreateAnotherUser(t testing.TB, client *codersdk.Client, organizationID uuid.UUID, roles ...string) (*codersdk.Client, codersdk.User) {
return createAnotherUserRetry(t, client, organizationID, 5, roles)
}
@@ -680,7 +681,7 @@ func AuthzUserSubject(user codersdk.User, orgID uuid.UUID) rbac.Subject {
roles = append(roles, r.Name)
}
// We assume only 1 org exists
roles = append(roles, rbac.RoleOrgMember(orgID))
roles = append(roles, rbac.ScopedRoleOrgMember(orgID))
return rbac.Subject{
ID: user.ID.String(),
@@ -754,6 +755,8 @@ func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationI
for _, roleName := range roles {
roleName := roleName
orgID, ok := rbac.IsOrgRole(roleName)
roleName, _, err = rbac.RoleSplit(roleName)
require.NoError(t, err, "split org role name")
if ok {
orgRoles[orgID] = append(orgRoles[orgID], roleName)
} else {
+13 -7
View File
@@ -204,13 +204,6 @@ func Group(group database.Group, members []database.User) codersdk.Group {
}
}
func SlimRole(role rbac.Role) codersdk.SlimRole {
return codersdk.SlimRole{
DisplayName: role.DisplayName,
Name: role.Name,
}
}
func TemplateInsightsParameters(parameterRows []database.GetTemplateParameterInsightsRow) ([]codersdk.TemplateParameterUsage, error) {
// Use a stable sort, similarly to how we would sort in the query, note that
// we don't sort in the query because order varies depending on the table
@@ -525,6 +518,19 @@ func ProvisionerDaemon(dbDaemon database.ProvisionerDaemon) codersdk.Provisioner
return result
}
func SlimRole(role rbac.Role) codersdk.SlimRole {
roleName, orgIDStr, err := rbac.RoleSplit(role.Name)
if err != nil {
roleName = role.Name
}
return codersdk.SlimRole{
DisplayName: role.DisplayName,
Name: roleName,
OrganizationID: orgIDStr,
}
}
func RBACRole(role rbac.Role) codersdk.Role {
roleName, orgIDStr, err := rbac.RoleSplit(role.Name)
if err != nil {
+3 -3
View File
@@ -153,7 +153,7 @@ func TestUpsertCustomRoles(t *testing.T) {
UUID: uuid.New(),
Valid: true,
},
subject: merge(canAssignRole, rbac.RoleOrgAdmin(orgID.UUID)),
subject: merge(canAssignRole, rbac.ScopedRoleOrgAdmin(orgID.UUID)),
org: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
codersdk.ResourceWorkspace: {codersdk.ActionRead},
}),
@@ -162,7 +162,7 @@ func TestUpsertCustomRoles(t *testing.T) {
{
name: "user-escalation",
// These roles do not grant user perms
subject: merge(canAssignRole, rbac.RoleOrgAdmin(orgID.UUID)),
subject: merge(canAssignRole, rbac.ScopedRoleOrgAdmin(orgID.UUID)),
user: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
codersdk.ResourceWorkspace: {codersdk.ActionRead},
}),
@@ -190,7 +190,7 @@ func TestUpsertCustomRoles(t *testing.T) {
},
{
name: "read-workspace-in-org",
subject: merge(canAssignRole, rbac.RoleOrgAdmin(orgID.UUID)),
subject: merge(canAssignRole, rbac.ScopedRoleOrgAdmin(orgID.UUID)),
organizationID: orgID,
org: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
codersdk.ResourceWorkspace: {codersdk.ActionRead},
+16 -2
View File
@@ -2472,7 +2472,7 @@ func (q *querier) InsertOrganization(ctx context.Context, arg database.InsertOrg
func (q *querier) InsertOrganizationMember(ctx context.Context, arg database.InsertOrganizationMemberParams) (database.OrganizationMember, error) {
// All roles are added roles. Org member is always implied.
addedRoles := append(arg.Roles, rbac.RoleOrgMember(arg.OrganizationID))
addedRoles := append(arg.Roles, rbac.ScopedRoleOrgMember(arg.OrganizationID))
err := q.canAssignRoles(ctx, &arg.OrganizationID, addedRoles, []string{})
if err != nil {
return database.OrganizationMember{}, err
@@ -2847,8 +2847,22 @@ func (q *querier) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemb
return database.OrganizationMember{}, err
}
// The 'rbac' package expects role names to be scoped.
// Convert the argument roles for validation.
scopedGranted := make([]string, 0, len(arg.GrantedRoles))
for _, grantedRole := range arg.GrantedRoles {
// This check is a developer safety check. Old code might try to invoke this code path with
// organization id suffixes. Catch this and return a nice error so it can be fixed.
_, foundOrg, _ := rbac.RoleSplit(grantedRole)
if foundOrg != "" {
return database.OrganizationMember{}, xerrors.Errorf("attempt to assign a role %q, remove the ':<organization_id> suffix", grantedRole)
}
scopedGranted = append(scopedGranted, rbac.RoleName(grantedRole, arg.OrgID.String()))
}
// The org member role is always implied.
impliedTypes := append(arg.GrantedRoles, rbac.RoleOrgMember(arg.OrgID))
impliedTypes := append(scopedGranted, rbac.ScopedRoleOrgMember(arg.OrgID))
added, removed := rbac.ChangeRoleSet(member.Roles, impliedTypes)
err = q.canAssignRoles(ctx, &arg.OrgID, added, removed)
if err != nil {
+2 -2
View File
@@ -636,7 +636,7 @@ func (s *MethodTestSuite) TestOrganization() {
check.Args(database.InsertOrganizationMemberParams{
OrganizationID: o.ID,
UserID: u.ID,
Roles: []string{rbac.RoleOrgAdmin(o.ID)},
Roles: []string{rbac.ScopedRoleOrgAdmin(o.ID)},
}).Asserts(
rbac.ResourceAssignRole.InOrg(o.ID), policy.ActionAssign,
rbac.ResourceOrganizationMember.InOrg(o.ID).WithID(u.ID), policy.ActionCreate)
@@ -664,7 +664,7 @@ func (s *MethodTestSuite) TestOrganization() {
mem := dbgen.OrganizationMember(s.T(), db, database.OrganizationMember{
OrganizationID: o.ID,
UserID: u.ID,
Roles: []string{rbac.RoleOrgAdmin(o.ID)},
Roles: []string{rbac.ScopedRoleOrgAdmin(o.ID)},
})
out := mem
out.Roles = []string{}
+3 -1
View File
@@ -1997,7 +1997,9 @@ func (q *FakeQuerier) GetAuthorizationUserRoles(_ context.Context, userID uuid.U
for _, mem := range q.organizationMembers {
if mem.UserID == userID {
roles = append(roles, mem.Roles...)
for _, orgRole := range mem.Roles {
roles = append(roles, orgRole+":"+mem.OrganizationID.String())
}
roles = append(roles, "organization-member:"+mem.OrganizationID.String())
}
}
+1 -1
View File
@@ -576,7 +576,7 @@ CREATE TABLE organization_members (
organization_id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
roles text[] DEFAULT '{organization-member}'::text[] NOT NULL
roles text[] DEFAULT '{}'::text[] NOT NULL
);
CREATE TABLE organizations (
@@ -0,0 +1 @@
ALTER TABLE ONLY organization_members ALTER COLUMN roles SET DEFAULT '{organization-member}';
@@ -0,0 +1,7 @@
-- The default was 'organization-member', but we imply that in the
-- 'GetAuthorizationUserRoles' query.
ALTER TABLE ONLY organization_members ALTER COLUMN roles SET DEFAULT '{}';
-- No one should be using organization roles yet. If they are, the names in the
-- database are now incorrect. Just remove them all.
UPDATE organization_members SET roles = '{}';
+5 -3
View File
@@ -8432,12 +8432,14 @@ SELECT
array_append(users.rbac_roles, 'member'),
(
SELECT
array_agg(org_roles)
-- The roles are returned as a flat array, org scoped and site side.
-- Concatenating the organization id scopes the organization roles.
array_agg(org_roles || ':' || organization_members.organization_id::text)
FROM
organization_members,
-- All org_members get the org-member role for their orgs
-- All org_members get the organization-member role for their orgs
unnest(
array_append(roles, 'organization-member:' || organization_members.organization_id::text)
array_append(roles, 'organization-member')
) AS org_roles
WHERE
user_id = users.id
+5 -3
View File
@@ -227,12 +227,14 @@ SELECT
array_append(users.rbac_roles, 'member'),
(
SELECT
array_agg(org_roles)
-- The roles are returned as a flat array, org scoped and site side.
-- Concatenating the organization id scopes the organization roles.
array_agg(org_roles || ':' || organization_members.organization_id::text)
FROM
organization_members,
-- All org_members get the org-member role for their orgs
-- All org_members get the organization-member role for their orgs
unnest(
array_append(roles, 'organization-member:' || organization_members.organization_id::text)
array_append(roles, 'organization-member')
) AS org_roles
WHERE
user_id = users.id
+4 -4
View File
@@ -68,7 +68,7 @@ func TestExtractUserRoles(t *testing.T) {
Roles: orgRoles,
})
require.NoError(t, err)
return user, append(roles, append(orgRoles, rbac.RoleMember(), rbac.RoleOrgMember(org.ID))...), token
return user, append(roles, append(orgRoles, rbac.RoleMember(), rbac.ScopedRoleOrgMember(org.ID))...), token
},
},
{
@@ -89,7 +89,8 @@ func TestExtractUserRoles(t *testing.T) {
orgRoles := []string{}
if i%2 == 0 {
orgRoles = append(orgRoles, rbac.RoleOrgAdmin(organization.ID))
orgRoles = append(orgRoles, rbac.RoleOrgAdmin())
roles = append(roles, rbac.ScopedRoleOrgAdmin(organization.ID))
}
_, err = db.InsertOrganizationMember(context.Background(), database.InsertOrganizationMemberParams{
OrganizationID: organization.ID,
@@ -99,8 +100,7 @@ func TestExtractUserRoles(t *testing.T) {
Roles: orgRoles,
})
require.NoError(t, err)
roles = append(roles, orgRoles...)
roles = append(roles, rbac.RoleOrgMember(organization.ID))
roles = append(roles, rbac.ScopedRoleOrgMember(organization.ID))
}
return user, roles, token
},
+1 -1
View File
@@ -152,7 +152,7 @@ func TestOrganizationParam(t *testing.T) {
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
OrganizationID: organization.ID,
UserID: user.ID,
Roles: []string{rbac.RoleOrgMember(organization.ID)},
Roles: []string{rbac.ScopedRoleOrgMember(organization.ID)},
})
_, err := db.UpdateUserRoles(ctx, database.UpdateUserRolesParams{
ID: user.ID,
+1 -1
View File
@@ -68,7 +68,7 @@ func convertOrganizationMember(mem database.OrganizationMember) codersdk.Organiz
}
for _, roleName := range mem.Roles {
rbacRole, _ := rbac.RoleByName(roleName)
rbacRole, _ := rbac.RoleByName(rbac.RoleName(roleName, mem.OrganizationID.String()))
convertedMember.Roles = append(convertedMember.Roles, db2sdk.SlimRole(rbacRole))
}
return convertedMember
+1 -1
View File
@@ -94,7 +94,7 @@ func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) {
// come back to determining the default role of the person who
// creates the org. Until that happens, all users in an organization
// should be just regular members.
rbac.RoleOrgMember(organization.ID),
rbac.ScopedRoleOrgMember(organization.ID),
},
})
if err != nil {
+20 -20
View File
@@ -168,7 +168,7 @@ func TestFilter(t *testing.T) {
Name: "Admin",
Actor: Subject{
ID: userIDs[0].String(),
Roles: RoleNames{RoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
Roles: RoleNames{ScopedRoleOrgMember(orgIDs[0]), "auditor", 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: RoleNames{RoleOrgMember(orgIDs[0]), RoleOrgAdmin(orgIDs[0]), RoleMember()},
Roles: RoleNames{ScopedRoleOrgMember(orgIDs[0]), 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: RoleNames{RoleOrgMember(orgIDs[0]), RoleOrgMember(orgIDs[1]), RoleMember()},
Roles: RoleNames{ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgMember(orgIDs[1]), RoleMember()},
},
ObjectType: ResourceWorkspace.Type,
Action: policy.ActionRead,
@@ -196,11 +196,11 @@ func TestFilter(t *testing.T) {
Actor: Subject{
ID: userIDs[0].String(),
Roles: RoleNames{
RoleOrgMember(orgIDs[0]), RoleOrgAdmin(orgIDs[0]),
RoleOrgMember(orgIDs[1]), RoleOrgAdmin(orgIDs[1]),
RoleOrgMember(orgIDs[2]), RoleOrgAdmin(orgIDs[2]),
RoleOrgMember(orgIDs[4]),
RoleOrgMember(orgIDs[5]),
ScopedRoleOrgMember(orgIDs[0]), ScopedRoleOrgAdmin(orgIDs[0]),
ScopedRoleOrgMember(orgIDs[1]), ScopedRoleOrgAdmin(orgIDs[1]),
ScopedRoleOrgMember(orgIDs[2]), ScopedRoleOrgAdmin(orgIDs[2]),
ScopedRoleOrgMember(orgIDs[4]),
ScopedRoleOrgMember(orgIDs[5]),
RoleMember(),
},
},
@@ -221,10 +221,10 @@ func TestFilter(t *testing.T) {
Actor: Subject{
ID: userIDs[0].String(),
Roles: RoleNames{
RoleOrgMember(orgIDs[0]),
RoleOrgMember(orgIDs[1]),
RoleOrgMember(orgIDs[2]),
RoleOrgMember(orgIDs[3]),
ScopedRoleOrgMember(orgIDs[0]),
ScopedRoleOrgMember(orgIDs[1]),
ScopedRoleOrgMember(orgIDs[2]),
ScopedRoleOrgMember(orgIDs[3]),
RoleMember(),
},
},
@@ -235,7 +235,7 @@ func TestFilter(t *testing.T) {
Name: "ScopeApplicationConnect",
Actor: Subject{
ID: userIDs[0].String(),
Roles: RoleNames{RoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
Roles: RoleNames{ScopedRoleOrgMember(orgIDs[0]), "auditor", RoleOwner(), RoleMember()},
},
ObjectType: ResourceWorkspace.Type,
Action: policy.ActionRead,
@@ -297,7 +297,7 @@ func TestAuthorizeDomain(t *testing.T) {
Groups: []string{allUsersGroup},
Roles: Roles{
must(RoleByName(RoleMember())),
must(RoleByName(RoleOrgMember(defOrg))),
must(RoleByName(ScopedRoleOrgMember(defOrg))),
},
}
@@ -435,7 +435,7 @@ func TestAuthorizeDomain(t *testing.T) {
ID: "me",
Scope: must(ExpandScope(ScopeAll)),
Roles: Roles{
must(RoleByName(RoleOrgAdmin(defOrg))),
must(RoleByName(ScopedRoleOrgAdmin(defOrg))),
must(RoleByName(RoleMember())),
},
}
@@ -507,7 +507,7 @@ func TestAuthorizeDomain(t *testing.T) {
ID: "me",
Scope: must(ExpandScope(ScopeApplicationConnect)),
Roles: Roles{
must(RoleByName(RoleOrgMember(defOrg))),
must(RoleByName(ScopedRoleOrgMember(defOrg))),
must(RoleByName(RoleMember())),
},
}
@@ -770,7 +770,7 @@ func TestAuthorizeLevels(t *testing.T) {
},
},
},
must(RoleByName(RoleOrgAdmin(defOrg))),
must(RoleByName(ScopedRoleOrgAdmin(defOrg))),
{
Name: "user-deny-all",
// List out deny permissions explicitly
@@ -856,7 +856,7 @@ func TestAuthorizeScope(t *testing.T) {
ID: "me",
Roles: Roles{
must(RoleByName(RoleMember())),
must(RoleByName(RoleOrgMember(defOrg))),
must(RoleByName(ScopedRoleOrgMember(defOrg))),
},
Scope: must(ExpandScope(ScopeApplicationConnect)),
}
@@ -892,7 +892,7 @@ func TestAuthorizeScope(t *testing.T) {
ID: "me",
Roles: Roles{
must(RoleByName(RoleMember())),
must(RoleByName(RoleOrgMember(defOrg))),
must(RoleByName(ScopedRoleOrgMember(defOrg))),
},
Scope: Scope{
Role: Role{
@@ -981,7 +981,7 @@ func TestAuthorizeScope(t *testing.T) {
ID: "me",
Roles: Roles{
must(RoleByName(RoleMember())),
must(RoleByName(RoleOrgMember(defOrg))),
must(RoleByName(ScopedRoleOrgMember(defOrg))),
},
Scope: Scope{
Role: Role{
+10 -10
View File
@@ -49,7 +49,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Name: "Admin",
Actor: rbac.Subject{
// Give some extra roles that an admin might have
Roles: rbac.RoleNames{rbac.RoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
Roles: rbac.RoleNames{rbac.ScopedRoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
ID: user.String(),
Scope: rbac.ScopeAll,
Groups: noiseGroups,
@@ -58,7 +58,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
{
Name: "OrgAdmin",
Actor: rbac.Subject{
Roles: rbac.RoleNames{rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgAdmin(orgs[0]), rbac.RoleMember()},
Roles: rbac.RoleNames{rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgAdmin(orgs[0]), rbac.RoleMember()},
ID: user.String(),
Scope: rbac.ScopeAll,
Groups: noiseGroups,
@@ -68,7 +68,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Name: "OrgMember",
Actor: rbac.Subject{
// Member of 2 orgs
Roles: rbac.RoleNames{rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgMember(orgs[1]), rbac.RoleMember()},
Roles: rbac.RoleNames{rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgMember(orgs[1]), rbac.RoleMember()},
ID: user.String(),
Scope: rbac.ScopeAll,
Groups: noiseGroups,
@@ -79,9 +79,9 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Actor: rbac.Subject{
// Admin of many orgs
Roles: rbac.RoleNames{
rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgAdmin(orgs[0]),
rbac.RoleOrgMember(orgs[1]), rbac.RoleOrgAdmin(orgs[1]),
rbac.RoleOrgMember(orgs[2]), rbac.RoleOrgAdmin(orgs[2]),
rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgAdmin(orgs[0]),
rbac.ScopedRoleOrgMember(orgs[1]), rbac.ScopedRoleOrgAdmin(orgs[1]),
rbac.ScopedRoleOrgMember(orgs[2]), rbac.ScopedRoleOrgAdmin(orgs[2]),
rbac.RoleMember(),
},
ID: user.String(),
@@ -94,9 +94,9 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Actor: rbac.Subject{
// Admin of many orgs
Roles: rbac.RoleNames{
rbac.RoleOrgMember(orgs[0]), rbac.RoleOrgAdmin(orgs[0]),
rbac.RoleOrgMember(orgs[1]), rbac.RoleOrgAdmin(orgs[1]),
rbac.RoleOrgMember(orgs[2]), rbac.RoleOrgAdmin(orgs[2]),
rbac.ScopedRoleOrgMember(orgs[0]), rbac.ScopedRoleOrgAdmin(orgs[0]),
rbac.ScopedRoleOrgMember(orgs[1]), rbac.ScopedRoleOrgAdmin(orgs[1]),
rbac.ScopedRoleOrgMember(orgs[2]), rbac.ScopedRoleOrgAdmin(orgs[2]),
rbac.RoleMember(),
},
ID: user.String(),
@@ -108,7 +108,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U
Name: "AdminWithScope",
Actor: rbac.Subject{
// Give some extra roles that an admin might have
Roles: rbac.RoleNames{rbac.RoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
Roles: rbac.RoleNames{rbac.ScopedRoleOrgMember(orgs[0]), "auditor", rbac.RoleOwner(), rbac.RoleMember()},
ID: user.String(),
Scope: rbac.ScopeApplicationConnect,
Groups: noiseGroups,
+16 -2
View File
@@ -70,11 +70,25 @@ func RoleMember() string {
return RoleName(member, "")
}
func RoleOrgAdmin(organizationID uuid.UUID) string {
func RoleOrgAdmin() string {
return orgAdmin
}
func RoleOrgMember() string {
return orgMember
}
// ScopedRoleOrgAdmin is the org role with the organization ID
// Deprecated This was used before organization scope was included as a
// field in all user facing APIs. Usage of 'ScopedRoleOrgAdmin()' is preferred.
func ScopedRoleOrgAdmin(organizationID uuid.UUID) string {
return RoleName(orgAdmin, organizationID.String())
}
func RoleOrgMember(organizationID uuid.UUID) string {
// ScopedRoleOrgMember is the org role with the organization ID
// Deprecated This was used before organization scope was included as a
// field in all user facing APIs. Usage of 'ScopedRoleOrgMember()' is preferred.
func ScopedRoleOrgMember(organizationID uuid.UUID) string {
return RoleName(orgMember, organizationID.String())
}
+2 -2
View File
@@ -20,7 +20,7 @@ import (
// A possible large improvement would be to implement the ast.Value interface directly.
func BenchmarkRBACValueAllocation(b *testing.B) {
actor := Subject{
Roles: RoleNames{RoleOrgMember(uuid.New()), RoleOrgAdmin(uuid.New()), RoleMember()},
Roles: RoleNames{ScopedRoleOrgMember(uuid.New()), ScopedRoleOrgAdmin(uuid.New()), RoleMember()},
ID: uuid.NewString(),
Scope: ScopeAll,
Groups: []string{uuid.NewString(), uuid.NewString(), uuid.NewString()},
@@ -73,7 +73,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 := RoleNames{RoleOrgMember(uuid.New()), RoleOrgAdmin(uuid.New()), RoleMember()}.Expand()
roles, err := RoleNames{ScopedRoleOrgMember(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
+6 -6
View File
@@ -99,13 +99,13 @@ func TestRolePermissions(t *testing.T) {
// Subjects to user
memberMe := authSubject{Name: "member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleNames{rbac.RoleMember()}}}
orgMemberMe := authSubject{Name: "org_member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOrgMember(orgID)}}}
orgMemberMe := authSubject{Name: "org_member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID)}}}
owner := authSubject{Name: "owner", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOwner()}}}
orgAdmin := authSubject{Name: "org_admin", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOrgMember(orgID), rbac.RoleOrgAdmin(orgID)}}}
orgAdmin := authSubject{Name: "org_admin", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.ScopedRoleOrgMember(orgID), rbac.ScopedRoleOrgAdmin(orgID)}}}
otherOrgMember := authSubject{Name: "org_member_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOrgMember(otherOrg)}}}
otherOrgAdmin := authSubject{Name: "org_admin_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOrgMember(otherOrg), rbac.RoleOrgAdmin(otherOrg)}}}
otherOrgMember := authSubject{Name: "org_member_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg)}}}
otherOrgAdmin := authSubject{Name: "org_admin_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.ScopedRoleOrgMember(otherOrg), rbac.ScopedRoleOrgAdmin(otherOrg)}}}
templateAdmin := authSubject{Name: "template-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleTemplateAdmin()}}}
userAdmin := authSubject{Name: "user-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleUserAdmin()}}}
@@ -636,12 +636,12 @@ func TestIsOrgRole(t *testing.T) {
// Org roles
{
RoleName: rbac.RoleOrgAdmin(randomUUID),
RoleName: rbac.ScopedRoleOrgAdmin(randomUUID),
OrgRole: true,
OrgID: randomUUID.String(),
},
{
RoleName: rbac.RoleOrgMember(randomUUID),
RoleName: rbac.ScopedRoleOrgMember(randomUUID),
OrgRole: true,
OrgID: randomUUID.String(),
},
+4 -4
View File
@@ -26,7 +26,7 @@ func TestListRoles(t *testing.T) {
// Create owner, member, and org admin
owner := coderdtest.CreateFirstUser(t, client)
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
orgAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleOrgAdmin(owner.OrganizationID))
orgAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID))
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
t.Cleanup(cancel)
@@ -64,7 +64,7 @@ func TestListRoles(t *testing.T) {
return member.ListOrganizationRoles(ctx, owner.OrganizationID)
},
ExpectedRoles: convertRoles(map[string]bool{
rbac.RoleOrgAdmin(owner.OrganizationID): false,
rbac.ScopedRoleOrgAdmin(owner.OrganizationID): false,
}),
},
{
@@ -93,7 +93,7 @@ func TestListRoles(t *testing.T) {
return orgAdmin.ListOrganizationRoles(ctx, owner.OrganizationID)
},
ExpectedRoles: convertRoles(map[string]bool{
rbac.RoleOrgAdmin(owner.OrganizationID): true,
rbac.ScopedRoleOrgAdmin(owner.OrganizationID): true,
}),
},
{
@@ -122,7 +122,7 @@ func TestListRoles(t *testing.T) {
return client.ListOrganizationRoles(ctx, owner.OrganizationID)
},
ExpectedRoles: convertRoles(map[string]bool{
rbac.RoleOrgAdmin(owner.OrganizationID): true,
rbac.ScopedRoleOrgAdmin(owner.OrganizationID): true,
}),
},
}
+6 -6
View File
@@ -962,12 +962,12 @@ func TestGrantSiteRoles(t *testing.T) {
admin := coderdtest.New(t, nil)
first := coderdtest.CreateFirstUser(t, admin)
member, _ := coderdtest.CreateAnotherUser(t, admin, first.OrganizationID)
orgAdmin, _ := coderdtest.CreateAnotherUser(t, admin, first.OrganizationID, rbac.RoleOrgAdmin(first.OrganizationID))
orgAdmin, _ := coderdtest.CreateAnotherUser(t, admin, first.OrganizationID, rbac.ScopedRoleOrgAdmin(first.OrganizationID))
randOrg, err := admin.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
Name: "random",
})
require.NoError(t, err)
_, randOrgUser := coderdtest.CreateAnotherUser(t, admin, randOrg.ID, rbac.RoleOrgAdmin(randOrg.ID))
_, randOrgUser := coderdtest.CreateAnotherUser(t, admin, randOrg.ID, rbac.ScopedRoleOrgAdmin(randOrg.ID))
userAdmin, _ := coderdtest.CreateAnotherUser(t, admin, first.OrganizationID, rbac.RoleUserAdmin())
const newUser = "newUser"
@@ -986,7 +986,7 @@ func TestGrantSiteRoles(t *testing.T) {
Name: "OrgRoleInSite",
Client: admin,
AssignToUser: codersdk.Me,
Roles: []string{rbac.RoleOrgAdmin(first.OrganizationID)},
Roles: []string{rbac.RoleOrgAdmin()},
Error: true,
StatusCode: http.StatusBadRequest,
},
@@ -1029,7 +1029,7 @@ func TestGrantSiteRoles(t *testing.T) {
Client: orgAdmin,
OrgID: randOrg.ID,
AssignToUser: randOrgUser.ID.String(),
Roles: []string{rbac.RoleOrgMember(randOrg.ID)},
Roles: []string{rbac.RoleOrgMember()},
Error: true,
StatusCode: http.StatusNotFound,
},
@@ -1047,9 +1047,9 @@ func TestGrantSiteRoles(t *testing.T) {
Client: orgAdmin,
OrgID: first.OrganizationID,
AssignToUser: newUser,
Roles: []string{rbac.RoleOrgAdmin(first.OrganizationID)},
Roles: []string{rbac.RoleOrgAdmin()},
ExpectedRoles: []string{
rbac.RoleOrgAdmin(first.OrganizationID),
rbac.RoleOrgAdmin(),
},
Error: false,
},
+4 -3
View File
@@ -14,8 +14,9 @@ import (
// and it would require extra db calls to fetch this information. The UI does
// not need it, so most api calls will use this structure that omits information.
type SlimRole struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
OrganizationID string `json:"organization_id,omitempty"`
}
type AssignableRoles struct {
@@ -36,7 +37,7 @@ type Permission struct {
// Role is a longer form of SlimRole used to edit custom roles.
type Role struct {
Name string `json:"name" table:"name,default_sort" validate:"username"`
OrganizationID string `json:"organization_id" table:"organization_id" format:"uuid"`
OrganizationID string `json:"organization_id,omitempty" table:"organization_id" format:"uuid"`
DisplayName string `json:"display_name" table:"display_name"`
SitePermissions []Permission `json:"site_permissions" table:"site_permissions"`
// OrganizationPermissions are specific for the organization in the field 'OrganizationID' above.
+2 -1
View File
@@ -68,7 +68,8 @@ curl -X GET http://coder-server:8080/api/v2/audit?limit=0 \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
+5 -2
View File
@@ -1600,7 +1600,8 @@ curl -X PATCH http://coder-server:8080/api/v2/scim/v2/Users/{id} \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -1655,7 +1656,8 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/acl \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -1690,6 +1692,7 @@ Status Code **200**
| `» roles` | array | false | | |
| `»» display_name` | string | false | | |
| `»» name` | string | false | | |
| `»» organization_id` | string | false | | |
| `» status` | [codersdk.UserStatus](schemas.md#codersdkuserstatus) | false | | |
| `» theme_preference` | string | false | | |
| `» username` | string | true | | |
+2 -1
View File
@@ -289,7 +289,8 @@ curl -X PUT http://coder-server:8080/api/v2/organizations/{organization}/members
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"updated_at": "2019-08-24T14:15:22Z",
+19 -11
View File
@@ -966,7 +966,8 @@
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -1045,7 +1046,8 @@
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -2972,7 +2974,8 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -3616,7 +3619,8 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"updated_at": "2019-08-24T14:15:22Z",
@@ -4452,16 +4456,18 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
```json
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| -------------- | ------ | -------- | ------------ | ----------- |
| `display_name` | string | false | | |
| `name` | string | false | | |
| Name | Type | Required | Restrictions | Description |
| ----------------- | ------ | -------- | ------------ | ----------- |
| `display_name` | string | false | | |
| `name` | string | false | | |
| `organization_id` | string | false | | |
## codersdk.SupportConfig
@@ -5002,7 +5008,8 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -5607,7 +5614,8 @@ If the schedule is empty, the user will be updated to use the default schedule.|
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
+20 -10
View File
@@ -42,7 +42,8 @@ curl -X GET http://coder-server:8080/api/v2/users \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -111,7 +112,8 @@ curl -X POST http://coder-server:8080/api/v2/users \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -381,7 +383,8 @@ curl -X GET http://coder-server:8080/api/v2/users/{user} \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -434,7 +437,8 @@ curl -X DELETE http://coder-server:8080/api/v2/users/{user} \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -497,7 +501,8 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/appearance \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -1148,7 +1153,8 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/profile \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -1201,7 +1207,8 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/roles \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -1264,7 +1271,8 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/roles \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -1317,7 +1325,8 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/status/activate \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
@@ -1370,7 +1379,8 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/status/suspend \
"roles": [
{
"display_name": "string",
"name": "string"
"name": "string",
"organization_id": "string"
}
],
"status": "active",
+1 -1
View File
@@ -35,7 +35,7 @@ func TestCheckACLPermissions(t *testing.T) {
memberClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID)
memberUser, err := memberClient.User(ctx, codersdk.Me)
require.NoError(t, err)
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID, rbac.RoleOrgAdmin(adminUser.OrganizationID))
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID, rbac.ScopedRoleOrgAdmin(adminUser.OrganizationID))
orgAdminUser, err := orgAdminClient.User(ctx, codersdk.Me)
require.NoError(t, err)
+1 -1
View File
@@ -197,7 +197,7 @@ func TestProvisionerDaemonServe(t *testing.T) {
codersdk.FeatureExternalProvisionerDaemons: 1,
},
}})
another, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.RoleOrgAdmin(user.OrganizationID))
another, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.ScopedRoleOrgAdmin(user.OrganizationID))
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
_, err := another.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{
+3 -3
View File
@@ -1639,9 +1639,9 @@ func TestTemplateAccess(t *testing.T) {
newOrg, err := ownerClient.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{Name: orgName})
require.NoError(t, err, "failed to create org")
adminCli, adminUsr := coderdtest.CreateAnotherUser(t, ownerClient, newOrg.ID, rbac.RoleOrgAdmin(newOrg.ID))
groupMemCli, groupMemUsr := coderdtest.CreateAnotherUser(t, ownerClient, newOrg.ID, rbac.RoleOrgMember(newOrg.ID))
memberCli, memberUsr := coderdtest.CreateAnotherUser(t, ownerClient, newOrg.ID, rbac.RoleOrgMember(newOrg.ID))
adminCli, adminUsr := coderdtest.CreateAnotherUser(t, ownerClient, newOrg.ID, rbac.ScopedRoleOrgAdmin(newOrg.ID))
groupMemCli, groupMemUsr := coderdtest.CreateAnotherUser(t, ownerClient, newOrg.ID, rbac.ScopedRoleOrgMember(newOrg.ID))
memberCli, memberUsr := coderdtest.CreateAnotherUser(t, ownerClient, newOrg.ID, rbac.ScopedRoleOrgMember(newOrg.ID))
// Make group
group, err := adminCli.CreateGroup(ctx, newOrg.ID, codersdk.CreateGroupRequest{
+2 -1
View File
@@ -977,7 +977,7 @@ export interface Response {
// From codersdk/roles.go
export interface Role {
readonly name: string;
readonly organization_id: string;
readonly organization_id?: string;
readonly display_name: string;
readonly site_permissions: readonly Permission[];
readonly organization_permissions: readonly Permission[];
@@ -1030,6 +1030,7 @@ export interface SessionLifetime {
export interface SlimRole {
readonly name: string;
readonly display_name: string;
readonly organization_id?: string;
}
// From codersdk/deployment.go