Skip to content

Commit 4b2f89d

Browse files
authored
feat: enable PBAC checking on organization settings page (calcom#22467)
1 parent 5360cb5 commit 4b2f89d

26 files changed

Lines changed: 936 additions & 105 deletions

File tree

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/attributes/page.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { _generateMetadata, getTranslate } from "app/_utils";
2+
import { headers, cookies } from "next/headers";
3+
import { redirect } from "next/navigation";
24

35
import OrgSettingsAttributesPage from "@calcom/ee/organizations/pages/settings/attributes/attributes-list-view";
6+
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
7+
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
8+
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
49
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
10+
import { MembershipRole } from "@calcom/prisma/enums";
11+
12+
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
513

614
export const generateMetadata = async () =>
715
await _generateMetadata(
@@ -14,10 +22,40 @@ export const generateMetadata = async () =>
1422

1523
const Page = async () => {
1624
const t = await getTranslate();
25+
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
26+
27+
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
28+
return redirect("/settings/profile");
29+
}
30+
31+
const { canRead, canEdit, canDelete, canCreate } = await getResourcePermissions({
32+
userId: session.user.id,
33+
teamId: session.user.profile.organizationId,
34+
resource: Resource.Attributes,
35+
userRole: session.user.org.role,
36+
fallbackRoles: {
37+
read: {
38+
roles: [MembershipRole.MEMBER, MembershipRole.ADMIN, MembershipRole.OWNER],
39+
},
40+
update: {
41+
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
42+
},
43+
delete: {
44+
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
45+
},
46+
create: {
47+
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
48+
},
49+
},
50+
});
51+
52+
if (!canRead) {
53+
return redirect("/settings/profile");
54+
}
1755

1856
return (
1957
<SettingsHeader title={t("attributes")} description={t("attribute_meta_description")}>
20-
<OrgSettingsAttributesPage />
58+
<OrgSettingsAttributesPage permissions={{ canEdit, canDelete, canCreate }} />
2159
</SettingsHeader>
2260
);
2361
};

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/dsync/page.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { _generateMetadata, getTranslate } from "app/_utils";
2+
import { headers, cookies } from "next/headers";
3+
import { redirect } from "next/navigation";
24

5+
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
36
import DirectorySyncTeamView from "@calcom/features/ee/dsync/page/team-dsync-view";
7+
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
8+
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
49
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
10+
import { MembershipRole } from "@calcom/prisma/enums";
11+
12+
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
513

614
export const generateMetadata = async () =>
715
await _generateMetadata(
@@ -14,10 +22,27 @@ export const generateMetadata = async () =>
1422

1523
const Page = async () => {
1624
const t = await getTranslate();
25+
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
26+
27+
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
28+
return redirect("/settings/organizations/general");
29+
}
30+
31+
const { canEdit } = await getResourcePermissions({
32+
userId: session.user.id,
33+
teamId: session.user.profile.organizationId,
34+
resource: Resource.Organization,
35+
userRole: session.user.org.role,
36+
fallbackRoles: {
37+
update: {
38+
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
39+
},
40+
},
41+
});
1742

1843
return (
1944
<SettingsHeader title={t("directory_sync")} description={t("directory_sync_description")}>
20-
<DirectorySyncTeamView />
45+
<DirectorySyncTeamView permissions={{ canEdit }} />
2146
</SettingsHeader>
2247
);
2348
};

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/privacy/page.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { _generateMetadata, getTranslate } from "app/_utils";
2+
import { headers, cookies } from "next/headers";
3+
import { redirect } from "next/navigation";
24

5+
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
36
import PrivacyView from "@calcom/features/ee/organizations/pages/settings/privacy";
7+
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
8+
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
49
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
10+
import { MembershipRole } from "@calcom/prisma/enums";
11+
12+
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
513

614
export const generateMetadata = async () =>
715
await _generateMetadata(
@@ -15,9 +23,34 @@ export const generateMetadata = async () =>
1523
const Page = async () => {
1624
const t = await getTranslate();
1725

26+
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
27+
28+
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
29+
return redirect("/settings/profile");
30+
}
31+
32+
const { canRead, canEdit } = await getResourcePermissions({
33+
userId: session.user.id,
34+
teamId: session.user.profile.organizationId,
35+
resource: Resource.Organization,
36+
userRole: session.user.org.role,
37+
fallbackRoles: {
38+
read: {
39+
roles: [MembershipRole.MEMBER, MembershipRole.ADMIN, MembershipRole.OWNER],
40+
},
41+
update: {
42+
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
43+
},
44+
},
45+
});
46+
47+
if (!canRead) {
48+
return redirect("/settings/profile");
49+
}
50+
1851
return (
1952
<SettingsHeader title={t("privacy")} description={t("privacy_organization_description")}>
20-
<PrivacyView />
53+
<PrivacyView permissions={{ canRead, canEdit }} />
2154
</SettingsHeader>
2255
);
2356
};

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/sso/page.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { _generateMetadata, getTranslate } from "app/_utils";
2+
import { headers, cookies } from "next/headers";
3+
import { redirect } from "next/navigation";
24

5+
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
36
import OrgSSOView from "@calcom/features/ee/sso/page/orgs-sso-view";
7+
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
8+
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
49
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
10+
import { MembershipRole } from "@calcom/prisma/enums";
11+
12+
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
513

614
export const generateMetadata = async () =>
715
await _generateMetadata(
@@ -14,10 +22,27 @@ export const generateMetadata = async () =>
1422

1523
const Page = async () => {
1624
const t = await getTranslate();
25+
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
26+
27+
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
28+
return redirect("/settings/organizations/general");
29+
}
30+
31+
const { canEdit } = await getResourcePermissions({
32+
userId: session.user.id,
33+
teamId: session.user.profile.organizationId,
34+
resource: Resource.Organization,
35+
userRole: session.user.org.role,
36+
fallbackRoles: {
37+
update: {
38+
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
39+
},
40+
},
41+
});
1742

1843
return (
1944
<SettingsHeader title={t("sso_configuration")} description={t("sso_configuration_description_orgs")}>
20-
<OrgSSOView />
45+
<OrgSSOView permissions={{ canEdit }} />
2146
</SettingsHeader>
2247
);
2348
};

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/general/page.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { _generateMetadata, getTranslate } from "app/_utils";
2+
import { headers, cookies } from "next/headers";
3+
import { redirect } from "next/navigation";
24

5+
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
36
import LegacyPage from "@calcom/features/ee/organizations/pages/settings/general";
7+
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
8+
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
49
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
10+
import { MembershipRole } from "@calcom/prisma/enums";
11+
12+
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
513

614
export const generateMetadata = async () =>
715
await _generateMetadata(
@@ -15,9 +23,30 @@ export const generateMetadata = async () =>
1523
const Page = async () => {
1624
const t = await getTranslate();
1725

26+
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
27+
28+
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
29+
return redirect("/settings/profile");
30+
}
31+
32+
const { canRead, canEdit } = await getResourcePermissions({
33+
userId: session.user.id,
34+
teamId: session.user.profile.organizationId,
35+
resource: Resource.Organization,
36+
userRole: session.user.org.role,
37+
fallbackRoles: {
38+
read: {
39+
roles: [MembershipRole.MEMBER, MembershipRole.ADMIN, MembershipRole.OWNER],
40+
},
41+
update: {
42+
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
43+
},
44+
},
45+
});
46+
1847
return (
1948
<SettingsHeader title={t("general")} description={t("general_description")} borderInShellHeader={true}>
20-
<LegacyPage />
49+
<LegacyPage permissions={{ canRead, canEdit }} />
2150
</SettingsHeader>
2251
);
2352
};

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/profile/page.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import { _generateMetadata, getTranslate } from "app/_utils";
2+
import { cookies, headers } from "next/headers";
3+
import { redirect } from "next/navigation";
24

5+
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
36
import LegacyPage from "@calcom/features/ee/organizations/pages/settings/profile";
7+
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
8+
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
49
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
10+
import type { Membership } from "@calcom/prisma/client";
11+
import { MembershipRole } from "@calcom/prisma/enums";
12+
13+
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
514

615
export const generateMetadata = async () =>
716
await _generateMetadata(
@@ -13,14 +22,47 @@ export const generateMetadata = async () =>
1322
);
1423

1524
const Page = async () => {
25+
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
1626
const t = await getTranslate();
1727

28+
const orgRole = session?.user.profile?.organization.members?.find(
29+
(member: Membership) => member.userId === session?.user.id
30+
)?.role;
31+
32+
if (!session?.user.id || !session?.user.profile?.organizationId || !orgRole) {
33+
return redirect("/settings/profile");
34+
}
35+
36+
const { canRead, canEdit, canDelete } = await getResourcePermissions({
37+
userId: session.user.id,
38+
teamId: session?.user.profile?.organizationId,
39+
resource: Resource.Organization,
40+
userRole: orgRole,
41+
fallbackRoles: {
42+
read: {
43+
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
44+
},
45+
update: {
46+
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
47+
},
48+
delete: {
49+
roles: [MembershipRole.OWNER],
50+
},
51+
},
52+
});
53+
1854
return (
1955
<SettingsHeader
2056
title={t("profile")}
2157
description={t("profile_org_description")}
2258
borderInShellHeader={true}>
23-
<LegacyPage />
59+
<LegacyPage
60+
permissions={{
61+
canEdit,
62+
canRead,
63+
canDelete,
64+
}}
65+
/>
2466
</SettingsHeader>
2567
);
2668
};

apps/web/public/static/locales/en/common.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,10 @@
871871
"create_team": "Create Team",
872872
"name": "Name",
873873
"nameless_team": "Nameless Team",
874+
"oauth_clients": "OAuth Clients",
875+
"oauth_clients_description": "Manage OAuth clients for your organization",
876+
"create_oauth_client": "Create OAuth Client",
877+
"create_oauth_client_description": "Create a new OAuth client for third-party integrations",
874878
"oauth_client_deletion_message": "OAuth client deleted successfully",
875879
"create_new_team_description": "Create a new team to collaborate with users.",
876880
"create_new_team": "Create a new team",
@@ -1836,6 +1840,8 @@
18361840
"edit_event_type": "Edit event type",
18371841
"only_admin_can_see_members_of_org": "This Organization is private, and only the organization's admin or owner can view its members.",
18381842
"only_admin_can_manage_sso_org": "Only the organization's admin or owner can manage SSO settings",
1843+
"only_admin_can_manage_directory_sync": "Only the organization's admin or owner can manage directory sync settings",
1844+
"only_admin_can_manage_oauth_clients": "Only the organization's admin or owner can manage OAuth clients",
18391845
"collective_scheduling": "Collective Scheduling",
18401846
"make_it_easy_to_book": "Make it easy to book your team when everyone is available.",
18411847
"find_the_best_person": "Find the best person available and cycle through your team.",
@@ -3302,6 +3308,11 @@
33023308
"error_creating_role": "Error creating role",
33033309
"error_updating_role": "Error updating role",
33043310
"pbac_desc_create_roles": "Create roles",
3311+
"pbac_resource_attributes": "Attributes",
3312+
"pbac_desc_view_organization_attributes": "View organization attributes",
3313+
"pbac_desc_update_organization_attributes": "Update organization attributes",
3314+
"pbac_desc_delete_organization_attributes": "Delete organization attributes",
3315+
"pbac_desc_create_organization_attributes": "Create organization attributes",
33053316
"pbac_desc_view_roles": "View roles",
33063317
"pbac_desc_update_roles": "Update roles",
33073318
"pbac_desc_delete_roles": "Delete roles",

packages/features/ee/dsync/page/team-dsync-view.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { showToast } from "@calcom/ui/components/toast";
1212
import ConfigureDirectorySync from "../components/ConfigureDirectorySync";
1313

1414
// For Hosted Cal - Team view
15-
const DirectorySync = () => {
15+
const DirectorySync = ({ permissions }: { permissions?: { canEdit: boolean } }) => {
1616
const { t } = useLocale();
1717
const router = useRouter();
1818

@@ -36,6 +36,10 @@ const DirectorySync = () => {
3636
showToast(error.message, "error");
3737
}
3838

39+
if (!permissions?.canEdit) {
40+
router.push("/404");
41+
}
42+
3943
return (
4044
<div className="bg-default w-full sm:mx-0 xl:mt-0">
4145
{HOSTED_CAL_FEATURES && <ConfigureDirectorySync organizationId={currentOrg?.id || null} />}

packages/features/ee/organizations/pages/components/DisablePhoneOnlySMSNotificationsSwitch.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ import { showToast } from "@calcom/ui/components/toast";
1010

1111
interface GeneralViewProps {
1212
currentOrg: RouterOutputs["viewer"]["organizations"]["listCurrent"];
13-
isAdminOrOwner: boolean;
1413
}
1514

16-
export const DisablePhoneOnlySMSNotificationsSwitch = ({ currentOrg, isAdminOrOwner }: GeneralViewProps) => {
15+
export const DisablePhoneOnlySMSNotificationsSwitch = ({ currentOrg }: GeneralViewProps) => {
1716
const { t } = useLocale();
1817
const utils = trpc.useUtils();
1918
const [disablePhoneOnlySMSNotificationsActive, setDisablePhoneOnlySMSNotificationsActive] = useState(
@@ -32,8 +31,6 @@ export const DisablePhoneOnlySMSNotificationsSwitch = ({ currentOrg, isAdminOrOw
3231
},
3332
});
3433

35-
if (!isAdminOrOwner) return null;
36-
3734
return (
3835
<>
3936
<SettingsToggle

0 commit comments

Comments
 (0)