Skip to content

Commit 18423e5

Browse files
feat: (PBAC) Introduce depends on permission registery (calcom#23440)
Co-authored-by: Eunjae Lee <hey@eunjae.dev>
1 parent b332d98 commit 18423e5

3 files changed

Lines changed: 174 additions & 26 deletions

File tree

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/roles/_components/usePermissions.ts

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { CrudAction } from "@calcom/features/pbac/domain/types/permission-registry";
22
import { PERMISSION_REGISTRY } from "@calcom/features/pbac/domain/types/permission-registry";
3+
import {
4+
getTransitiveDependencies,
5+
getTransitiveDependents,
6+
} from "@calcom/features/pbac/utils/permissionTraversal";
37

48
export type PermissionLevel = "none" | "read" | "all";
59

@@ -129,34 +133,22 @@ export function usePermissions(): UsePermissionsReturn {
129133
// Add the requested permission
130134
newPermissions.push(permission);
131135

132-
// If enabling create, update, or delete, automatically enable read permission
133-
if (action === CrudAction.Create || action === CrudAction.Update || action === CrudAction.Delete) {
134-
const readPermission = `${resource}.${CrudAction.Read}`;
135-
if (!newPermissions.includes(readPermission)) {
136-
newPermissions.push(readPermission);
136+
// Add all transitive dependencies
137+
const dependencies = getTransitiveDependencies(permission);
138+
dependencies.forEach((dependency) => {
139+
if (!newPermissions.includes(dependency)) {
140+
newPermissions.push(dependency);
137141
}
138-
}
142+
});
139143
} else {
140-
// When disabling a permission, check if we need to disable related permissions
141-
if (action === CrudAction.Read) {
142-
// If disabling read, also disable create, update, and delete since they depend on read
143-
const dependentActions = [CrudAction.Create, CrudAction.Update, CrudAction.Delete];
144-
dependentActions.forEach((dependentAction) => {
145-
const dependentPermission = `${resource}.${dependentAction}`;
146-
newPermissions = newPermissions.filter((p) => p !== dependentPermission);
147-
});
148-
} else if (
149-
action === CrudAction.Create ||
150-
action === CrudAction.Update ||
151-
action === CrudAction.Delete
152-
) {
153-
// If disabling create, update, or delete, just remove that specific permission
154-
// Read permission remains enabled
155-
newPermissions = newPermissions.filter((p) => p !== permission);
156-
} else {
157-
// For other actions (custom actions), just remove the specific permission
158-
newPermissions = newPermissions.filter((p) => p !== permission);
159-
}
144+
// When disabling a permission, first remove the permission itself
145+
newPermissions = newPermissions.filter((p) => p !== permission);
146+
147+
// Remove all transitive dependents
148+
const dependents = getTransitiveDependents(permission);
149+
dependents.forEach((dependent) => {
150+
newPermissions = newPermissions.filter((p) => p !== dependent);
151+
});
160152
}
161153

162154
// Only add *.* back if all permissions are now selected

packages/features/pbac/domain/types/permission-registry.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface PermissionDetails {
4141
i18nKey: string;
4242
descriptionI18nKey: string;
4343
scope?: Scope[]; // Optional for backward compatibility
44+
dependsOn?: PermissionString[]; // Dependencies that must be enabled when this permission is enabled
4445
}
4546

4647
export type ResourceConfig = {
@@ -122,6 +123,7 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
122123
category: "role",
123124
i18nKey: "pbac_action_create",
124125
descriptionI18nKey: "pbac_desc_create_roles",
126+
dependsOn: ["role.read"],
125127
},
126128
[CrudAction.Read]: {
127129
description: "View roles",
@@ -134,12 +136,14 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
134136
category: "role",
135137
i18nKey: "pbac_action_update",
136138
descriptionI18nKey: "pbac_desc_update_roles",
139+
dependsOn: ["role.read"],
137140
},
138141
[CrudAction.Delete]: {
139142
description: "Delete roles",
140143
category: "role",
141144
i18nKey: "pbac_action_delete",
142145
descriptionI18nKey: "pbac_desc_delete_roles",
146+
dependsOn: ["role.read"],
143147
},
144148
},
145149
[Resource.EventType]: {
@@ -151,6 +155,7 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
151155
category: "event",
152156
i18nKey: "pbac_action_create",
153157
descriptionI18nKey: "pbac_desc_create_event_types",
158+
dependsOn: ["eventType.read"],
154159
},
155160
[CrudAction.Read]: {
156161
description: "View event types",
@@ -163,12 +168,14 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
163168
category: "event",
164169
i18nKey: "pbac_action_update",
165170
descriptionI18nKey: "pbac_desc_update_event_types",
171+
dependsOn: ["eventType.read"],
166172
},
167173
[CrudAction.Delete]: {
168174
description: "Delete event types",
169175
category: "event",
170176
i18nKey: "pbac_action_delete",
171177
descriptionI18nKey: "pbac_desc_delete_event_types",
178+
dependsOn: ["eventType.read"],
172179
},
173180
},
174181
[Resource.Team]: {
@@ -181,6 +188,7 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
181188
i18nKey: "pbac_action_create",
182189
descriptionI18nKey: "pbac_desc_create_teams",
183190
scope: [Scope.Organization],
191+
dependsOn: ["team.read"],
184192
},
185193
[CrudAction.Read]: {
186194
description: "View team details",
@@ -193,30 +201,35 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
193201
category: "team",
194202
i18nKey: "pbac_action_update",
195203
descriptionI18nKey: "pbac_desc_update_team_settings",
204+
dependsOn: ["team.read"],
196205
},
197206
[CrudAction.Delete]: {
198207
description: "Delete team",
199208
category: "team",
200209
i18nKey: "pbac_action_delete",
201210
descriptionI18nKey: "pbac_desc_delete_team",
211+
dependsOn: ["team.read"],
202212
},
203213
[CustomAction.Invite]: {
204214
description: "Invite team members",
205215
category: "team",
206216
i18nKey: "pbac_action_invite",
207217
descriptionI18nKey: "pbac_desc_invite_team_members",
218+
dependsOn: ["team.read"],
208219
},
209220
[CustomAction.Remove]: {
210221
description: "Remove team members",
211222
category: "team",
212223
i18nKey: "pbac_action_remove",
213224
descriptionI18nKey: "pbac_desc_remove_team_members",
225+
dependsOn: ["team.read"],
214226
},
215227
[CustomAction.ChangeMemberRole]: {
216228
description: "Change role of team members",
217229
category: "team",
218230
i18nKey: "pbac_action_change_member_role",
219231
descriptionI18nKey: "pbac_desc_change_team_member_role",
232+
dependsOn: ["team.read"],
220233
},
221234
},
222235
[Resource.Organization]: {
@@ -243,41 +256,47 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
243256
i18nKey: "pbac_action_list_members",
244257
descriptionI18nKey: "pbac_desc_list_organization_members",
245258
scope: [Scope.Organization],
259+
dependsOn: ["organization.read"],
246260
},
247261
[CustomAction.Invite]: {
248262
description: "Invite organization members",
249263
category: "org",
250264
i18nKey: "pbac_action_invite",
251265
descriptionI18nKey: "pbac_desc_invite_organization_members",
252266
scope: [Scope.Organization],
267+
dependsOn: ["organization.listMembers"],
253268
},
254269
[CustomAction.Remove]: {
255270
description: "Remove organization members",
256271
category: "org",
257272
i18nKey: "pbac_action_remove",
258273
descriptionI18nKey: "pbac_desc_remove_organization_members",
259274
scope: [Scope.Organization],
275+
dependsOn: ["organization.listMembers"],
260276
},
261277
[CustomAction.ManageBilling]: {
262278
description: "Manage organization billing",
263279
category: "org",
264280
i18nKey: "pbac_action_manage_billing",
265281
descriptionI18nKey: "pbac_desc_manage_organization_billing",
266282
scope: [Scope.Organization],
283+
dependsOn: ["organization.read"],
267284
},
268285
[CustomAction.ChangeMemberRole]: {
269286
description: "Change role of team members",
270287
category: "org",
271288
i18nKey: "pbac_action_change_member_role",
272289
descriptionI18nKey: "pbac_desc_change_organization_member_role",
273290
scope: [Scope.Organization],
291+
dependsOn: ["organization.listMembers", "role.read"],
274292
},
275293
[CrudAction.Update]: {
276294
description: "Edit organization settings",
277295
category: "org",
278296
i18nKey: "pbac_action_update",
279297
descriptionI18nKey: "pbac_desc_edit_organization_settings",
280298
scope: [Scope.Organization],
299+
dependsOn: ["organization.read"],
281300
},
282301
},
283302
[Resource.Booking]: {
@@ -296,25 +315,29 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
296315
i18nKey: "pbac_action_read_team_bookings",
297316
descriptionI18nKey: "pbac_desc_view_team_bookings",
298317
scope: [Scope.Team],
318+
dependsOn: ["booking.read"],
299319
},
300320
[CustomAction.ReadOrgBookings]: {
301321
description: "View organization bookings",
302322
category: "booking",
303323
i18nKey: "pbac_action_read_org_bookings",
304324
descriptionI18nKey: "pbac_desc_view_organization_bookings",
305325
scope: [Scope.Organization],
326+
dependsOn: ["booking.read"],
306327
},
307328
[CustomAction.ReadRecordings]: {
308329
description: "View booking recordings",
309330
category: "booking",
310331
i18nKey: "pbac_action_read_recordings",
311332
descriptionI18nKey: "pbac_desc_view_booking_recordings",
333+
dependsOn: ["booking.read"],
312334
},
313335
[CrudAction.Update]: {
314336
description: "Update bookings",
315337
category: "booking",
316338
i18nKey: "pbac_action_update",
317339
descriptionI18nKey: "pbac_desc_update_bookings",
340+
dependsOn: ["booking.read"],
318341
},
319342
},
320343
[Resource.Insights]: {
@@ -337,6 +360,7 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
337360
category: "workflow",
338361
i18nKey: "pbac_action_create",
339362
descriptionI18nKey: "pbac_desc_create_workflows",
363+
dependsOn: ["workflow.read"],
340364
},
341365
[CrudAction.Read]: {
342366
description: "View workflows",
@@ -349,12 +373,14 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
349373
category: "workflow",
350374
i18nKey: "pbac_action_update",
351375
descriptionI18nKey: "pbac_desc_update_workflows",
376+
dependsOn: ["workflow.read"],
352377
},
353378
[CrudAction.Delete]: {
354379
description: "Delete workflows",
355380
category: "workflow",
356381
i18nKey: "pbac_action_delete",
357382
descriptionI18nKey: "pbac_desc_delete_workflows",
383+
dependsOn: ["workflow.read"],
358384
},
359385
},
360386
[Resource.Attributes]: {
@@ -372,18 +398,21 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
372398
category: "attributes",
373399
i18nKey: "pbac_action_update",
374400
descriptionI18nKey: "pbac_desc_update_organization_attributes",
401+
dependsOn: ["organization.attributes.read"],
375402
},
376403
[CrudAction.Delete]: {
377404
description: "Delete organization attributes",
378405
category: "attributes",
379406
i18nKey: "pbac_action_delete",
380407
descriptionI18nKey: "pbac_desc_delete_organization_attributes",
408+
dependsOn: ["organization.attributes.read"],
381409
},
382410
[CrudAction.Create]: {
383411
description: "Create organization attributes",
384412
category: "attributes",
385413
i18nKey: "pbac_action_create",
386414
descriptionI18nKey: "pbac_desc_create_organization_attributes",
415+
dependsOn: ["organization.attributes.read"],
387416
},
388417
},
389418
[Resource.RoutingForm]: {
@@ -395,6 +424,7 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
395424
category: "routing",
396425
i18nKey: "pbac_action_create",
397426
descriptionI18nKey: "pbac_desc_create_routing_forms",
427+
dependsOn: ["routingForm.read"],
398428
},
399429
[CrudAction.Read]: {
400430
description: "View routing forms",
@@ -407,12 +437,14 @@ export const PERMISSION_REGISTRY: PermissionRegistry = {
407437
category: "routing",
408438
i18nKey: "pbac_action_update",
409439
descriptionI18nKey: "pbac_desc_update_routing_forms",
440+
dependsOn: ["routingForm.read"],
410441
},
411442
[CrudAction.Delete]: {
412443
description: "Delete routing forms",
413444
category: "routing",
414445
i18nKey: "pbac_action_delete",
415446
descriptionI18nKey: "pbac_desc_delete_routing_forms",
447+
dependsOn: ["routingForm.read"],
416448
},
417449
},
418450
};

0 commit comments

Comments
 (0)