Skip to content

Commit fc21e15

Browse files
authored
fix(UI): redirect if user is not permissioned to see workspace (#6786)
* fix(UI): redirect if user is not permissioned to see workspace * fix tests
1 parent 08afe3c commit fc21e15

File tree

5 files changed

+125
-60
lines changed

5 files changed

+125
-60
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useQuery } from "@tanstack/react-query"
2+
import { checkAuthorization } from "api/api"
3+
4+
export const useReadPagePermissions = (
5+
resource_type: string,
6+
resource_id?: string,
7+
enabled = true,
8+
) => {
9+
const queryKey = ["readPagePermissions", resource_type, resource_id]
10+
const params = {
11+
checks: {
12+
readPagePermissions: {
13+
object: {
14+
resource_type,
15+
resource_id,
16+
},
17+
action: "read",
18+
},
19+
},
20+
}
21+
22+
return useQuery({
23+
queryKey,
24+
queryFn: () => checkAuthorization(params),
25+
enabled,
26+
})
27+
}

site/src/pages/AuditPage/AuditPage.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ describe("AuditPage", () => {
4848
const mock = jest.spyOn(CreateDayString, "createDayString")
4949
mock.mockImplementation(() => "a minute ago")
5050

51+
jest.spyOn(API, "checkAuthorization").mockResolvedValue({
52+
readPagePermissions: true,
53+
})
54+
5155
// Mock the entitlements
5256
server.use(
5357
rest.get("/api/v2/entitlements", (req, res, ctx) => {

site/src/pages/AuditPage/AuditPage.tsx

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,15 @@ import { pageTitle } from "util/page"
1111
import { auditMachine } from "xServices/audit/auditXService"
1212
import { PaginationMachineRef } from "xServices/pagination/paginationXService"
1313
import { AuditPageView } from "./AuditPageView"
14+
import { RequirePermission } from "components/RequirePermission/RequirePermission"
15+
import { useReadPagePermissions } from "hooks/useReadPagePermissions"
16+
import { Loader } from "components/Loader/Loader"
1417

1518
const AuditPage: FC = () => {
19+
// we call the below hook to make sure the user has access to view the page
20+
const { data: permissions, isLoading: isLoadingPermissions } =
21+
useReadPagePermissions("audit_log")
22+
1623
const [searchParams, setSearchParams] = useSearchParams()
1724
const filter = searchParams.get("filter") ?? ""
1825
const [auditState, auditSend] = useMachine(auditMachine, {
@@ -28,26 +35,34 @@ const AuditPage: FC = () => {
2835

2936
const { auditLogs, count, apiError } = auditState.context
3037
const paginationRef = auditState.context.paginationRef as PaginationMachineRef
31-
const { audit_log: isAuditLogVisible } = useFeatureVisibility()
38+
const { audit_log: isAuditLogEnabled } = useFeatureVisibility()
39+
40+
if (!permissions || isLoadingPermissions) {
41+
return <Loader />
42+
}
3243

3344
return (
34-
<>
35-
<Helmet>
36-
<title>{pageTitle("Audit")}</title>
37-
</Helmet>
38-
<AuditPageView
39-
filter={filter}
40-
auditLogs={auditLogs}
41-
count={count}
42-
onFilter={(filter) => {
43-
auditSend("FILTER", { filter })
44-
}}
45-
paginationRef={paginationRef}
46-
isNonInitialPage={nonInitialPage(searchParams)}
47-
isAuditLogVisible={isAuditLogVisible}
48-
error={apiError}
49-
/>
50-
</>
45+
<RequirePermission
46+
isFeatureVisible={isAuditLogEnabled && permissions.readPagePermissions}
47+
>
48+
<>
49+
<Helmet>
50+
<title>{pageTitle("Audit")}</title>
51+
</Helmet>
52+
<AuditPageView
53+
filter={filter}
54+
auditLogs={auditLogs}
55+
count={count}
56+
onFilter={(filter) => {
57+
auditSend("FILTER", { filter })
58+
}}
59+
paginationRef={paginationRef}
60+
isNonInitialPage={nonInitialPage(searchParams)}
61+
isAuditLogVisible={isAuditLogEnabled}
62+
error={apiError}
63+
/>
64+
</>
65+
</RequirePermission>
5166
)
5267
}
5368

site/src/pages/WorkspacePage/WorkspacePage.test.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ const { t } = i18next
3131

3232
// It renders the workspace page and waits for it be loaded
3333
const renderWorkspacePage = async () => {
34+
jest.spyOn(api, "checkAuthorization").mockResolvedValue({
35+
readPagePermissions: true,
36+
})
3437
jest.spyOn(api, "getTemplate").mockResolvedValueOnce(MockTemplate)
3538
jest.spyOn(api, "getTemplateVersionRichParameters").mockResolvedValueOnce([])
3639
renderWithAuth(<WorkspacePage />, {
@@ -191,6 +194,9 @@ describe("WorkspacePage", () => {
191194
it("updates the parameters when they are missing during update", async () => {
192195
// Setup mocks
193196
const user = userEvent.setup()
197+
jest.spyOn(api, "checkAuthorization").mockResolvedValue({
198+
readPagePermissions: true,
199+
})
194200
jest
195201
.spyOn(api, "getWorkspaceByOwnerAndName")
196202
.mockResolvedValueOnce(MockOutdatedWorkspace)

site/src/pages/WorkspacePage/WorkspacePage.tsx

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import { Loader } from "components/Loader/Loader"
66
import { FC, useEffect } from "react"
77
import { useParams } from "react-router-dom"
88
import { firstOrItem } from "util/array"
9-
import { quotaMachine } from "xServices/quotas/quotasXService"
109
import { workspaceMachine } from "xServices/workspace/workspaceXService"
1110
import { WorkspaceReadyPage } from "./WorkspaceReadyPage"
11+
import { quotaMachine } from "xServices/quotas/quotasXService"
12+
import { RequirePermission } from "components/RequirePermission/RequirePermission"
13+
import { useReadPagePermissions } from "hooks/useReadPagePermissions"
1214

1315
export const WorkspacePage: FC = () => {
16+
const styles = useStyles()
1417
const { username: usernameQueryParam, workspace: workspaceQueryParam } =
1518
useParams()
1619
const username = firstOrItem(usernameQueryParam, null)
@@ -23,9 +26,13 @@ export const WorkspacePage: FC = () => {
2326
getTemplateParametersWarning,
2427
checkPermissionsError,
2528
} = workspaceState.context
29+
30+
// we call the below hook to make sure the user has access to view the page
31+
const { data: permissions, isLoading: isLoadingPermissions } =
32+
useReadPagePermissions("workspace", workspace?.id)
33+
2634
const [quotaState, quotaSend] = useMachine(quotaMachine)
2735
const { getQuotaError } = quotaState.context
28-
const styles = useStyles()
2936

3037
/**
3138
* Get workspace, template, and organization on mount and whenever workspaceId changes.
@@ -41,47 +48,53 @@ export const WorkspacePage: FC = () => {
4148
username && quotaSend({ type: "GET_QUOTA", username })
4249
}, [username, quotaSend])
4350

51+
if (!permissions || isLoadingPermissions) {
52+
return <Loader />
53+
}
54+
4455
return (
45-
<ChooseOne>
46-
<Cond condition={workspaceState.matches("error")}>
47-
<div className={styles.error}>
48-
{Boolean(getWorkspaceError) && (
49-
<AlertBanner severity="error" error={getWorkspaceError} />
50-
)}
51-
{Boolean(getTemplateWarning) && (
52-
<AlertBanner severity="error" error={getTemplateWarning} />
53-
)}
54-
{Boolean(getTemplateParametersWarning) && (
55-
<AlertBanner
56-
severity="error"
57-
error={getTemplateParametersWarning}
58-
/>
59-
)}
60-
{Boolean(checkPermissionsError) && (
61-
<AlertBanner severity="error" error={checkPermissionsError} />
62-
)}
63-
{Boolean(getQuotaError) && (
64-
<AlertBanner severity="error" error={getQuotaError} />
65-
)}
66-
</div>
67-
</Cond>
68-
<Cond
69-
condition={
70-
Boolean(workspace) &&
71-
workspaceState.matches("ready") &&
72-
quotaState.matches("success")
73-
}
74-
>
75-
<WorkspaceReadyPage
76-
workspaceState={workspaceState}
77-
quotaState={quotaState}
78-
workspaceSend={workspaceSend}
79-
/>
80-
</Cond>
81-
<Cond>
82-
<Loader />
83-
</Cond>
84-
</ChooseOne>
56+
<RequirePermission isFeatureVisible={permissions.readPagePermissions}>
57+
<ChooseOne>
58+
<Cond condition={workspaceState.matches("error")}>
59+
<div className={styles.error}>
60+
{Boolean(getWorkspaceError) && (
61+
<AlertBanner severity="error" error={getWorkspaceError} />
62+
)}
63+
{Boolean(getTemplateWarning) && (
64+
<AlertBanner severity="error" error={getTemplateWarning} />
65+
)}
66+
{Boolean(getTemplateParametersWarning) && (
67+
<AlertBanner
68+
severity="error"
69+
error={getTemplateParametersWarning}
70+
/>
71+
)}
72+
{Boolean(checkPermissionsError) && (
73+
<AlertBanner severity="error" error={checkPermissionsError} />
74+
)}
75+
{Boolean(getQuotaError) && (
76+
<AlertBanner severity="error" error={getQuotaError} />
77+
)}
78+
</div>
79+
</Cond>
80+
<Cond
81+
condition={
82+
Boolean(workspace) &&
83+
workspaceState.matches("ready") &&
84+
quotaState.matches("success")
85+
}
86+
>
87+
<WorkspaceReadyPage
88+
workspaceState={workspaceState}
89+
quotaState={quotaState}
90+
workspaceSend={workspaceSend}
91+
/>
92+
</Cond>
93+
<Cond>
94+
<Loader />
95+
</Cond>
96+
</ChooseOne>
97+
</RequirePermission>
8598
)
8699
}
87100

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