Skip to content

Commit 166467c

Browse files
authored
fix: don't require organization_id in body when updating a custom role (#14102)
1 parent e2cec45 commit 166467c

File tree

11 files changed

+56
-58
lines changed

11 files changed

+56
-58
lines changed

cli/organizationroles.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ func (r *RootCmd) editOrganizationRole(orgContext *OrganizationContext) *serpent
203203
// Do not actually post
204204
updated = customRole
205205
} else {
206-
updated, err = client.PatchOrganizationRole(ctx, org.ID, customRole)
206+
updated, err = client.PatchOrganizationRole(ctx, customRole)
207207
if err != nil {
208208
return xerrors.Errorf("patch role: %w", err)
209209
}

coderd/roles.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ import (
2020
// roles. Ideally only included in the enterprise package, but the routes are
2121
// intermixed with AGPL endpoints.
2222
type CustomRoleHandler interface {
23-
PatchOrganizationRole(ctx context.Context, rw http.ResponseWriter, r *http.Request, orgID uuid.UUID, role codersdk.Role) (codersdk.Role, bool)
23+
PatchOrganizationRole(ctx context.Context, rw http.ResponseWriter, r *http.Request, orgID uuid.UUID, role codersdk.PatchRoleRequest) (codersdk.Role, bool)
2424
}
2525

2626
type agplCustomRoleHandler struct{}
2727

28-
func (agplCustomRoleHandler) PatchOrganizationRole(ctx context.Context, rw http.ResponseWriter, _ *http.Request, _ uuid.UUID, _ codersdk.Role) (codersdk.Role, bool) {
28+
func (agplCustomRoleHandler) PatchOrganizationRole(ctx context.Context, rw http.ResponseWriter, _ *http.Request, _ uuid.UUID, _ codersdk.PatchRoleRequest) (codersdk.Role, bool) {
2929
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
3030
Message: "Creating and updating custom roles is an Enterprise feature. Contact sales!",
3131
})
@@ -49,7 +49,7 @@ func (api *API) patchOrgRoles(rw http.ResponseWriter, r *http.Request) {
4949
organization = httpmw.OrganizationParam(r)
5050
)
5151

52-
var req codersdk.Role
52+
var req codersdk.PatchRoleRequest
5353
if !httpapi.Read(ctx, rw, r, &req) {
5454
return
5555
}

codersdk/roles.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ type Permission struct {
5050
Action RBACAction `json:"action"`
5151
}
5252

53-
// Role is a longer form of SlimRole used to edit custom roles.
53+
// Role is a longer form of SlimRole that includes permissions details.
5454
type Role struct {
5555
Name string `json:"name" table:"name,default_sort" validate:"username"`
5656
OrganizationID string `json:"organization_id,omitempty" table:"organization_id" format:"uuid"`
@@ -61,6 +61,16 @@ type Role struct {
6161
UserPermissions []Permission `json:"user_permissions" table:"user_permissions"`
6262
}
6363

64+
// PatchRoleRequest is used to edit custom roles.
65+
type PatchRoleRequest struct {
66+
Name string `json:"name" table:"name,default_sort" validate:"username"`
67+
DisplayName string `json:"display_name" table:"display_name"`
68+
SitePermissions []Permission `json:"site_permissions" table:"site_permissions"`
69+
// OrganizationPermissions are specific to the organization the role belongs to.
70+
OrganizationPermissions []Permission `json:"organization_permissions" table:"organization_permissions"`
71+
UserPermissions []Permission `json:"user_permissions" table:"user_permissions"`
72+
}
73+
6474
// FullName returns the role name scoped to the organization ID. This is useful if
6575
// printing a set of roles from different scopes, as duplicated names across multiple
6676
// scopes will become unique.
@@ -73,18 +83,26 @@ func (r Role) FullName() string {
7383
}
7484

7585
// PatchOrganizationRole will upsert a custom organization role
76-
func (c *Client) PatchOrganizationRole(ctx context.Context, organizationID uuid.UUID, req Role) (Role, error) {
86+
func (c *Client) PatchOrganizationRole(ctx context.Context, role Role) (Role, error) {
87+
req := PatchRoleRequest{
88+
Name: role.Name,
89+
DisplayName: role.DisplayName,
90+
SitePermissions: role.SitePermissions,
91+
OrganizationPermissions: role.OrganizationPermissions,
92+
UserPermissions: role.UserPermissions,
93+
}
94+
7795
res, err := c.Request(ctx, http.MethodPatch,
78-
fmt.Sprintf("/api/v2/organizations/%s/members/roles", organizationID.String()), req)
96+
fmt.Sprintf("/api/v2/organizations/%s/members/roles", role.OrganizationID), req)
7997
if err != nil {
8098
return Role{}, err
8199
}
82100
defer res.Body.Close()
83101
if res.StatusCode != http.StatusOK {
84102
return Role{}, ReadBodyAsError(res)
85103
}
86-
var role Role
87-
return role, json.NewDecoder(res.Body).Decode(&role)
104+
var r Role
105+
return r, json.NewDecoder(res.Body).Decode(&r)
88106
}
89107

90108
// ListSiteRoles lists all assignable site wide roles.

enterprise/cli/organizationmembers_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func TestEnterpriseListOrganizationMembers(t *testing.T) {
3636

3737
ctx := testutil.Context(t, testutil.WaitMedium)
3838
//nolint:gocritic // only owners can patch roles
39-
customRole, err := ownerClient.PatchOrganizationRole(ctx, owner.OrganizationID, codersdk.Role{
39+
customRole, err := ownerClient.PatchOrganizationRole(ctx, codersdk.Role{
4040
Name: "custom",
4141
OrganizationID: owner.OrganizationID.String(),
4242
DisplayName: "Custom Role",
@@ -89,7 +89,7 @@ func TestAssignOrganizationMemberRole(t *testing.T) {
8989

9090
ctx := testutil.Context(t, testutil.WaitMedium)
9191
// nolint:gocritic // requires owner role to create
92-
customRole, err := ownerClient.PatchOrganizationRole(ctx, owner.OrganizationID, codersdk.Role{
92+
customRole, err := ownerClient.PatchOrganizationRole(ctx, codersdk.Role{
9393
Name: "custom-role",
9494
OrganizationID: owner.OrganizationID.String(),
9595
DisplayName: "Custom Role",

enterprise/cli/templatecreate_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func TestTemplateCreate(t *testing.T) {
168168
ctx := testutil.Context(t, testutil.WaitMedium)
169169

170170
//nolint:gocritic // owner required to make custom roles
171-
orgTemplateAdminRole, err := ownerClient.PatchOrganizationRole(ctx, secondOrg.ID, codersdk.Role{
171+
orgTemplateAdminRole, err := ownerClient.PatchOrganizationRole(ctx, codersdk.Role{
172172
Name: "org-template-admin",
173173
OrganizationID: secondOrg.ID.String(),
174174
OrganizationPermissions: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{

enterprise/coderd/roles.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type enterpriseCustomRoleHandler struct {
2121
Enabled bool
2222
}
2323

24-
func (h enterpriseCustomRoleHandler) PatchOrganizationRole(ctx context.Context, rw http.ResponseWriter, r *http.Request, orgID uuid.UUID, role codersdk.Role) (codersdk.Role, bool) {
24+
func (h enterpriseCustomRoleHandler) PatchOrganizationRole(ctx context.Context, rw http.ResponseWriter, r *http.Request, orgID uuid.UUID, role codersdk.PatchRoleRequest) (codersdk.Role, bool) {
2525
if !h.Enabled {
2626
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
2727
Message: "Custom roles are not enabled",
@@ -77,14 +77,6 @@ func (h enterpriseCustomRoleHandler) PatchOrganizationRole(ctx context.Context,
7777
return codersdk.Role{}, false
7878
}
7979

80-
if role.OrganizationID != orgID.String() {
81-
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
82-
Message: "Invalid request, organization in role and url must match",
83-
Detail: fmt.Sprintf("role organization=%q does not match URL=%q", role.OrganizationID, orgID.String()),
84-
})
85-
return codersdk.Role{}, false
86-
}
87-
8880
originalRoles, err := db.CustomRoles(ctx, database.CustomRolesParams{
8981
LookupRoles: []database.NameOrganizationPair{
9082
{

enterprise/coderd/roles_test.go

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func TestCustomOrganizationRole(t *testing.T) {
5757
ctx := testutil.Context(t, testutil.WaitMedium)
5858

5959
//nolint:gocritic // owner is required for this
60-
role, err := owner.PatchOrganizationRole(ctx, first.OrganizationID, templateAdminCustom(first.OrganizationID))
60+
role, err := owner.PatchOrganizationRole(ctx, templateAdminCustom(first.OrganizationID))
6161
require.NoError(t, err, "upsert role")
6262

6363
// Assign the custom template admin role
@@ -111,7 +111,7 @@ func TestCustomOrganizationRole(t *testing.T) {
111111
ctx := testutil.Context(t, testutil.WaitMedium)
112112

113113
//nolint:gocritic // owner is required for this
114-
role, err := owner.PatchOrganizationRole(ctx, first.OrganizationID, templateAdminCustom(first.OrganizationID))
114+
role, err := owner.PatchOrganizationRole(ctx, templateAdminCustom(first.OrganizationID))
115115
require.NoError(t, err, "upsert role")
116116

117117
// Remove the license to block enterprise functionality
@@ -124,7 +124,7 @@ func TestCustomOrganizationRole(t *testing.T) {
124124
}
125125

126126
// Verify functionality is lost
127-
_, err = owner.PatchOrganizationRole(ctx, first.OrganizationID, templateAdminCustom(first.OrganizationID))
127+
_, err = owner.PatchOrganizationRole(ctx, templateAdminCustom(first.OrganizationID))
128128
require.ErrorContains(t, err, "roles are not enabled")
129129

130130
// Assign the custom template admin role
@@ -152,7 +152,7 @@ func TestCustomOrganizationRole(t *testing.T) {
152152

153153
ctx := testutil.Context(t, testutil.WaitMedium)
154154
//nolint:gocritic // owner is required for this
155-
role, err := owner.PatchOrganizationRole(ctx, first.OrganizationID, templateAdminCustom(first.OrganizationID))
155+
role, err := owner.PatchOrganizationRole(ctx, templateAdminCustom(first.OrganizationID))
156156
require.NoError(t, err, "upsert role")
157157

158158
// Assign the custom template admin role
@@ -169,7 +169,7 @@ func TestCustomOrganizationRole(t *testing.T) {
169169
newRole.SitePermissions = nil
170170
newRole.OrganizationPermissions = nil
171171
newRole.UserPermissions = nil
172-
_, err = owner.PatchOrganizationRole(ctx, first.OrganizationID, newRole)
172+
_, err = owner.PatchOrganizationRole(ctx, newRole)
173173
require.NoError(t, err, "upsert role with override")
174174

175175
// The role should no longer have template perms
@@ -203,7 +203,7 @@ func TestCustomOrganizationRole(t *testing.T) {
203203
ctx := testutil.Context(t, testutil.WaitMedium)
204204

205205
//nolint:gocritic // owner is required for this
206-
_, err := owner.PatchOrganizationRole(ctx, first.OrganizationID, codersdk.Role{
206+
_, err := owner.PatchOrganizationRole(ctx, codersdk.Role{
207207
Name: "Bad_Name", // No underscores allowed
208208
DisplayName: "Testing Purposes",
209209
OrganizationID: first.OrganizationID.String(),
@@ -232,38 +232,17 @@ func TestCustomOrganizationRole(t *testing.T) {
232232
ctx := testutil.Context(t, testutil.WaitMedium)
233233

234234
//nolint:gocritic // owner is required for this
235-
_, err := owner.PatchOrganizationRole(ctx, first.OrganizationID, codersdk.Role{
235+
_, err := owner.PatchOrganizationRole(ctx, codersdk.Role{
236236
Name: "owner", // Reserved
237237
DisplayName: "Testing Purposes",
238+
OrganizationID: first.OrganizationID.String(),
238239
SitePermissions: nil,
239240
OrganizationPermissions: nil,
240241
UserPermissions: nil,
241242
})
242243
require.ErrorContains(t, err, "Reserved")
243244
})
244245

245-
t.Run("MismatchedOrganizations", func(t *testing.T) {
246-
t.Parallel()
247-
dv := coderdtest.DeploymentValues(t)
248-
dv.Experiments = []string{string(codersdk.ExperimentCustomRoles)}
249-
owner, first := coderdenttest.New(t, &coderdenttest.Options{
250-
Options: &coderdtest.Options{
251-
DeploymentValues: dv,
252-
},
253-
LicenseOptions: &coderdenttest.LicenseOptions{
254-
Features: license.Features{
255-
codersdk.FeatureCustomRoles: 1,
256-
},
257-
},
258-
})
259-
260-
ctx := testutil.Context(t, testutil.WaitMedium)
261-
262-
//nolint:gocritic // owner is required for this
263-
_, err := owner.PatchOrganizationRole(ctx, first.OrganizationID, templateAdminCustom(uuid.New()))
264-
require.ErrorContains(t, err, "does not match")
265-
})
266-
267246
// Attempt to add site & user permissions, which is not allowed
268247
t.Run("ExcessPermissions", func(t *testing.T) {
269248
t.Parallel()
@@ -291,7 +270,7 @@ func TestCustomOrganizationRole(t *testing.T) {
291270
}
292271

293272
//nolint:gocritic // owner is required for this
294-
_, err := owner.PatchOrganizationRole(ctx, first.OrganizationID, siteRole)
273+
_, err := owner.PatchOrganizationRole(ctx, siteRole)
295274
require.ErrorContains(t, err, "site wide permissions")
296275

297276
userRole := templateAdminCustom(first.OrganizationID)
@@ -303,11 +282,11 @@ func TestCustomOrganizationRole(t *testing.T) {
303282
}
304283

305284
//nolint:gocritic // owner is required for this
306-
_, err = owner.PatchOrganizationRole(ctx, first.OrganizationID, userRole)
285+
_, err = owner.PatchOrganizationRole(ctx, userRole)
307286
require.ErrorContains(t, err, "not allowed to assign user permissions")
308287
})
309288

310-
t.Run("InvalidUUID", func(t *testing.T) {
289+
t.Run("NotFound", func(t *testing.T) {
311290
t.Parallel()
312291
dv := coderdtest.DeploymentValues(t)
313292
dv.Experiments = []string{string(codersdk.ExperimentCustomRoles)}
@@ -328,8 +307,8 @@ func TestCustomOrganizationRole(t *testing.T) {
328307
newRole.OrganizationID = "0000" // This is not a valid uuid
329308

330309
//nolint:gocritic // owner is required for this
331-
_, err := owner.PatchOrganizationRole(ctx, first.OrganizationID, newRole)
332-
require.ErrorContains(t, err, "Invalid request")
310+
_, err := owner.PatchOrganizationRole(ctx, newRole)
311+
require.ErrorContains(t, err, "Resource not found")
333312
})
334313
}
335314

enterprise/coderd/templates_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ func TestTemplates(t *testing.T) {
751751
})
752752

753753
//nolint:gocritic // owner required to make custom roles
754-
orgTemplateAdminRole, err := ownerClient.PatchOrganizationRole(ctx, secondOrg.ID, codersdk.Role{
754+
orgTemplateAdminRole, err := ownerClient.PatchOrganizationRole(ctx, codersdk.Role{
755755
Name: "org-template-admin",
756756
OrganizationID: secondOrg.ID.String(),
757757
OrganizationPermissions: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{

enterprise/coderd/userauth_test.go

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

706706
ctx := testutil.Context(t, testutil.WaitShort)
707707
//nolint:gocritic // owner required
708-
customRole, err := ownerClient.PatchOrganizationRole(ctx, owner.OrganizationID, codersdk.Role{
708+
customRole, err := ownerClient.PatchOrganizationRole(ctx, codersdk.Role{
709709
Name: "custom-role",
710710
OrganizationID: owner.OrganizationID.String(),
711711
OrganizationPermissions: []codersdk.Permission{},

enterprise/coderd/users_test.go

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

272272
ctx := testutil.Context(t, testutil.WaitShort)
273273
// Create a custom role as an organization admin that allows making templates.
274-
auditorRole, err := client.PatchOrganizationRole(ctx, owner.OrganizationID, codersdk.Role{
274+
auditorRole, err := client.PatchOrganizationRole(ctx, codersdk.Role{
275275
Name: "org-template-admin",
276276
OrganizationID: owner.OrganizationID.String(),
277277
DisplayName: "Template Admin",

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