Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit097f739

Browse files
authored
feat: add organization-scoped permission checks to deployment settings (#14063)
* s/readAllUsers/viewAllUsersOther frontend variables use the `view` syntax. Arguably we should use `read` to match the backend, but `view` does seem more UI-like.* Check license for organizationsAll the checks now require both the experiment and license.I also renamed the variable canViewOrganizations everywhere forconsistency.* Allow any auditor to view the audit log* Use fine-grained permissions on settings pageSince in addition to deployment settings this page now also includesusers, audit logs, groups, and orgs.Since you might not be able to fetch deployment values, move all theloaders to the individual pages instead of in the wrapping layout.* Add stories for organization members pageNeeded to break it out into a separate view to do this.* Add stories for multi-org sidebar* Remove multi-org check from management settings layoutWe only use this layout when multi-org is enabled, so no need to run thecheck a second time.* Add more stories for deployment dropdown
1 parent0ad5f60 commit097f739

File tree

29 files changed

+1250
-645
lines changed

29 files changed

+1250
-645
lines changed

‎site/src/api/queries/organizations.ts‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,64 @@ export const provisionerDaemons = (organization: string) => {
120120
queryFn:()=>API.getProvisionerDaemonsByOrganization(organization),
121121
};
122122
};
123+
124+
/**
125+
* Fetch permissions for a single organization.
126+
*
127+
* If the ID is undefined, return a disabled query.
128+
*/
129+
exportconstorganizationPermissions=(organizationId:string|undefined)=>{
130+
if(!organizationId){
131+
return{enabled:false};
132+
}
133+
return{
134+
queryKey:["organization",organizationId,"permissions"],
135+
queryFn:()=>
136+
API.checkAuthorization({
137+
checks:{
138+
viewMembers:{
139+
object:{
140+
resource_type:"organization_member",
141+
organization_id:organizationId,
142+
},
143+
action:"read",
144+
},
145+
editMembers:{
146+
object:{
147+
resource_type:"organization_member",
148+
organization_id:organizationId,
149+
},
150+
action:"update",
151+
},
152+
createGroup:{
153+
object:{
154+
resource_type:"group",
155+
organization_id:organizationId,
156+
},
157+
action:"create",
158+
},
159+
viewGroups:{
160+
object:{
161+
resource_type:"group",
162+
organization_id:organizationId,
163+
},
164+
action:"read",
165+
},
166+
editOrganization:{
167+
object:{
168+
resource_type:"organization",
169+
organization_id:organizationId,
170+
},
171+
action:"update",
172+
},
173+
auditOrganization:{
174+
object:{
175+
resource_type:"audit_log",
176+
organization_id:organizationId,
177+
},
178+
action:"read",
179+
},
180+
},
181+
}),
182+
};
183+
};

‎site/src/contexts/auth/permissions.tsx‎

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
exportconstchecks={
2-
readAllUsers:"readAllUsers",
2+
viewAllUsers:"viewAllUsers",
33
updateUsers:"updateUsers",
44
createUser:"createUser",
55
createTemplates:"createTemplates",
66
updateTemplates:"updateTemplates",
77
deleteTemplates:"deleteTemplates",
8-
viewAuditLog:"viewAuditLog",
8+
viewAnyAuditLog:"viewAnyAuditLog",
99
viewDeploymentValues:"viewDeploymentValues",
10-
createGroup:"createGroup",
10+
editDeploymentValues:"editDeploymentValues",
1111
viewUpdateCheck:"viewUpdateCheck",
1212
viewExternalAuthConfig:"viewExternalAuthConfig",
1313
viewDeploymentStats:"viewDeploymentStats",
1414
editWorkspaceProxies:"editWorkspaceProxies",
15+
createOrganization:"createOrganization",
16+
editAnyOrganization:"editAnyOrganization",
17+
viewAnyGroup:"viewAnyGroup",
18+
createGroup:"createGroup",
19+
viewAllLicenses:"viewAllLicenses",
1520
}asconst;
1621

1722
exportconstpermissionsToCheck={
18-
[checks.readAllUsers]:{
23+
[checks.viewAllUsers]:{
1924
object:{
2025
resource_type:"user",
2126
},
@@ -51,9 +56,10 @@ export const permissionsToCheck = {
5156
},
5257
action:"delete",
5358
},
54-
[checks.viewAuditLog]:{
59+
[checks.viewAnyAuditLog]:{
5560
object:{
5661
resource_type:"audit_log",
62+
any_org:true,
5763
},
5864
action:"read",
5965
},
@@ -63,11 +69,11 @@ export const permissionsToCheck = {
6369
},
6470
action:"read",
6571
},
66-
[checks.createGroup]:{
72+
[checks.editDeploymentValues]:{
6773
object:{
68-
resource_type:"group",
74+
resource_type:"deployment_config",
6975
},
70-
action:"create",
76+
action:"update",
7177
},
7278
[checks.viewUpdateCheck]:{
7379
object:{
@@ -93,6 +99,38 @@ export const permissionsToCheck = {
9399
},
94100
action:"create",
95101
},
102+
[checks.createOrganization]:{
103+
object:{
104+
resource_type:"organization",
105+
},
106+
action:"create",
107+
},
108+
[checks.editAnyOrganization]:{
109+
object:{
110+
resource_type:"organization",
111+
any_org:true,
112+
},
113+
action:"update",
114+
},
115+
[checks.viewAnyGroup]:{
116+
object:{
117+
resource_type:"group",
118+
org_id:"any",
119+
},
120+
action:"read",
121+
},
122+
[checks.createGroup]:{
123+
object:{
124+
resource_type:"group",
125+
},
126+
action:"create",
127+
},
128+
[checks.viewAllLicenses]:{
129+
object:{
130+
resource_type:"license",
131+
},
132+
action:"read",
133+
},
96134
}asconst;
97135

98136
exporttypePermissions=Record<keyoftypeofpermissionsToCheck,boolean>;

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ export const Navbar: FC = () => {
1616
const{user:me, permissions, signOut}=useAuthenticated();
1717
constfeatureVisibility=useFeatureVisibility();
1818
constcanViewAuditLog=
19-
featureVisibility["audit_log"]&&Boolean(permissions.viewAuditLog);
19+
featureVisibility.audit_log&&Boolean(permissions.viewAnyAuditLog);
2020
constcanViewDeployment=Boolean(permissions.viewDeploymentValues);
2121
constcanViewOrganizations=
22+
Boolean(permissions.editAnyOrganization)&&
2223
featureVisibility.multiple_organizations&&
2324
experiments.includes("multi-organization");
24-
constcanViewAllUsers=Boolean(permissions.readAllUsers);
25+
constcanViewAllUsers=Boolean(permissions.viewAllUsers);
2526
constproxyContextValue=useProxy();
2627
constcanViewHealth=canViewDeployment;
2728

‎site/src/modules/dashboard/Navbar/NavbarView.stories.tsx‎

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
importtype{Meta,StoryObj}from"@storybook/react";
2+
import{within,userEvent}from"@storybook/test";
23
import{chromaticWithTablet}from"testHelpers/chromatic";
34
import{MockUser,MockUser2}from"testHelpers/entities";
45
import{withDashboardProvider}from"testHelpers/storybook";
@@ -10,26 +11,63 @@ const meta: Meta<typeof NavbarView> = {
1011
component:NavbarView,
1112
args:{
1213
user:MockUser,
14+
canViewAllUsers:true,
1315
canViewAuditLog:true,
1416
canViewDeployment:true,
15-
canViewAllUsers:true,
1617
canViewHealth:true,
18+
canViewOrganizations:true,
1719
},
1820
decorators:[withDashboardProvider],
1921
};
2022

2123
exportdefaultmeta;
2224
typeStory=StoryObj<typeofNavbarView>;
2325

24-
exportconstForAdmin:Story={};
26+
exportconstForAdmin:Story={
27+
play:async({ canvasElement})=>{
28+
constcanvas=within(canvasElement);
29+
awaituserEvent.click(canvas.getByRole("button",{name:"Deployment"}));
30+
},
31+
};
32+
33+
exportconstForAuditor:Story={
34+
args:{
35+
user:MockUser2,
36+
canViewAllUsers:false,
37+
canViewAuditLog:true,
38+
canViewDeployment:false,
39+
canViewHealth:false,
40+
canViewOrganizations:false,
41+
},
42+
play:async({ canvasElement})=>{
43+
constcanvas=within(canvasElement);
44+
awaituserEvent.click(canvas.getByRole("button",{name:"Deployment"}));
45+
},
46+
};
47+
48+
exportconstForOrgAdmin:Story={
49+
args:{
50+
user:MockUser2,
51+
canViewAllUsers:false,
52+
canViewAuditLog:true,
53+
canViewDeployment:false,
54+
canViewHealth:false,
55+
canViewOrganizations:true,
56+
},
57+
play:async({ canvasElement})=>{
58+
constcanvas=within(canvasElement);
59+
awaituserEvent.click(canvas.getByRole("button",{name:"Deployment"}));
60+
},
61+
};
2562

2663
exportconstForMember:Story={
2764
args:{
2865
user:MockUser2,
66+
canViewAllUsers:false,
2967
canViewAuditLog:false,
3068
canViewDeployment:false,
31-
canViewAllUsers:false,
3269
canViewHealth:false,
70+
canViewOrganizations:false,
3371
},
3472
};
3573

‎site/src/pages/AuditPage/AuditPage.tsx‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ import {
1717
import{AuditPageView}from"./AuditPageView";
1818

1919
constAuditPage:FC=()=>{
20-
const{audit_log:isAuditLogVisible}=useFeatureVisibility();
20+
constfeats=useFeatureVisibility();
2121
const{ experiments}=useDashboard();
2222
constlocation=useLocation();
23-
constisMultiOrg=experiments.includes("multi-organization");
2423

2524
/**
2625
* There is an implicit link between auditsQuery and filter via the
@@ -75,7 +74,9 @@ const AuditPage: FC = () => {
7574
// TODO: Once multi-org is stable, we should place this redirect into the
7675
// router directly, if we still need to maintain it (for users who are
7776
// typing the old URL manually or have it bookmarked).
78-
if(isMultiOrg&&location.pathname!=="/deployment/audit"){
77+
constcanViewOrganizations=
78+
feats.multiple_organizations&&experiments.includes("multi-organization");
79+
if(canViewOrganizations&&location.pathname!=="/deployment/audit"){
7980
return<Navigateto={`/deployment/audit${location.search}`}replace/>;
8081
}
8182

@@ -88,18 +89,18 @@ const AuditPage: FC = () => {
8889
<AuditPageView
8990
auditLogs={auditsQuery.data?.audit_logs}
9091
isNonInitialPage={isNonInitialPage(searchParams)}
91-
isAuditLogVisible={isAuditLogVisible}
92+
isAuditLogVisible={feats.audit_log}
9293
auditsQuery={auditsQuery}
9394
error={auditsQuery.error}
94-
showOrgDetails={isMultiOrg}
95+
showOrgDetails={canViewOrganizations}
9596
filterProps={{
9697
filter,
9798
error:auditsQuery.error,
9899
menus:{
99100
user:userMenu,
100101
action:actionMenu,
101102
resourceType:resourceTypeMenu,
102-
organization:isMultiOrg ?organizationsMenu :undefined,
103+
organization:canViewOrganizations ?organizationsMenu :undefined,
103104
},
104105
}}
105106
/>

‎site/src/pages/DeploySettingsPage/DeploySettingsLayout.tsx‎

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import { Stack } from "components/Stack/Stack";
99
import{useAuthenticated}from"contexts/auth/RequireAuth";
1010
import{RequirePermission}from"contexts/auth/RequirePermission";
1111
import{useDashboard}from"modules/dashboard/useDashboard";
12+
import{useFeatureVisibility}from"modules/dashboard/useFeatureVisibility";
1213
import{ManagementSettingsLayout}from"pages/ManagementSettingsPage/ManagementSettingsLayout";
1314
import{Sidebar}from"./Sidebar";
1415

1516
typeDeploySettingsContextValue={
16-
deploymentValues:DeploymentConfig;
17+
deploymentValues:DeploymentConfig|undefined;
1718
};
1819

1920
exportconstDeploySettingsContext=createContext<
@@ -33,9 +34,11 @@ export const useDeploySettings = (): DeploySettingsContextValue => {
3334
exportconstDeploySettingsLayout:FC=()=>{
3435
const{ experiments}=useDashboard();
3536

36-
constmultiOrgExperimentEnabled=experiments.includes("multi-organization");
37+
constfeats=useFeatureVisibility();
38+
constcanViewOrganizations=
39+
feats.multiple_organizations&&experiments.includes("multi-organization");
3740

38-
returnmultiOrgExperimentEnabled ?(
41+
returncanViewOrganizations ?(
3942
<ManagementSettingsLayout/>
4043
) :(
4144
<DeploySettingsLayoutInner/>
@@ -52,19 +55,15 @@ const DeploySettingsLayoutInner: FC = () => {
5255
<Stackcss={{padding:"48px 0"}}direction="row"spacing={6}>
5356
<Sidebar/>
5457
<maincss={{maxWidth:800,width:"100%"}}>
55-
{deploymentConfigQuery.data ?(
56-
<DeploySettingsContext.Provider
57-
value={{
58-
deploymentValues:deploymentConfigQuery.data,
59-
}}
60-
>
61-
<Suspensefallback={<Loader/>}>
62-
<Outlet/>
63-
</Suspense>
64-
</DeploySettingsContext.Provider>
65-
) :(
66-
<Loader/>
67-
)}
58+
<DeploySettingsContext.Provider
59+
value={{
60+
deploymentValues:deploymentConfigQuery.data,
61+
}}
62+
>
63+
<Suspensefallback={<Loader/>}>
64+
<Outlet/>
65+
</Suspense>
66+
</DeploySettingsContext.Provider>
6867
</main>
6968
</Stack>
7069
</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
importtype{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-
<ExternalAuthSettingsPageViewconfig={deploymentValues.config}/>
17+
{deploymentValues ?(
18+
<ExternalAuthSettingsPageViewconfig={deploymentValues.config}/>
19+
) :(
20+
<Loader/>
21+
)}
1722
</>
1823
);
1924
};

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp