Skip to content

Commit fa47456

Browse files
sean-brydonemrysal
andauthored
fix: (pbac) edit sheet readonly to all to readonly (calcom#22672)
* fix readonly to all to readonly * fix sheet not showing correct all,readonly,none state until refresh * update test to include the correct roleId --------- Co-authored-by: Alex van Andel <me@alexvanandel.com>
1 parent dbaa072 commit fa47456

4 files changed

Lines changed: 110 additions & 2 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { describe, it, expect } from "vitest";
2+
3+
import { usePermissions } from "../usePermissions";
4+
5+
describe("usePermissions", () => {
6+
const { getResourcePermissionLevel } = usePermissions();
7+
8+
describe("getResourcePermissionLevel", () => {
9+
it("should return 'all' for any resource when *.* permission is present", () => {
10+
const permissions = ["*.*", "eventType.create", "eventType.read"];
11+
12+
expect(getResourcePermissionLevel("eventType", permissions)).toBe("all");
13+
expect(getResourcePermissionLevel("booking", permissions)).toBe("all");
14+
expect(getResourcePermissionLevel("team", permissions)).toBe("all");
15+
});
16+
17+
it("should return 'all' for resource with all individual permissions", () => {
18+
const permissions = ["eventType.create", "eventType.read", "eventType.update", "eventType.delete"];
19+
20+
expect(getResourcePermissionLevel("eventType", permissions)).toBe("all");
21+
});
22+
it("should return 'read' for resource with only read permission", () => {
23+
const permissions = ["eventType.read"];
24+
25+
expect(getResourcePermissionLevel("eventType", permissions)).toBe("read");
26+
});
27+
28+
it("should return 'none' for resource with no permissions", () => {
29+
const permissions = ["booking.create"];
30+
31+
expect(getResourcePermissionLevel("eventType", permissions)).toBe("none");
32+
});
33+
34+
it("should handle * resource correctly", () => {
35+
const permissionsWithAll = ["*.*"];
36+
const permissionsWithoutAll = ["eventType.read"];
37+
38+
expect(getResourcePermissionLevel("*", permissionsWithAll)).toBe("all");
39+
expect(getResourcePermissionLevel("*", permissionsWithoutAll)).toBe("none");
40+
});
41+
42+
it("should prioritize *.* over individual permissions", () => {
43+
const permissions = ["*.*", "eventType.read"]; // Has global all but only read for eventType individually
44+
45+
expect(getResourcePermissionLevel("eventType", permissions)).toBe("all");
46+
});
47+
});
48+
});

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ export function usePermissions(): UsePermissionsReturn {
4646
const resourceConfig = PERMISSION_REGISTRY[resource as keyof typeof PERMISSION_REGISTRY];
4747
if (!resourceConfig) return "none";
4848

49+
// Check if global all permissions (*.*) is present
50+
if (permissions.includes("*.*")) {
51+
return "all";
52+
}
53+
4954
// Filter out internal keys like _resource when checking permissions
5055
const allResourcePerms = Object.keys(resourceConfig)
5156
.filter((action) => !action.startsWith("_"))

packages/features/pbac/infrastructure/repositories/RoleRepository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export class RoleRepository {
108108
await trx.rolePermission.deleteMany({
109109
where: {
110110
roleId,
111-
AND: permissionChanges.toRemove.map((p) => ({
111+
OR: permissionChanges.toRemove.map((p) => ({
112112
resource: p.resource,
113113
action: p.action,
114114
})),

packages/features/pbac/services/__tests__/role.service.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,15 @@ describe("RoleService", () => {
6767
teamId: roleData.teamId,
6868
color: "#000000",
6969
type: RoleType.CUSTOM,
70-
permissions: [{ id: "perm-1", resource: "eventType", action: "create" }],
70+
permissions: [
71+
{
72+
id: "perm-1",
73+
resource: "eventType",
74+
action: "create",
75+
roleId: "new-role",
76+
createdAt: new Date(),
77+
},
78+
],
7179
createdAt: new Date(),
7280
updatedAt: new Date(),
7381
};
@@ -215,6 +223,53 @@ describe("RoleService", () => {
215223
expect(result).toBeDefined();
216224
});
217225

226+
it("should properly remove multiple permissions when changing from all to none", async () => {
227+
const roleId = "role-id";
228+
const permissions = [] as PermissionString[]; // Setting to none
229+
const existingPermissions = [
230+
{ id: "1", roleId, resource: "eventType", action: "create" },
231+
{ id: "2", roleId, resource: "eventType", action: "read" },
232+
{ id: "3", roleId, resource: "eventType", action: "update" },
233+
{ id: "4", roleId, resource: "eventType", action: "delete" },
234+
];
235+
236+
const permissionChanges = {
237+
toAdd: [],
238+
toRemove: [
239+
{ id: "1", roleId, resource: "eventType", action: "create" },
240+
{ id: "2", roleId, resource: "eventType", action: "read" },
241+
{ id: "3", roleId, resource: "eventType", action: "update" },
242+
{ id: "4", roleId, resource: "eventType", action: "delete" },
243+
],
244+
};
245+
246+
const role: Role = {
247+
id: roleId,
248+
name: "Test Role",
249+
teamId: 1,
250+
type: RoleType.CUSTOM,
251+
color: "#000000",
252+
permissions: [],
253+
createdAt: new Date(),
254+
updatedAt: new Date(),
255+
};
256+
257+
mockRepository.findById.mockResolvedValueOnce(role);
258+
mockRepository.getPermissions.mockResolvedValueOnce(existingPermissions);
259+
mockPermissionDiffService.calculateDiff.mockReturnValueOnce(permissionChanges);
260+
mockRepository.update.mockResolvedValueOnce(role);
261+
262+
const result = await service.update({ roleId, permissions });
263+
264+
expect(mockPermissionDiffService.calculateDiff).toHaveBeenCalledWith(permissions, existingPermissions);
265+
expect(mockRepository.update).toHaveBeenCalledWith(roleId, permissionChanges, {
266+
color: undefined,
267+
name: undefined,
268+
});
269+
expect(result).toBeDefined();
270+
expect(result.permissions).toHaveLength(0);
271+
});
272+
218273
it("should throw error if role does not exist", async () => {
219274
const roleId = "non-existent-role";
220275
const permissions = ["eventType.create"] as PermissionString[];

0 commit comments

Comments
 (0)