Skip to content

Commit 35211e2

Browse files
authored
feat: Add user roles, but do not yet enforce them (#1200)
* chore: Rework roles to be expandable by name alone
1 parent ba4c3ce commit 35211e2

26 files changed

+1150
-232
lines changed

coderd/audit/table.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ var AuditableResources = auditMap(map[any]map[string]Action{
4242
"created_at": ActionIgnore, // Never changes.
4343
"updated_at": ActionIgnore, // Changes, but is implicit and not helpful in a diff.
4444
"status": ActionTrack, // A user can update another user status
45+
"rbac_roles": ActionTrack, // A user's roles are mutable
4546
},
4647
&database.Workspace{}: {
4748
"id": ActionIgnore, // Never changes.

coderd/coderd.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ func New(options *Options) (http.Handler, func()) {
120120
r.Get("/", api.workspacesByOwner)
121121
})
122122
})
123+
r.Route("/members", func(r chi.Router) {
124+
r.Route("/{user}", func(r chi.Router) {
125+
r.Use(
126+
httpmw.ExtractUserParam(options.Database),
127+
)
128+
r.Put("/roles", api.putMemberRoles)
129+
})
130+
})
123131
})
124132
r.Route("/parameters/{scope}/{id}", func(r chi.Router) {
125133
r.Use(apiKeyMiddleware)
@@ -183,6 +191,10 @@ func New(options *Options) (http.Handler, func()) {
183191
r.Get("/", api.userByName)
184192
r.Put("/profile", api.putUserProfile)
185193
r.Put("/suspend", api.putUserSuspend)
194+
// TODO: @emyrk Might want to move these to a /roles group instead of /user.
195+
// As we include more roles like org roles, it makes less sense to scope these here.
196+
r.Put("/roles", api.putUserRoles)
197+
r.Get("/roles", api.userRoles)
186198
r.Get("/organizations", api.organizationsByUser)
187199
r.Post("/organizations", api.postOrganizationsByUser)
188200
r.Post("/keys", api.postAPIKey)

coderd/database/databasefake/databasefake.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,43 @@ func (q *fakeQuerier) GetOrganizationIDsByMemberIDs(_ context.Context, ids []uui
743743
return getOrganizationIDsByMemberIDRows, nil
744744
}
745745

746+
func (q *fakeQuerier) GetOrganizationMembershipsByUserID(_ context.Context, userID uuid.UUID) ([]database.OrganizationMember, error) {
747+
q.mutex.RLock()
748+
defer q.mutex.RUnlock()
749+
750+
var memberships []database.OrganizationMember
751+
for _, organizationMember := range q.organizationMembers {
752+
mem := organizationMember
753+
if mem.UserID != userID {
754+
continue
755+
}
756+
memberships = append(memberships, mem)
757+
}
758+
return memberships, nil
759+
}
760+
761+
func (q *fakeQuerier) UpdateMemberRoles(_ context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) {
762+
for i, mem := range q.organizationMembers {
763+
if mem.UserID == arg.UserID && mem.OrganizationID == arg.OrgID {
764+
uniqueRoles := make([]string, 0, len(arg.GrantedRoles))
765+
exist := make(map[string]struct{})
766+
for _, r := range arg.GrantedRoles {
767+
if _, ok := exist[r]; ok {
768+
continue
769+
}
770+
exist[r] = struct{}{}
771+
uniqueRoles = append(uniqueRoles, r)
772+
}
773+
sort.Strings(uniqueRoles)
774+
775+
mem.Roles = uniqueRoles
776+
q.organizationMembers[i] = mem
777+
return mem, nil
778+
}
779+
}
780+
return database.OrganizationMember{}, sql.ErrNoRows
781+
}
782+
746783
func (q *fakeQuerier) GetProvisionerDaemons(_ context.Context) ([]database.ProvisionerDaemon, error) {
747784
q.mutex.RLock()
748785
defer q.mutex.RUnlock()
@@ -1173,11 +1210,42 @@ func (q *fakeQuerier) InsertUser(_ context.Context, arg database.InsertUserParam
11731210
UpdatedAt: arg.UpdatedAt,
11741211
Username: arg.Username,
11751212
Status: database.UserStatusActive,
1213+
RBACRoles: arg.RBACRoles,
11761214
}
11771215
q.users = append(q.users, user)
11781216
return user, nil
11791217
}
11801218

1219+
func (q *fakeQuerier) UpdateUserRoles(_ context.Context, arg database.UpdateUserRolesParams) (database.User, error) {
1220+
q.mutex.Lock()
1221+
defer q.mutex.Unlock()
1222+
1223+
for index, user := range q.users {
1224+
if user.ID != arg.ID {
1225+
continue
1226+
}
1227+
1228+
// Set new roles
1229+
user.RBACRoles = arg.GrantedRoles
1230+
// Remove duplicates and sort
1231+
uniqueRoles := make([]string, 0, len(user.RBACRoles))
1232+
exist := make(map[string]struct{})
1233+
for _, r := range user.RBACRoles {
1234+
if _, ok := exist[r]; ok {
1235+
continue
1236+
}
1237+
exist[r] = struct{}{}
1238+
uniqueRoles = append(uniqueRoles, r)
1239+
}
1240+
sort.Strings(uniqueRoles)
1241+
user.RBACRoles = uniqueRoles
1242+
1243+
q.users[index] = user
1244+
return user, nil
1245+
}
1246+
return database.User{}, sql.ErrNoRows
1247+
}
1248+
11811249
func (q *fakeQuerier) UpdateUserProfile(_ context.Context, arg database.UpdateUserProfileParams) (database.User, error) {
11821250
q.mutex.Lock()
11831251
defer q.mutex.Unlock()

coderd/database/dump.sql

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE ONLY users
2+
DROP COLUMN IF EXISTS rbac_roles;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
ALTER TABLE ONLY users
2+
ADD COLUMN IF NOT EXISTS rbac_roles text[] DEFAULT '{}' NOT NULL;
3+
4+
-- All users are site members. So give them the standard role.
5+
-- Also give them membership to the first org we retrieve. We should only have
6+
-- 1 organization at this point in the product.
7+
UPDATE
8+
users
9+
SET
10+
rbac_roles = ARRAY ['member', 'organization-member:' || (SELECT id FROM organizations LIMIT 1)];
11+
12+
-- Give the first user created the admin role
13+
UPDATE
14+
users
15+
SET
16+
rbac_roles = rbac_roles || ARRAY ['admin']
17+
WHERE
18+
id = (SELECT id FROM users ORDER BY created_at ASC LIMIT 1)

coderd/database/models.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/querier.go

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

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