Skip to content

Commit 2c33a82

Browse files
committed
feat: Split org/site roles
- UpdateRoles vs Grant/RemoveRoles - UpdateRoles and UpdateMemberRoles
1 parent 32f5dc0 commit 2c33a82

File tree

13 files changed

+357
-90
lines changed

13 files changed

+357
-90
lines changed

coderd/coderd.go

Lines changed: 8 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)

coderd/database/databasefake/databasefake.go

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,28 @@ func (q *fakeQuerier) GetOrganizationMembershipsByUserID(_ context.Context, user
747747
return memberships, nil
748748
}
749749

750+
func (q *fakeQuerier) UpdateMemberRoles(_ context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) {
751+
for i, mem := range q.organizationMembers {
752+
if mem.UserID == arg.UserID && mem.OrganizationID == arg.OrgID {
753+
uniqueRoles := make([]string, 0, len(arg.GrantedRoles))
754+
exist := make(map[string]struct{})
755+
for _, r := range arg.GrantedRoles {
756+
if _, ok := exist[r]; ok {
757+
continue
758+
}
759+
exist[r] = struct{}{}
760+
uniqueRoles = append(uniqueRoles, r)
761+
}
762+
sort.Strings(uniqueRoles)
763+
764+
mem.Roles = uniqueRoles
765+
q.organizationMembers[i] = mem
766+
return mem, nil
767+
}
768+
}
769+
return database.OrganizationMember{}, sql.ErrNoRows
770+
}
771+
750772
func (q *fakeQuerier) GetProvisionerDaemons(_ context.Context) ([]database.ProvisionerDaemon, error) {
751773
q.mutex.RLock()
752774
defer q.mutex.RUnlock()
@@ -1177,13 +1199,13 @@ func (q *fakeQuerier) InsertUser(_ context.Context, arg database.InsertUserParam
11771199
UpdatedAt: arg.UpdatedAt,
11781200
Username: arg.Username,
11791201
Status: database.UserStatusActive,
1180-
RbacRoles: arg.RbacRoles,
1202+
RBACRoles: arg.RBACRoles,
11811203
}
11821204
q.users = append(q.users, user)
11831205
return user, nil
11841206
}
11851207

1186-
func (q *fakeQuerier) GrantUserRole(_ context.Context, arg database.GrantUserRoleParams) (database.User, error) {
1208+
func (q *fakeQuerier) UpdateUserRoles(_ context.Context, arg database.UpdateUserRolesParams) (database.User, error) {
11871209
q.mutex.Lock()
11881210
defer q.mutex.Unlock()
11891211

@@ -1192,20 +1214,20 @@ func (q *fakeQuerier) GrantUserRole(_ context.Context, arg database.GrantUserRol
11921214
continue
11931215
}
11941216

1195-
// Append the new roles
1196-
user.RbacRoles = append(user.RbacRoles, arg.GrantedRoles...)
1217+
// Set new roles
1218+
user.RBACRoles = arg.GrantedRoles
11971219
// Remove duplicates and sort
1198-
uniqueRoles := make([]string, 0, len(user.RbacRoles))
1220+
uniqueRoles := make([]string, 0, len(user.RBACRoles))
11991221
exist := make(map[string]struct{})
1200-
for _, r := range user.RbacRoles {
1222+
for _, r := range user.RBACRoles {
12011223
if _, ok := exist[r]; ok {
12021224
continue
12031225
}
12041226
exist[r] = struct{}{}
12051227
uniqueRoles = append(uniqueRoles, r)
12061228
}
12071229
sort.Strings(uniqueRoles)
1208-
user.RbacRoles = uniqueRoles
1230+
user.RBACRoles = uniqueRoles
12091231

12101232
q.users[index] = user
12111233
return user, nil

coderd/database/querier.go

Lines changed: 2 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: 63 additions & 32 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/organizationmembers.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,14 @@ WHERE
3939
user_id = ANY(@ids :: uuid [ ])
4040
GROUP BY
4141
user_id;
42+
43+
-- name: UpdateMemberRoles :one
44+
UPDATE
45+
organization_members
46+
SET
47+
-- Remove all duplicates from the roles.
48+
roles = ARRAY(SELECT DISTINCT UNNEST(@granted_roles :: text[]))
49+
WHERE
50+
user_id = @user_id
51+
AND organization_id = @org_id
52+
RETURNING *;

coderd/database/queries/users.sql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ SET
4949
WHERE
5050
id = $1 RETURNING *;
5151

52-
-- name: GrantUserRole :one
52+
-- name: UpdateUserRoles :one
5353
UPDATE
5454
users
5555
SET
56-
-- Append new roles and remove duplicates just to keep things clean.
57-
rbac_roles = ARRAY(SELECT DISTINCT UNNEST(rbac_roles || @granted_roles :: text[]))
56+
-- Remove all duplicates from the roles.
57+
rbac_roles = ARRAY(SELECT DISTINCT UNNEST(@granted_roles :: text[]))
5858
WHERE
5959
id = @id
6060
RETURNING *;

coderd/members.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package coderd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/google/uuid"
9+
10+
"golang.org/x/xerrors"
11+
12+
"github.com/coder/coder/coderd/rbac"
13+
14+
"github.com/coder/coder/coderd/database"
15+
"github.com/coder/coder/coderd/httpapi"
16+
"github.com/coder/coder/coderd/httpmw"
17+
"github.com/coder/coder/codersdk"
18+
)
19+
20+
func (api *api) putMemberRoles(rw http.ResponseWriter, r *http.Request) {
21+
// User is the user to modify
22+
// TODO: Until rbac authorize is implemented, only be able to change your
23+
// own roles. This also means you can grant yourself whatever roles you want.
24+
user := httpmw.UserParam(r)
25+
apiKey := httpmw.APIKey(r)
26+
organization := httpmw.OrganizationParam(r)
27+
if apiKey.UserID != user.ID {
28+
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
29+
Message: fmt.Sprintf("modifying other users is not supported at this time"),
30+
})
31+
return
32+
}
33+
34+
var params codersdk.UpdateRoles
35+
if !httpapi.Read(rw, r, &params) {
36+
return
37+
}
38+
39+
updatedUser, err := api.updateOrganizationMemberRoles(r.Context(), database.UpdateMemberRolesParams{
40+
GrantedRoles: params.Roles,
41+
UserID: user.ID,
42+
OrgID: organization.ID,
43+
})
44+
if err != nil {
45+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
46+
Message: err.Error(),
47+
})
48+
return
49+
}
50+
51+
httpapi.Write(rw, http.StatusOK, convertOrganizationMember(updatedUser))
52+
}
53+
54+
func (api *api) updateOrganizationMemberRoles(ctx context.Context, args database.UpdateMemberRolesParams) (database.OrganizationMember, error) {
55+
// Enforce only site wide roles
56+
for _, r := range args.GrantedRoles {
57+
// Must be an org role for the org in the args
58+
orgID, ok := rbac.IsOrgRole(r)
59+
if !ok {
60+
return database.OrganizationMember{}, xerrors.Errorf("must only update organization roles")
61+
}
62+
63+
roleOrg, err := uuid.Parse(orgID)
64+
if err != nil {
65+
return database.OrganizationMember{}, xerrors.Errorf("role must have proper uuids for organization, %q does not", r)
66+
}
67+
68+
if roleOrg != args.OrgID {
69+
return database.OrganizationMember{}, xerrors.Errorf("must only pass roles for org %q", args.OrgID.String())
70+
}
71+
72+
if _, err := rbac.RoleByName(r); err != nil {
73+
return database.OrganizationMember{}, xerrors.Errorf("%q is not a supported role", r)
74+
}
75+
}
76+
77+
updatedUser, err := api.Database.UpdateMemberRoles(ctx, args)
78+
if err != nil {
79+
return database.OrganizationMember{}, xerrors.Errorf("update site roles: %w", err)
80+
}
81+
return updatedUser, nil
82+
}
83+
84+
func convertOrganizationMember(mem database.OrganizationMember) codersdk.OrganizationMember {
85+
return codersdk.OrganizationMember{
86+
UserID: mem.UserID,
87+
OrganizationID: mem.OrganizationID,
88+
CreatedAt: mem.CreatedAt,
89+
UpdatedAt: mem.UpdatedAt,
90+
Roles: mem.Roles,
91+
}
92+
}

0 commit comments

Comments
 (0)