Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 501e581

Browse files
committed
feat: Member roles are implied and never exlpicitly added
1 parent 26a2a16 commit 501e581

File tree

14 files changed

+56
-82
lines changed

14 files changed

+56
-82
lines changed

coderd/coderdtest/coderdtest.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ func CreateAnotherUser(t *testing.T, client *codersdk.Client, organizationID uui
281281
organizationID, err := uuid.Parse(orgID)
282282
require.NoError(t, err, fmt.Sprintf("parse org id %q", orgID))
283283
_, err = client.UpdateOrganizationMemberRoles(context.Background(), organizationID, user.ID.String(),
284-
codersdk.UpdateRoles{Roles: append(roles, rbac.RoleOrgMember(organizationID))})
284+
codersdk.UpdateRoles{Roles: roles})
285285
require.NoError(t, err, "update org membership roles")
286286
}
287287
}

coderd/database/databasefake/databasefake.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ func (q *fakeQuerier) GetAllUserRoles(_ context.Context, userID uuid.UUID) (data
286286
if u.ID == userID {
287287
u := u
288288
roles = append(roles, u.RBACRoles...)
289+
roles = append(roles, "member")
289290
user = &u
290291
break
291292
}
@@ -294,6 +295,7 @@ func (q *fakeQuerier) GetAllUserRoles(_ context.Context, userID uuid.UUID) (data
294295
for _, mem := range q.organizationMembers {
295296
if mem.UserID == userID {
296297
roles = append(roles, mem.Roles...)
298+
roles = append(roles, "organization-member:"+mem.OrganizationID.String())
297299
}
298300
}
299301

coderd/database/queries/users.sql

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,16 @@ WHERE
136136

137137
-- name: GetAllUserRoles :one
138138
SELECT
139-
-- username is returned just to help for logging purposes
140-
-- status is used to enforce 'suspended' users, as all roles are ignored
141-
-- when suspended.
142-
id, username, status, array_cat(users.rbac_roles, organization_members.roles) :: text[] AS roles
139+
-- username is returned just to help for logging purposes
140+
-- status is used to enforce 'suspended' users, as all roles are ignored
141+
-- when suspended.
142+
id, username, status,
143+
array_cat(
144+
-- All users are members
145+
array_append(users.rbac_roles, 'member'),
146+
-- All org_members get the org-member role for their orgs
147+
array_append(organization_members.roles, 'organization-member:'||organization_members.organization_id::text)) :: text[]
148+
AS roles
143149
FROM
144150
users
145151
LEFT JOIN organization_members

coderd/httpmw/apikey.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ func APIKey(r *http.Request) database.APIKey {
3131
return apiKey
3232
}
3333

34+
// User roles are the 'subject' field of Authorize()
35+
type userRolesKey struct{}
36+
37+
// UserRoles returns the API key from the ExtractUserRoles handler.
38+
func UserRoles(r *http.Request) database.GetAllUserRolesRow {
39+
apiKey, ok := r.Context().Value(userRolesKey{}).(database.GetAllUserRolesRow)
40+
if !ok {
41+
panic("developer error: user roles middleware not provided")
42+
}
43+
return apiKey
44+
}
45+
3446
// OAuth2Configs is a collection of configurations for OAuth-based authentication.
3547
// This should be extended to support other authentication types in the future.
3648
type OAuth2Configs struct {

coderd/httpmw/authorize.go

Lines changed: 0 additions & 40 deletions
This file was deleted.

coderd/httpmw/authorize_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,23 @@ func TestExtractUserRoles(t *testing.T) {
3131
{
3232
Name: "Member",
3333
AddUser: func(db database.Store) (database.User, []string, string) {
34-
roles := []string{rbac.RoleMember()}
34+
roles := []string{}
3535
user, token := addUser(t, db, roles...)
36-
return user, roles, token
36+
return user, append(roles, rbac.RoleMember()), token
3737
},
3838
},
3939
{
4040
Name: "Admin",
4141
AddUser: func(db database.Store) (database.User, []string, string) {
42-
roles := []string{rbac.RoleMember(), rbac.RoleAdmin()}
42+
roles := []string{rbac.RoleAdmin()}
4343
user, token := addUser(t, db, roles...)
44-
return user, roles, token
44+
return user, append(roles, rbac.RoleMember()), token
4545
},
4646
},
4747
{
4848
Name: "OrgMember",
4949
AddUser: func(db database.Store) (database.User, []string, string) {
50-
roles := []string{rbac.RoleMember()}
50+
roles := []string{}
5151
user, token := addUser(t, db, roles...)
5252
org, err := db.InsertOrganization(context.Background(), database.InsertOrganizationParams{
5353
ID: uuid.New(),
@@ -58,7 +58,7 @@ func TestExtractUserRoles(t *testing.T) {
5858
})
5959
require.NoError(t, err)
6060

61-
orgRoles := []string{rbac.RoleOrgMember(org.ID)}
61+
orgRoles := []string{}
6262
_, err = db.InsertOrganizationMember(context.Background(), database.InsertOrganizationMemberParams{
6363
OrganizationID: org.ID,
6464
UserID: user.ID,
@@ -67,7 +67,7 @@ func TestExtractUserRoles(t *testing.T) {
6767
Roles: orgRoles,
6868
})
6969
require.NoError(t, err)
70-
return user, append(roles, orgRoles...), token
70+
return user, append(roles, append(orgRoles, rbac.RoleMember(), rbac.RoleOrgMember(org.ID))...), token
7171
},
7272
},
7373
}

coderd/members.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ func (api *API) putMemberRoles(rw http.ResponseWriter, r *http.Request) {
2626
return
2727
}
2828

29-
added, removed := rbac.ChangeRoleSet(member.Roles, params.Roles)
29+
// The org-member role is always implied.
30+
impliedTypes := append(params.Roles, rbac.RoleOrgMember(organization.ID))
31+
added, removed := rbac.ChangeRoleSet(member.Roles, impliedTypes)
3032
for _, roleName := range added {
3133
// Assigning a role requires the create permission.
3234
if !api.Authorize(rw, r, rbac.ActionCreate, rbac.ResourceOrgRoleAssignment.WithID(roleName).InOrg(organization.ID)) {

coderd/organizations.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,6 @@ func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) {
7373
CreatedAt: database.Now(),
7474
UpdatedAt: database.Now(),
7575
Roles: []string{
76-
// Also assign member role incase they get demoted from admin
77-
rbac.RoleOrgMember(organization.ID),
7876
rbac.RoleOrgAdmin(organization.ID),
7977
},
8078
})

coderd/rbac/builtin.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ var (
6363
member: func(_ string) Role {
6464
return Role{
6565
Name: member,
66-
DisplayName: "Member",
66+
DisplayName: "",
6767
Site: permissions(map[Object][]Action{
6868
// All users can read all other users and know they exist.
6969
ResourceUser: {ActionRead},
@@ -116,7 +116,7 @@ var (
116116
orgMember: func(organizationID string) Role {
117117
return Role{
118118
Name: roleName(orgMember, organizationID),
119-
DisplayName: "Organization Member",
119+
DisplayName: "",
120120
Org: map[string][]Permission{
121121
organizationID: {
122122
{

coderd/rbac/role.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ type Permission struct {
1717
// Users of this package should instead **only** use the role names, and
1818
// this package will expand the role names into their json payloads.
1919
type Role struct {
20-
Name string `json:"name"`
20+
Name string `json:"name"`
21+
// DisplayName is used for UI purposes. If the role has no display name,
22+
// that means the UI should never display it.
2123
DisplayName string `json:"display_name"`
2224
Site []Permission `json:"site"`
2325
// Org is a map of orgid to permissions. We represent orgid as a string.

coderd/users.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
8888
// and add some rbac bypass when calling api functions this way??
8989
// Add the admin role to this first user.
9090
_, err = api.Database.UpdateUserRoles(r.Context(), database.UpdateUserRolesParams{
91-
GrantedRoles: []string{rbac.RoleAdmin(), rbac.RoleMember()},
91+
GrantedRoles: []string{rbac.RoleAdmin()},
9292
ID: user.ID,
9393
})
9494
if err != nil {
@@ -480,7 +480,9 @@ func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) {
480480
return
481481
}
482482

483-
added, removed := rbac.ChangeRoleSet(roles.Roles, params.Roles)
483+
// The member role is always implied.
484+
impliedTypes := append(params.Roles, rbac.RoleMember())
485+
added, removed := rbac.ChangeRoleSet(roles.Roles, impliedTypes)
484486
for _, roleName := range added {
485487
// Assigning a role requires the create permission.
486488
if !api.Authorize(rw, r, rbac.ActionCreate, rbac.ResourceRoleAssignment.WithID(roleName)) {
@@ -764,8 +766,6 @@ func (api *API) createUser(ctx context.Context, req codersdk.CreateUserRequest)
764766
req.OrganizationID = organization.ID
765767
orgRoles = append(orgRoles, rbac.RoleOrgAdmin(req.OrganizationID))
766768
}
767-
// Always also be a member.
768-
orgRoles = append(orgRoles, rbac.RoleOrgMember(req.OrganizationID))
769769

770770
params := database.InsertUserParams{
771771
ID: uuid.New(),
@@ -774,7 +774,7 @@ func (api *API) createUser(ctx context.Context, req codersdk.CreateUserRequest)
774774
CreatedAt: database.Now(),
775775
UpdatedAt: database.Now(),
776776
// All new users are defaulted to members of the site.
777-
RBACRoles: []string{rbac.RoleMember()},
777+
RBACRoles: []string{},
778778
}
779779
// If a user signs up with OAuth, they can have no password!
780780
if req.Password != "" {

coderd/users_test.go

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -416,43 +416,43 @@ func TestGrantRoles(t *testing.T) {
416416
require.NoError(t, err, "member user")
417417

418418
_, err = admin.UpdateUserRoles(ctx, codersdk.Me, codersdk.UpdateRoles{
419-
Roles: []string{rbac.RoleOrgMember(first.OrganizationID)},
419+
Roles: []string{rbac.RoleOrgAdmin(first.OrganizationID)},
420420
})
421421
require.Error(t, err, "org role in site")
422422
requireStatusCode(t, err, http.StatusBadRequest)
423423

424424
_, err = admin.UpdateUserRoles(ctx, uuid.New().String(), codersdk.UpdateRoles{
425-
Roles: []string{rbac.RoleOrgMember(first.OrganizationID)},
425+
Roles: []string{rbac.RoleOrgAdmin(first.OrganizationID)},
426426
})
427427
require.Error(t, err, "user does not exist")
428428
requireStatusCode(t, err, http.StatusBadRequest)
429429

430430
_, err = admin.UpdateOrganizationMemberRoles(ctx, first.OrganizationID, codersdk.Me, codersdk.UpdateRoles{
431-
Roles: []string{rbac.RoleMember()},
431+
Roles: []string{rbac.RoleAdmin()},
432432
})
433433
require.Error(t, err, "site role in org")
434434
requireStatusCode(t, err, http.StatusBadRequest)
435435

436436
_, err = admin.UpdateOrganizationMemberRoles(ctx, uuid.New(), codersdk.Me, codersdk.UpdateRoles{
437-
Roles: []string{rbac.RoleMember()},
437+
Roles: []string{},
438438
})
439439
require.Error(t, err, "role in org without membership")
440440
requireStatusCode(t, err, http.StatusNotFound)
441441

442442
_, err = member.UpdateUserRoles(ctx, first.UserID.String(), codersdk.UpdateRoles{
443-
Roles: []string{rbac.RoleMember()},
443+
Roles: []string{},
444444
})
445445
require.Error(t, err, "member cannot change other's roles")
446446
requireStatusCode(t, err, http.StatusForbidden)
447447

448448
_, err = member.UpdateUserRoles(ctx, memberUser.ID.String(), codersdk.UpdateRoles{
449-
Roles: []string{rbac.RoleMember()},
449+
Roles: []string{},
450450
})
451451
require.Error(t, err, "member cannot change any roles")
452452
requireStatusCode(t, err, http.StatusForbidden)
453453

454454
_, err = member.UpdateOrganizationMemberRoles(ctx, first.OrganizationID, first.UserID.String(), codersdk.UpdateRoles{
455-
Roles: []string{rbac.RoleMember()},
455+
Roles: []string{},
456456
})
457457
require.Error(t, err, "member cannot change other's org roles")
458458
requireStatusCode(t, err, http.StatusForbidden)
@@ -468,11 +468,9 @@ func TestGrantRoles(t *testing.T) {
468468
require.NoError(t, err)
469469
require.ElementsMatch(t, roles.Roles, []string{
470470
rbac.RoleAdmin(),
471-
rbac.RoleMember(),
472471
}, "should be a member and admin")
473472

474473
require.ElementsMatch(t, roles.OrganizationRoles[first.OrganizationID], []string{
475-
rbac.RoleOrgMember(first.OrganizationID),
476474
rbac.RoleOrgAdmin(first.OrganizationID),
477475
}, "should be a member and admin")
478476
})
@@ -486,12 +484,10 @@ func TestGrantRoles(t *testing.T) {
486484
member := coderdtest.CreateAnotherUser(t, admin, first.OrganizationID)
487485
roles, err := member.GetUserRoles(ctx, codersdk.Me)
488486
require.NoError(t, err)
489-
require.ElementsMatch(t, roles.Roles, []string{
490-
rbac.RoleMember(),
491-
}, "should be a member and admin")
487+
require.ElementsMatch(t, roles.Roles, []string{}, "should be a member")
492488
require.ElementsMatch(t,
493489
roles.OrganizationRoles[first.OrganizationID],
494-
[]string{rbac.RoleOrgMember(first.OrganizationID)},
490+
[]string{},
495491
)
496492

497493
memberUser, err := member.User(ctx, codersdk.Me)
@@ -501,7 +497,6 @@ func TestGrantRoles(t *testing.T) {
501497
_, err = admin.UpdateUserRoles(ctx, memberUser.ID.String(), codersdk.UpdateRoles{
502498
Roles: []string{
503499
// Promote to site admin
504-
rbac.RoleMember(),
505500
rbac.RoleAdmin(),
506501
},
507502
})
@@ -511,7 +506,6 @@ func TestGrantRoles(t *testing.T) {
511506
_, err = member.UpdateOrganizationMemberRoles(ctx, first.OrganizationID, codersdk.Me, codersdk.UpdateRoles{
512507
Roles: []string{
513508
// Promote to org admin
514-
rbac.RoleOrgMember(first.OrganizationID),
515509
rbac.RoleOrgAdmin(first.OrganizationID),
516510
},
517511
})
@@ -520,12 +514,10 @@ func TestGrantRoles(t *testing.T) {
520514
roles, err = member.GetUserRoles(ctx, codersdk.Me)
521515
require.NoError(t, err)
522516
require.ElementsMatch(t, roles.Roles, []string{
523-
rbac.RoleMember(),
524517
rbac.RoleAdmin(),
525518
}, "should be a member and admin")
526519

527520
require.ElementsMatch(t, roles.OrganizationRoles[first.OrganizationID], []string{
528-
rbac.RoleOrgMember(first.OrganizationID),
529521
rbac.RoleOrgAdmin(first.OrganizationID),
530522
}, "should be a member and admin")
531523
})

coderd/workspaces_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func TestAdminViewAllWorkspaces(t *testing.T) {
8888

8989
// This other user is not in the first user's org. Since other is an admin, they can
9090
// still see the "first" user's workspace.
91-
other := coderdtest.CreateAnotherUser(t, client, otherOrg.ID, rbac.RoleAdmin(), rbac.RoleMember())
91+
other := coderdtest.CreateAnotherUser(t, client, otherOrg.ID, rbac.RoleAdmin())
9292
otherWorkspaces, err := other.Workspaces(context.Background(), codersdk.WorkspaceFilter{})
9393
require.NoError(t, err, "(other) fetch workspaces")
9494

codersdk/users.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ type UpdateUserPasswordRequest struct {
6969
}
7070

7171
type UpdateRoles struct {
72-
Roles []string `json:"roles" validate:"required"`
72+
Roles []string `json:"roles" validate:""`
7373
}
7474

7575
type UserRoles struct {

0 commit comments

Comments
 (0)