Skip to content

Commit 86b61ef

Browse files
authored
fix: use correct permissions for CRUD of custom roles (coder#16854)
resolves coder/internal#428 The goal of the PR is to start using updateOrgRoles and deleteOrgRoles permissions to gate custom roles functionality ``` updateOrgRoles: { object: { resource_type: "assign_org_role", organization_id: organizationId, }, action: "update", }, deleteOrgRoles: { object: { resource_type: "assign_org_role", organization_id: organizationId, }, action: "delete", } ```
1 parent 101b62d commit 86b61ef

File tree

9 files changed

+93
-62
lines changed

9 files changed

+93
-62
lines changed

site/src/modules/permissions/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ export type Permissions = {
66

77
export type PermissionName = keyof typeof permissionChecks;
88

9+
/**
10+
* Site-wide permission checks
11+
*/
912
export const permissionChecks = {
1013
viewAllUsers: {
1114
object: {

site/src/modules/permissions/organizations.ts

+14
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,20 @@ export const organizationPermissionChecks = (organizationId: string) =>
7373
},
7474
action: "create",
7575
},
76+
updateOrgRoles: {
77+
object: {
78+
resource_type: "assign_org_role",
79+
organization_id: organizationId,
80+
},
81+
action: "update",
82+
},
83+
deleteOrgRoles: {
84+
object: {
85+
resource_type: "assign_org_role",
86+
organization_id: organizationId,
87+
},
88+
action: "delete",
89+
},
7690
viewProvisioners: {
7791
object: {
7892
resource_type: "provisioner_daemon",

site/src/pages/OrganizationSettingsPage/CustomRolesPage/CreateEditRolePage.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ export const CreateEditRolePage: FC = () => {
4848
return (
4949
<RequirePermission
5050
isFeatureVisible={
51-
organizationPermissions.assignOrgRoles ||
52-
organizationPermissions.createOrgRoles
51+
role
52+
? organizationPermissions.updateOrgRoles
53+
: organizationPermissions.createOrgRoles
5354
}
5455
>
5556
<Helmet>
@@ -87,7 +88,6 @@ export const CreateEditRolePage: FC = () => {
8788
: createOrganizationRoleMutation.isLoading
8889
}
8990
organizationName={organizationName}
90-
canAssignOrgRole={organizationPermissions.assignOrgRoles}
9191
/>
9292
</RequirePermission>
9393
);

site/src/pages/OrganizationSettingsPage/CustomRolesPage/CreateEditRolePageView.stories.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export const Default: Story = {
2323
error: undefined,
2424
isLoading: false,
2525
organizationName: "my-org",
26-
canAssignOrgRole: true,
2726
},
2827
};
2928

@@ -81,7 +80,6 @@ export const InvalidCharsError: Story = {
8180
export const CannotEditRoleName: Story = {
8281
args: {
8382
...Default.args,
84-
canAssignOrgRole: false,
8583
},
8684
};
8785

site/src/pages/OrganizationSettingsPage/CustomRolesPage/CreateEditRolePageView.tsx

+27-33
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ export type CreateEditRolePageViewProps = {
4343
error?: unknown;
4444
isLoading: boolean;
4545
organizationName: string;
46-
canAssignOrgRole: boolean;
4746
allResources?: boolean;
4847
};
4948

@@ -53,7 +52,6 @@ export const CreateEditRolePageView: FC<CreateEditRolePageViewProps> = ({
5352
error,
5453
isLoading,
5554
organizationName,
56-
canAssignOrgRole,
5755
allResources = false,
5856
}) => {
5957
const navigate = useNavigate();
@@ -84,26 +82,24 @@ export const CreateEditRolePageView: FC<CreateEditRolePageViewProps> = ({
8482
title={`${role ? "Edit" : "Create"} Custom Role`}
8583
description="Set a name and permissions for this role."
8684
/>
87-
{canAssignOrgRole && (
88-
<div className="flex space-x-2 items-center">
89-
<Button
90-
variant="outline"
91-
onClick={() => {
92-
navigate(`/organizations/${organizationName}/roles`);
93-
}}
94-
>
95-
Cancel
96-
</Button>
97-
<Button
98-
onClick={() => {
99-
form.handleSubmit();
100-
}}
101-
>
102-
<Spinner loading={isLoading} />
103-
{role !== undefined ? "Save" : "Create Role"}
104-
</Button>
105-
</div>
106-
)}
85+
<div className="flex space-x-2 items-center">
86+
<Button
87+
variant="outline"
88+
onClick={() => {
89+
navigate(`/organizations/${organizationName}/roles`);
90+
}}
91+
>
92+
Cancel
93+
</Button>
94+
<Button
95+
onClick={() => {
96+
form.handleSubmit();
97+
}}
98+
>
99+
<Spinner loading={isLoading} />
100+
{role !== undefined ? "Save" : "Create Role"}
101+
</Button>
102+
</div>
107103
</Stack>
108104

109105
<VerticalForm onSubmit={form.handleSubmit}>
@@ -135,18 +131,16 @@ export const CreateEditRolePageView: FC<CreateEditRolePageViewProps> = ({
135131
allResources={allResources}
136132
/>
137133
</FormFields>
138-
{canAssignOrgRole && (
139-
<FormFooter>
140-
<Button onClick={onCancel} variant="outline">
141-
Cancel
142-
</Button>
134+
<FormFooter>
135+
<Button onClick={onCancel} variant="outline">
136+
Cancel
137+
</Button>
143138

144-
<Button type="submit" disabled={isLoading}>
145-
<Spinner loading={isLoading} />
146-
{role ? "Save role" : "Create Role"}
147-
</Button>
148-
</FormFooter>
149-
)}
139+
<Button type="submit" disabled={isLoading}>
140+
<Spinner loading={isLoading} />
141+
{role ? "Save role" : "Create Role"}
142+
</Button>
143+
</FormFooter>
150144
</VerticalForm>
151145
</>
152146
);

site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPage.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ export const CustomRolesPage: FC = () => {
8181
builtInRoles={builtInRoles}
8282
customRoles={customRoles}
8383
onDeleteRole={setRoleToDelete}
84-
canAssignOrgRole={organizationPermissions?.assignOrgRoles ?? false}
8584
canCreateOrgRole={organizationPermissions?.createOrgRoles ?? false}
85+
canUpdateOrgRole={organizationPermissions?.updateOrgRoles ?? false}
86+
canDeleteOrgRole={organizationPermissions?.deleteOrgRoles ?? false}
8687
isCustomRolesEnabled={isCustomRolesEnabled}
8788
/>
8889

site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPageView.stories.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ const meta: Meta<typeof CustomRolesPageView> = {
1111
args: {
1212
builtInRoles: [MockRoleWithOrgPermissions],
1313
customRoles: [MockRoleWithOrgPermissions],
14-
canAssignOrgRole: true,
1514
canCreateOrgRole: true,
1615
isCustomRolesEnabled: true,
1716
},
@@ -31,7 +30,7 @@ export const NotEnabled: Story = {
3130
export const NotEnabledEmptyTable: Story = {
3231
args: {
3332
customRoles: [],
34-
canAssignOrgRole: true,
33+
canCreateOrgRole: true,
3534
isCustomRolesEnabled: false,
3635
},
3736
};
@@ -58,7 +57,6 @@ export const EmptyDisplayName: Story = {
5857
export const EmptyTableUserWithoutPermission: Story = {
5958
args: {
6059
customRoles: [],
61-
canAssignOrgRole: false,
6260
canCreateOrgRole: false,
6361
},
6462
};

site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPageView.tsx

+39-20
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,19 @@ interface CustomRolesPageViewProps {
3434
builtInRoles: AssignableRoles[] | undefined;
3535
customRoles: AssignableRoles[] | undefined;
3636
onDeleteRole: (role: Role) => void;
37-
canAssignOrgRole: boolean;
3837
canCreateOrgRole: boolean;
38+
canUpdateOrgRole: boolean;
39+
canDeleteOrgRole: boolean;
3940
isCustomRolesEnabled: boolean;
4041
}
4142

4243
export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
4344
builtInRoles,
4445
customRoles,
4546
onDeleteRole,
46-
canAssignOrgRole,
4747
canCreateOrgRole,
48+
canUpdateOrgRole,
49+
canDeleteOrgRole,
4850
isCustomRolesEnabled,
4951
}) => {
5052
return (
@@ -77,7 +79,9 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
7779
<RoleTable
7880
roles={customRoles}
7981
isCustomRolesEnabled={isCustomRolesEnabled}
80-
canAssignOrgRole={canAssignOrgRole}
82+
canCreateOrgRole={canCreateOrgRole}
83+
canUpdateOrgRole={canUpdateOrgRole}
84+
canDeleteOrgRole={canDeleteOrgRole}
8185
onDeleteRole={onDeleteRole}
8286
/>
8387
<span>
@@ -90,7 +94,9 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
9094
<RoleTable
9195
roles={builtInRoles}
9296
isCustomRolesEnabled={isCustomRolesEnabled}
93-
canAssignOrgRole={canAssignOrgRole}
97+
canCreateOrgRole={canCreateOrgRole}
98+
canUpdateOrgRole={canUpdateOrgRole}
99+
canDeleteOrgRole={canDeleteOrgRole}
94100
onDeleteRole={onDeleteRole}
95101
/>
96102
</Stack>
@@ -100,15 +106,19 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
100106
interface RoleTableProps {
101107
roles: AssignableRoles[] | undefined;
102108
isCustomRolesEnabled: boolean;
103-
canAssignOrgRole: boolean;
109+
canCreateOrgRole: boolean;
110+
canUpdateOrgRole: boolean;
111+
canDeleteOrgRole: boolean;
104112
onDeleteRole: (role: Role) => void;
105113
}
106114

107115
const RoleTable: FC<RoleTableProps> = ({
108116
roles,
109117
isCustomRolesEnabled,
118+
canCreateOrgRole,
119+
canUpdateOrgRole,
120+
canDeleteOrgRole,
110121
onDeleteRole,
111-
canAssignOrgRole,
112122
}) => {
113123
const isLoading = roles === undefined;
114124
const isEmpty = Boolean(roles && roles.length === 0);
@@ -134,14 +144,14 @@ const RoleTable: FC<RoleTableProps> = ({
134144
<EmptyState
135145
message="No custom roles yet"
136146
description={
137-
canAssignOrgRole && isCustomRolesEnabled
147+
canCreateOrgRole && isCustomRolesEnabled
138148
? "Create your first custom role"
139149
: !isCustomRolesEnabled
140150
? "Upgrade to a premium license to create a custom role"
141151
: "You don't have permission to create a custom role"
142152
}
143153
cta={
144-
canAssignOrgRole &&
154+
canCreateOrgRole &&
145155
isCustomRolesEnabled && (
146156
<Button
147157
component={RouterLink}
@@ -165,7 +175,8 @@ const RoleTable: FC<RoleTableProps> = ({
165175
<RoleRow
166176
key={role.name}
167177
role={role}
168-
canAssignOrgRole={canAssignOrgRole}
178+
canUpdateOrgRole={canUpdateOrgRole}
179+
canDeleteOrgRole={canDeleteOrgRole}
169180
onDelete={() => onDeleteRole(role)}
170181
/>
171182
))}
@@ -179,11 +190,17 @@ const RoleTable: FC<RoleTableProps> = ({
179190

180191
interface RoleRowProps {
181192
role: AssignableRoles;
193+
canUpdateOrgRole: boolean;
194+
canDeleteOrgRole: boolean;
182195
onDelete: () => void;
183-
canAssignOrgRole: boolean;
184196
}
185197

186-
const RoleRow: FC<RoleRowProps> = ({ role, onDelete, canAssignOrgRole }) => {
198+
const RoleRow: FC<RoleRowProps> = ({
199+
role,
200+
onDelete,
201+
canUpdateOrgRole,
202+
canDeleteOrgRole,
203+
}) => {
187204
const navigate = useNavigate();
188205

189206
return (
@@ -195,20 +212,22 @@ const RoleRow: FC<RoleRowProps> = ({ role, onDelete, canAssignOrgRole }) => {
195212
</TableCell>
196213

197214
<TableCell>
198-
{!role.built_in && (
215+
{!role.built_in && (canUpdateOrgRole || canDeleteOrgRole) && (
199216
<MoreMenu>
200217
<MoreMenuTrigger>
201218
<ThreeDotsButton />
202219
</MoreMenuTrigger>
203220
<MoreMenuContent>
204-
<MoreMenuItem
205-
onClick={() => {
206-
navigate(role.name);
207-
}}
208-
>
209-
Edit
210-
</MoreMenuItem>
211-
{canAssignOrgRole && (
221+
{canUpdateOrgRole && (
222+
<MoreMenuItem
223+
onClick={() => {
224+
navigate(role.name);
225+
}}
226+
>
227+
Edit
228+
</MoreMenuItem>
229+
)}
230+
{canDeleteOrgRole && (
212231
<MoreMenuItem danger onClick={onDelete}>
213232
Delete&hellip;
214233
</MoreMenuItem>

site/src/testHelpers/entities.ts

+4
Original file line numberDiff line numberDiff line change
@@ -2900,6 +2900,8 @@ export const MockOrganizationPermissions: OrganizationPermissions = {
29002900
viewOrgRoles: true,
29012901
createOrgRoles: true,
29022902
assignOrgRoles: true,
2903+
updateOrgRoles: true,
2904+
deleteOrgRoles: true,
29032905
viewProvisioners: true,
29042906
viewProvisionerJobs: true,
29052907
viewIdpSyncSettings: true,
@@ -2916,6 +2918,8 @@ export const MockNoOrganizationPermissions: OrganizationPermissions = {
29162918
viewOrgRoles: false,
29172919
createOrgRoles: false,
29182920
assignOrgRoles: false,
2921+
updateOrgRoles: false,
2922+
deleteOrgRoles: false,
29192923
viewProvisioners: false,
29202924
viewProvisionerJobs: false,
29212925
viewIdpSyncSettings: false,

0 commit comments

Comments
 (0)