Skip to content

Commit ec11f11

Browse files
authored
fix: improve permissions checks in organization settings (coder#16849)
1 parent 092c129 commit ec11f11

File tree

12 files changed

+193
-132
lines changed

12 files changed

+193
-132
lines changed

site/e2e/tests/auditLogs.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ test("logins are logged", async ({ page }) => {
3535
await page.goto("/audit");
3636
const username = users.auditor.username;
3737

38-
const user = currentUser(page);
3938
const loginMessage = `${username} logged in`;
4039
// Make sure those things we did all actually show up
4140
await resetSearch(page, username);

site/src/pages/GroupsPage/GroupsPage.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import GroupAdd from "@mui/icons-material/GroupAddOutlined";
22
import { getErrorMessage } from "api/errors";
33
import { groupsByOrganization } from "api/queries/groups";
44
import { organizationsPermissions } from "api/queries/organizations";
5-
import { ErrorAlert } from "components/Alert/ErrorAlert";
65
import { Button } from "components/Button/Button";
76
import { EmptyState } from "components/EmptyState/EmptyState";
87
import { displayError } from "components/GlobalSnackbar/utils";
98
import { Loader } from "components/Loader/Loader";
109
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
1110
import { Stack } from "components/Stack/Stack";
1211
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
12+
import { RequirePermission } from "modules/permissions/RequirePermission";
1313
import { type FC, useEffect } from "react";
1414
import { Helmet } from "react-helmet-async";
1515
import { useQuery } from "react-query";
@@ -54,16 +54,26 @@ export const GroupsPage: FC = () => {
5454
return <Loader />;
5555
}
5656

57+
const helmet = (
58+
<Helmet>
59+
<title>{pageTitle("Groups")}</title>
60+
</Helmet>
61+
);
62+
5763
const permissions = permissionsQuery.data?.[organization.id];
58-
if (!permissions) {
59-
return <ErrorAlert error={permissionsQuery.error} />;
64+
65+
if (!permissions?.viewGroups) {
66+
return (
67+
<>
68+
{helmet}
69+
<RequirePermission isFeatureVisible={false} />
70+
</>
71+
);
6072
}
6173

6274
return (
6375
<>
64-
<Helmet>
65-
<title>{pageTitle("Groups")}</title>
66-
</Helmet>
76+
{helmet}
6777

6878
<Stack
6979
alignItems="baseline"

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

Lines changed: 54 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { getErrorMessage } from "api/errors";
22
import { deleteOrganizationRole, organizationRoles } from "api/queries/roles";
33
import type { Role } from "api/typesGenerated";
44
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
5+
import { EmptyState } from "components/EmptyState/EmptyState";
56
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
6-
import { Loader } from "components/Loader/Loader";
77
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
88
import { Stack } from "components/Stack/Stack";
99
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
@@ -22,7 +22,7 @@ export const CustomRolesPage: FC = () => {
2222
const { organization: organizationName } = useParams() as {
2323
organization: string;
2424
};
25-
const { organizationPermissions } = useOrganizationSettings();
25+
const { organization, organizationPermissions } = useOrganizationSettings();
2626

2727
const [roleToDelete, setRoleToDelete] = useState<Role>();
2828

@@ -49,65 +49,67 @@ export const CustomRolesPage: FC = () => {
4949
}
5050
}, [organizationRolesQuery.error]);
5151

52-
if (!organizationPermissions) {
53-
return <Loader />;
52+
if (!organization) {
53+
return <EmptyState message="Organization not found" />;
5454
}
5555

5656
return (
57-
<RequirePermission
58-
isFeatureVisible={
59-
organizationPermissions.assignOrgRoles ||
60-
organizationPermissions.createOrgRoles ||
61-
organizationPermissions.viewOrgRoles
62-
}
63-
>
57+
<>
6458
<Helmet>
65-
<title>{pageTitle("Custom Roles")}</title>
59+
<title>
60+
{pageTitle(
61+
"Custom Roles",
62+
organization.display_name || organization.name,
63+
)}
64+
</title>
6665
</Helmet>
67-
68-
<Stack
69-
alignItems="baseline"
70-
direction="row"
71-
justifyContent="space-between"
66+
<RequirePermission
67+
isFeatureVisible={organizationPermissions?.viewOrgRoles ?? false}
7268
>
73-
<SettingsHeader
74-
title="Roles"
75-
description="Manage roles for this organization."
76-
/>
77-
</Stack>
69+
<Stack
70+
alignItems="baseline"
71+
direction="row"
72+
justifyContent="space-between"
73+
>
74+
<SettingsHeader
75+
title="Roles"
76+
description="Manage roles for this organization."
77+
/>
78+
</Stack>
7879

79-
<CustomRolesPageView
80-
builtInRoles={builtInRoles}
81-
customRoles={customRoles}
82-
onDeleteRole={setRoleToDelete}
83-
canAssignOrgRole={organizationPermissions.assignOrgRoles}
84-
canCreateOrgRole={organizationPermissions.createOrgRoles}
85-
isCustomRolesEnabled={isCustomRolesEnabled}
86-
/>
80+
<CustomRolesPageView
81+
builtInRoles={builtInRoles}
82+
customRoles={customRoles}
83+
onDeleteRole={setRoleToDelete}
84+
canAssignOrgRole={organizationPermissions?.assignOrgRoles ?? false}
85+
canCreateOrgRole={organizationPermissions?.createOrgRoles ?? false}
86+
isCustomRolesEnabled={isCustomRolesEnabled}
87+
/>
8788

88-
<DeleteDialog
89-
key={roleToDelete?.name}
90-
isOpen={roleToDelete !== undefined}
91-
confirmLoading={deleteRoleMutation.isLoading}
92-
name={roleToDelete?.name ?? ""}
93-
entity="role"
94-
onCancel={() => setRoleToDelete(undefined)}
95-
onConfirm={async () => {
96-
try {
97-
if (roleToDelete) {
98-
await deleteRoleMutation.mutateAsync(roleToDelete.name);
89+
<DeleteDialog
90+
key={roleToDelete?.name}
91+
isOpen={roleToDelete !== undefined}
92+
confirmLoading={deleteRoleMutation.isLoading}
93+
name={roleToDelete?.name ?? ""}
94+
entity="role"
95+
onCancel={() => setRoleToDelete(undefined)}
96+
onConfirm={async () => {
97+
try {
98+
if (roleToDelete) {
99+
await deleteRoleMutation.mutateAsync(roleToDelete.name);
100+
}
101+
setRoleToDelete(undefined);
102+
await organizationRolesQuery.refetch();
103+
displaySuccess("Custom role deleted successfully!");
104+
} catch (error) {
105+
displayError(
106+
getErrorMessage(error, "Failed to delete custom role"),
107+
);
99108
}
100-
setRoleToDelete(undefined);
101-
await organizationRolesQuery.refetch();
102-
displaySuccess("Custom role deleted successfully!");
103-
} catch (error) {
104-
displayError(
105-
getErrorMessage(error, "Failed to delete custom role"),
106-
);
107-
}
108-
}}
109-
/>
110-
</RequirePermission>
109+
}}
110+
/>
111+
</RequirePermission>
112+
</>
111113
);
112114
};
113115

site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Link } from "components/Link/Link";
1616
import { Paywall } from "components/Paywall/Paywall";
1717
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
1818
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
19+
import { RequirePermission } from "modules/permissions/RequirePermission";
1920
import { type FC, useEffect, useState } from "react";
2021
import { Helmet } from "react-helmet-async";
2122
import { useMutation, useQueries, useQuery, useQueryClient } from "react-query";
@@ -31,8 +32,7 @@ export const IdpSyncPage: FC = () => {
3132
const { organization: organizationName } = useParams() as {
3233
organization: string;
3334
};
34-
const { organizations } = useOrganizationSettings();
35-
const organization = organizations?.find((o) => o.name === organizationName);
35+
const { organization, organizationPermissions } = useOrganizationSettings();
3636
const [groupField, setGroupField] = useState("");
3737
const [roleField, setRoleField] = useState("");
3838

@@ -80,6 +80,23 @@ export const IdpSyncPage: FC = () => {
8080
return <EmptyState message="Organization not found" />;
8181
}
8282

83+
const helmet = (
84+
<Helmet>
85+
<title>
86+
{pageTitle("IdP Sync", organization.display_name || organization.name)}
87+
</title>
88+
</Helmet>
89+
);
90+
91+
if (!organizationPermissions?.viewIdpSyncSettings) {
92+
return (
93+
<>
94+
{helmet}
95+
<RequirePermission isFeatureVisible={false} />
96+
</>
97+
);
98+
}
99+
83100
const patchGroupSyncSettingsMutation = useMutation(
84101
patchGroupSyncSettings(organizationName, queryClient),
85102
);
@@ -103,9 +120,7 @@ export const IdpSyncPage: FC = () => {
103120

104121
return (
105122
<>
106-
<Helmet>
107-
<title>{pageTitle("IdP Sync")}</title>
108-
</Helmet>
123+
{helmet}
109124

110125
<div className="flex flex-col gap-12">
111126
<header className="flex flex-row items-baseline justify-between">

site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
1515
import { Stack } from "components/Stack/Stack";
1616
import { useAuthenticated } from "contexts/auth/RequireAuth";
1717
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
18+
import { RequirePermission } from "modules/permissions/RequirePermission";
1819
import { type FC, useState } from "react";
1920
import { Helmet } from "react-helmet-async";
2021
import { useMutation, useQuery, useQueryClient } from "react-query";
@@ -54,7 +55,7 @@ const OrganizationMembersPage: FC = () => {
5455
const [memberToDelete, setMemberToDelete] =
5556
useState<OrganizationMemberWithUserData>();
5657

57-
if (!organization || !organizationPermissions) {
58+
if (!organization) {
5859
return <EmptyState message="Organization not found" />;
5960
}
6061

@@ -66,6 +67,15 @@ const OrganizationMembersPage: FC = () => {
6667
</Helmet>
6768
);
6869

70+
if (!organizationPermissions) {
71+
return (
72+
<>
73+
{helmet}
74+
<RequirePermission isFeatureVisible={false} />
75+
</>
76+
);
77+
}
78+
6979
return (
7080
<>
7181
{helmet}

site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage.tsx

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { EmptyState } from "components/EmptyState/EmptyState";
44
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
55
import { useDashboard } from "modules/dashboard/useDashboard";
66
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
7+
import { RequirePermission } from "modules/permissions/RequirePermission";
78
import type { FC } from "react";
89
import { Helmet } from "react-helmet-async";
910
import { useQuery } from "react-query";
@@ -15,7 +16,7 @@ const OrganizationProvisionersPage: FC = () => {
1516
const { organization: organizationName } = useParams() as {
1617
organization: string;
1718
};
18-
const { organization } = useOrganizationSettings();
19+
const { organization, organizationPermissions } = useOrganizationSettings();
1920
const { entitlements } = useDashboard();
2021
const { metadata } = useEmbeddedMetadata();
2122
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
@@ -25,16 +26,29 @@ const OrganizationProvisionersPage: FC = () => {
2526
return <EmptyState message="Organization not found" />;
2627
}
2728

29+
const helmet = (
30+
<Helmet>
31+
<title>
32+
{pageTitle(
33+
"Provisioners",
34+
organization.display_name || organization.name,
35+
)}
36+
</title>
37+
</Helmet>
38+
);
39+
40+
if (!organizationPermissions?.viewProvisioners) {
41+
return (
42+
<>
43+
{helmet}
44+
<RequirePermission isFeatureVisible={false} />
45+
</>
46+
);
47+
}
48+
2849
return (
2950
<>
30-
<Helmet>
31-
<title>
32-
{pageTitle(
33-
"Provisioners",
34-
organization.display_name || organization.name,
35-
)}
36-
</title>
37-
</Helmet>
51+
{helmet}
3852
<OrganizationProvisionersPageView
3953
showPaywall={!entitlements.features.multiple_organizations.enabled}
4054
error={provisionersQuery.error}

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