Skip to content

Commit cc87a0c

Browse files
authored
feat: Implied 'member' roles for site and organization (#1917)
* feat: Member roles are implied and never exlpicitly added * Rename "GetAllUserRoles" to "GetAuthorizationRoles" * feat: Add migration to remove implied roles * rename user auth role middleware
1 parent 2878346 commit cc87a0c

21 files changed

+131
-115
lines changed

coderd/authorize.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ import (
1313
)
1414

1515
func AuthorizeFilter[O rbac.Objecter](api *API, r *http.Request, action rbac.Action, objects []O) []O {
16-
roles := httpmw.UserRoles(r)
16+
roles := httpmw.AuthorizationUserRoles(r)
1717
return rbac.Filter(r.Context(), api.Authorizer, roles.ID.String(), roles.Roles, action, objects)
1818
}
1919

2020
func (api *API) Authorize(rw http.ResponseWriter, r *http.Request, action rbac.Action, object rbac.Objecter) bool {
21-
roles := httpmw.UserRoles(r)
21+
roles := httpmw.AuthorizationUserRoles(r)
2222
err := api.Authorizer.ByRoleName(r.Context(), roles.ID.String(), roles.Roles, action, object.RBACObject())
2323
if err != nil {
2424
httpapi.Write(rw, http.StatusForbidden, httpapi.Response{

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: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ func (q *fakeQuerier) GetUsersByIDs(_ context.Context, ids []uuid.UUID) ([]datab
276276
return users, nil
277277
}
278278

279-
func (q *fakeQuerier) GetAllUserRoles(_ context.Context, userID uuid.UUID) (database.GetAllUserRolesRow, error) {
279+
func (q *fakeQuerier) GetAuthorizationUserRoles(_ context.Context, userID uuid.UUID) (database.GetAuthorizationUserRolesRow, error) {
280280
q.mutex.RLock()
281281
defer q.mutex.RUnlock()
282282

@@ -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,14 +295,15 @@ 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

300302
if user == nil {
301-
return database.GetAllUserRolesRow{}, sql.ErrNoRows
303+
return database.GetAuthorizationUserRolesRow{}, sql.ErrNoRows
302304
}
303305

304-
return database.GetAllUserRolesRow{
306+
return database.GetAuthorizationUserRolesRow{
305307
ID: userID,
306308
Username: user.Username,
307309
Status: user.Status,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--- Remove the now implied 'member' role.
2+
UPDATE
3+
users
4+
SET
5+
rbac_roles = array_append(rbac_roles, 'member');
6+
7+
--- Remove the now implied 'organization-member' role.
8+
UPDATE
9+
organization_members
10+
SET
11+
roles = array_append(roles, 'organization-member:'||organization_id::text);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--- Remove the now implied 'member' role.
2+
UPDATE
3+
users
4+
SET
5+
rbac_roles = array_remove(rbac_roles, 'member');
6+
7+
--- Remove the now implied 'organization-member' role.
8+
UPDATE
9+
organization_members
10+
SET
11+
roles = array_remove(roles, 'organization-member:'||organization_id::text);

coderd/database/querier.go

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 17 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/users.sql

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,20 @@ WHERE
134134
id = $1 RETURNING *;
135135

136136

137-
-- name: GetAllUserRoles :one
137+
-- name: GetAuthorizationUserRoles :one
138+
-- This function returns roles for authorization purposes. Implied member roles
139+
-- are included.
138140
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
141+
-- username is returned just to help for logging purposes
142+
-- status is used to enforce 'suspended' users, as all roles are ignored
143+
-- when suspended.
144+
id, username, status,
145+
array_cat(
146+
-- All users are members
147+
array_append(users.rbac_roles, 'member'),
148+
-- All org_members get the org-member role for their orgs
149+
array_append(organization_members.roles, 'organization-member:'||organization_members.organization_id::text)) :: text[]
150+
AS roles
143151
FROM
144152
users
145153
LEFT JOIN organization_members

coderd/httpmw/apikey.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ 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+
// AuthorizationUserRoles returns the roles used for authorization.
38+
// Comes from the ExtractAPIKey handler.
39+
func AuthorizationUserRoles(r *http.Request) database.GetAuthorizationUserRolesRow {
40+
apiKey, ok := r.Context().Value(userRolesKey{}).(database.GetAuthorizationUserRolesRow)
41+
if !ok {
42+
panic("developer error: user roles middleware not provided")
43+
}
44+
return apiKey
45+
}
46+
3447
// OAuth2Configs is a collection of configurations for OAuth-based authentication.
3548
// This should be extended to support other authentication types in the future.
3649
type OAuth2Configs struct {
@@ -178,7 +191,7 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs) func(http.Handler) h
178191
// If the key is valid, we also fetch the user roles and status.
179192
// The roles are used for RBAC authorize checks, and the status
180193
// is to block 'suspended' users from accessing the platform.
181-
roles, err := db.GetAllUserRoles(r.Context(), key.UserID)
194+
roles, err := db.GetAuthorizationUserRoles(r.Context(), key.UserID)
182195
if err != nil {
183196
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
184197
Message: "roles not found",

coderd/httpmw/authorize.go

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

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy