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)