Skip to content

Commit f6dfce0

Browse files
committed
Use fine-grained permissions on settings page
Since in addition to deployment settings this page now also includes users, audit logs, groups, and orgs. Since you might not be able to fetch deployment values, move all the loaders to the individual pages instead of in the wrapping layout.
1 parent 402cd88 commit f6dfce0

File tree

15 files changed

+388
-146
lines changed

15 files changed

+388
-146
lines changed

site/src/api/queries/organizations.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,48 @@ export const organizationPermissions = (organizationId: string) => {
117117
queryFn: () =>
118118
API.checkAuthorization({
119119
checks: {
120+
viewUsers: {
121+
object: {
122+
resource_type: "user",
123+
organization_id: organizationId,
124+
},
125+
action: "read",
126+
},
127+
editUsers: {
128+
object: {
129+
resource_type: "user",
130+
organization_id: organizationId,
131+
},
132+
action: "update",
133+
},
120134
createGroup: {
121135
object: {
122136
resource_type: "group",
123137
organization_id: organizationId,
124138
},
125139
action: "create",
126140
},
141+
viewGroups: {
142+
object: {
143+
resource_type: "group",
144+
organization_id: organizationId,
145+
},
146+
action: "read",
147+
},
148+
editOrganization: {
149+
object: {
150+
resource_type: "organization",
151+
organization_id: organizationId,
152+
},
153+
action: "update",
154+
},
155+
auditOrganization: {
156+
object: {
157+
resource_type: "audit_log",
158+
organization_id: organizationId,
159+
},
160+
action: "read",
161+
},
127162
},
128163
}),
129164
};

site/src/contexts/auth/permissions.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ export const checks = {
66
createAnyTemplate: "createAnyTemplate",
77
updateAllTemplates: "updateAllTemplates",
88
viewDeploymentValues: "viewDeploymentValues",
9+
editDeploymentValues: "editDeploymentValues",
910
viewUpdateCheck: "viewUpdateCheck",
1011
viewExternalAuthConfig: "viewExternalAuthConfig",
1112
viewDeploymentStats: "viewDeploymentStats",
1213
editWorkspaceProxies: "editWorkspaceProxies",
14+
createOrganization: "createOrganization",
15+
editAnyOrganization: "editAnyOrganization",
16+
viewAnyGroup: "viewAnyGroup",
1317
} as const;
1418

1519
export const permissionsToCheck = {
@@ -57,6 +61,12 @@ export const permissionsToCheck = {
5761
},
5862
action: "read",
5963
},
64+
[checks.editDeploymentValues]: {
65+
object: {
66+
resource_type: "deployment_config",
67+
},
68+
action: "update",
69+
},
6070
[checks.viewUpdateCheck]: {
6171
object: {
6272
resource_type: "deployment_config",
@@ -81,6 +91,26 @@ export const permissionsToCheck = {
8191
},
8292
action: "create",
8393
},
94+
[checks.createOrganization]: {
95+
object: {
96+
resource_type: "organization",
97+
},
98+
action: "create",
99+
},
100+
[checks.editAnyOrganization]: {
101+
object: {
102+
resource_type: "organization",
103+
any_org: true,
104+
},
105+
action: "update",
106+
},
107+
[checks.viewAnyGroup]: {
108+
object: {
109+
resource_type: "group",
110+
org_id: "any",
111+
},
112+
action: "read",
113+
},
84114
} as const;
85115

86116
export type Permissions = Record<keyof typeof permissionsToCheck, boolean>;

site/src/modules/dashboard/Navbar/Navbar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const Navbar: FC = () => {
1919
featureVisibility.audit_log && Boolean(permissions.viewAnyAuditLog);
2020
const canViewDeployment = Boolean(permissions.viewDeploymentValues);
2121
const canViewOrganizations =
22+
Boolean(permissions.editAnyOrganization) &&
2223
featureVisibility.multiple_organizations &&
2324
experiments.includes("multi-organization");
2425
const canViewAllUsers = Boolean(permissions.viewAllUsers);

site/src/pages/DeploySettingsPage/DeploySettingsLayout.tsx

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { ManagementSettingsLayout } from "pages/ManagementSettingsPage/Managemen
1414
import { Sidebar } from "./Sidebar";
1515

1616
type DeploySettingsContextValue = {
17-
deploymentValues: DeploymentConfig;
17+
deploymentValues: DeploymentConfig | undefined;
1818
};
1919

2020
export const DeploySettingsContext = createContext<
@@ -55,19 +55,15 @@ const DeploySettingsLayoutInner: FC = () => {
5555
<Stack css={{ padding: "48px 0" }} direction="row" spacing={6}>
5656
<Sidebar />
5757
<main css={{ maxWidth: 800, width: "100%" }}>
58-
{deploymentConfigQuery.data ? (
59-
<DeploySettingsContext.Provider
60-
value={{
61-
deploymentValues: deploymentConfigQuery.data,
62-
}}
63-
>
64-
<Suspense fallback={<Loader />}>
65-
<Outlet />
66-
</Suspense>
67-
</DeploySettingsContext.Provider>
68-
) : (
69-
<Loader />
70-
)}
58+
<DeploySettingsContext.Provider
59+
value={{
60+
deploymentValues: deploymentConfigQuery.data,
61+
}}
62+
>
63+
<Suspense fallback={<Loader />}>
64+
<Outlet />
65+
</Suspense>
66+
</DeploySettingsContext.Provider>
7167
</main>
7268
</Stack>
7369
</Margins>

site/src/pages/DeploySettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPage.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { FC } from "react";
22
import { Helmet } from "react-helmet-async";
3+
import { Loader } from "components/Loader/Loader";
34
import { pageTitle } from "utils/page";
45
import { useDeploySettings } from "../DeploySettingsLayout";
56
import { ExternalAuthSettingsPageView } from "./ExternalAuthSettingsPageView";
@@ -13,7 +14,11 @@ const ExternalAuthSettingsPage: FC = () => {
1314
<title>{pageTitle("External Authentication Settings")}</title>
1415
</Helmet>
1516

16-
<ExternalAuthSettingsPageView config={deploymentValues.config} />
17+
{deploymentValues ? (
18+
<ExternalAuthSettingsPageView config={deploymentValues.config} />
19+
) : (
20+
<Loader />
21+
)}
1722
</>
1823
);
1924
};

site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useQuery } from "react-query";
44
import { deploymentDAUs } from "api/queries/deployment";
55
import { entitlements } from "api/queries/entitlements";
66
import { availableExperiments, experiments } from "api/queries/experiments";
7+
import { Loader } from "components/Loader/Loader";
78
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
89
import { pageTitle } from "utils/page";
910
import { useDeploySettings } from "../DeploySettingsLayout";
@@ -29,14 +30,18 @@ const GeneralSettingsPage: FC = () => {
2930
<Helmet>
3031
<title>{pageTitle("General Settings")}</title>
3132
</Helmet>
32-
<GeneralSettingsPageView
33-
deploymentOptions={deploymentValues.options}
34-
deploymentDAUs={deploymentDAUsQuery.data}
35-
deploymentDAUsError={deploymentDAUsQuery.error}
36-
entitlements={entitlementsQuery.data}
37-
invalidExperiments={invalidExperiments}
38-
safeExperiments={safeExperiments}
39-
/>
33+
{deploymentValues ? (
34+
<GeneralSettingsPageView
35+
deploymentOptions={deploymentValues.options}
36+
deploymentDAUs={deploymentDAUsQuery.data}
37+
deploymentDAUsError={deploymentDAUsQuery.error}
38+
entitlements={entitlementsQuery.data}
39+
invalidExperiments={invalidExperiments}
40+
safeExperiments={safeExperiments}
41+
/>
42+
) : (
43+
<Loader />
44+
)}
4045
</>
4146
);
4247
};

site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPage.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { FC } from "react";
22
import { Helmet } from "react-helmet-async";
3+
import { Loader } from "components/Loader/Loader";
34
import { pageTitle } from "utils/page";
45
import { useDeploySettings } from "../DeploySettingsLayout";
56
import { NetworkSettingsPageView } from "./NetworkSettingsPageView";
@@ -13,7 +14,11 @@ const NetworkSettingsPage: FC = () => {
1314
<title>{pageTitle("Network Settings")}</title>
1415
</Helmet>
1516

16-
<NetworkSettingsPageView options={deploymentValues.options} />
17+
{deploymentValues ? (
18+
<NetworkSettingsPageView options={deploymentValues.options} />
19+
) : (
20+
<Loader />
21+
)}
1722
</>
1823
);
1924
};

site/src/pages/DeploySettingsPage/ObservabilitySettingsPage/ObservabilitySettingsPage.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { FC } from "react";
22
import { Helmet } from "react-helmet-async";
3+
import { Loader } from "components/Loader/Loader";
34
import { useDashboard } from "modules/dashboard/useDashboard";
45
import { pageTitle } from "utils/page";
56
import { useDeploySettings } from "../DeploySettingsLayout";
@@ -15,10 +16,14 @@ const ObservabilitySettingsPage: FC = () => {
1516
<title>{pageTitle("Observability Settings")}</title>
1617
</Helmet>
1718

18-
<ObservabilitySettingsPageView
19-
options={deploymentValues.options}
20-
featureAuditLogEnabled={entitlements.features["audit_log"].enabled}
21-
/>
19+
{deploymentValues ? (
20+
<ObservabilitySettingsPageView
21+
options={deploymentValues.options}
22+
featureAuditLogEnabled={entitlements.features["audit_log"].enabled}
23+
/>
24+
) : (
25+
<Loader />
26+
)}
2227
</>
2328
);
2429
};

site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPage.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { FC } from "react";
22
import { Helmet } from "react-helmet-async";
3+
import { Loader } from "components/Loader/Loader";
34
import { useDashboard } from "modules/dashboard/useDashboard";
45
import { pageTitle } from "utils/page";
56
import { useDeploySettings } from "../DeploySettingsLayout";
@@ -15,12 +16,16 @@ const SecuritySettingsPage: FC = () => {
1516
<title>{pageTitle("Security Settings")}</title>
1617
</Helmet>
1718

18-
<SecuritySettingsPageView
19-
options={deploymentValues.options}
20-
featureBrowserOnlyEnabled={
21-
entitlements.features["browser_only"].enabled
22-
}
23-
/>
19+
{deploymentValues ? (
20+
<SecuritySettingsPageView
21+
options={deploymentValues.options}
22+
featureBrowserOnlyEnabled={
23+
entitlements.features["browser_only"].enabled
24+
}
25+
/>
26+
) : (
27+
<Loader />
28+
)}
2429
</>
2530
);
2631
};

site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPage.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { FC } from "react";
22
import { Helmet } from "react-helmet-async";
3+
import { Loader } from "components/Loader/Loader";
34
import { pageTitle } from "utils/page";
45
import { useDeploySettings } from "../DeploySettingsLayout";
56
import { UserAuthSettingsPageView } from "./UserAuthSettingsPageView";
@@ -13,7 +14,11 @@ const UserAuthSettingsPage: FC = () => {
1314
<title>{pageTitle("User Authentication Settings")}</title>
1415
</Helmet>
1516

16-
<UserAuthSettingsPageView options={deploymentValues.options} />
17+
{deploymentValues ? (
18+
<UserAuthSettingsPageView options={deploymentValues.options} />
19+
) : (
20+
<Loader />
21+
)}
1722
</>
1823
);
1924
};

site/src/pages/ManagementSettingsPage/ManagementSettingsLayout.tsx

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { DeploySettingsContext } from "../DeploySettingsPage/DeploySettingsLayou
1616
import { Sidebar } from "./Sidebar";
1717

1818
type OrganizationSettingsContextValue = {
19-
organizations: Organization[];
19+
organizations: Organization[] | undefined;
2020
};
2121

2222
const OrganizationSettingsContext = createContext<
@@ -37,7 +37,14 @@ export const ManagementSettingsLayout: FC = () => {
3737
const { permissions } = useAuthenticated();
3838
const { experiments } = useDashboard();
3939
const feats = useFeatureVisibility();
40-
const deploymentConfigQuery = useQuery(deploymentConfig());
40+
const deploymentConfigQuery = useQuery({
41+
...deploymentConfig(),
42+
// TODO: This is probably normally fine because we will not show links to
43+
// pages that need this data, but if you manually visit the page you
44+
// will see an endless loader when maybe we should show a "permission
45+
// denied" error or at least a 404 instead.
46+
enabled: permissions.viewDeploymentValues,
47+
});
4148
const organizationsQuery = useQuery(organizations());
4249

4350
const canViewOrganizations =
@@ -47,34 +54,34 @@ export const ManagementSettingsLayout: FC = () => {
4754
return <NotFoundPage />;
4855
}
4956

57+
// The deployment settings page also contains users, audit logs, groups and
58+
// organizations, so this page must be visible if you can see any of these.
59+
const canViewDeploymentSettingsPage =
60+
permissions.viewDeploymentValues ||
61+
permissions.viewAllUsers ||
62+
permissions.editAnyOrganization ||
63+
permissions.viewAnyAuditLog;
64+
5065
return (
51-
<RequirePermission isFeatureVisible={permissions.viewDeploymentValues}>
66+
<RequirePermission isFeatureVisible={canViewDeploymentSettingsPage}>
5267
<Margins>
5368
<Stack css={{ padding: "48px 0" }} direction="row" spacing={6}>
54-
{organizationsQuery.data ? (
55-
<OrganizationSettingsContext.Provider
56-
value={{ organizations: organizationsQuery.data }}
57-
>
58-
<Sidebar />
59-
<main css={{ width: "100%" }}>
60-
{deploymentConfigQuery.data ? (
61-
<DeploySettingsContext.Provider
62-
value={{
63-
deploymentValues: deploymentConfigQuery.data,
64-
}}
65-
>
66-
<Suspense fallback={<Loader />}>
67-
<Outlet />
68-
</Suspense>
69-
</DeploySettingsContext.Provider>
70-
) : (
71-
<Loader />
72-
)}
73-
</main>
74-
</OrganizationSettingsContext.Provider>
75-
) : (
76-
<Loader />
77-
)}
69+
<OrganizationSettingsContext.Provider
70+
value={{ organizations: organizationsQuery.data }}
71+
>
72+
<Sidebar />
73+
<main css={{ width: "100%" }}>
74+
<DeploySettingsContext.Provider
75+
value={{
76+
deploymentValues: deploymentConfigQuery.data,
77+
}}
78+
>
79+
<Suspense fallback={<Loader />}>
80+
<Outlet />
81+
</Suspense>
82+
</DeploySettingsContext.Provider>
83+
</main>
84+
</OrganizationSettingsContext.Provider>
7885
</Stack>
7986
</Margins>
8087
</RequirePermission>

0 commit comments

Comments
 (0)