Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ec11f11

Browse files
authored
fix: improve permissions checks in organization settings (#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

-1
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

+16-6
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

+54-52
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

+20-5
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

+11-1
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

+23-9
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)