mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
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
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { checkAuthorization } from "api/api"
|
||||
|
||||
export const useReadPagePermissions = (
|
||||
resource_type: string,
|
||||
resource_id?: string,
|
||||
enabled = true,
|
||||
) => {
|
||||
const queryKey = ["readPagePermissions", resource_type, resource_id]
|
||||
const params = {
|
||||
checks: {
|
||||
readPagePermissions: {
|
||||
object: {
|
||||
resource_type,
|
||||
resource_id,
|
||||
},
|
||||
action: "read",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return useQuery({
|
||||
queryKey,
|
||||
queryFn: () => checkAuthorization(params),
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
@@ -48,6 +48,10 @@ describe("AuditPage", () => {
|
||||
const mock = jest.spyOn(CreateDayString, "createDayString")
|
||||
mock.mockImplementation(() => "a minute ago")
|
||||
|
||||
jest.spyOn(API, "checkAuthorization").mockResolvedValue({
|
||||
readPagePermissions: true,
|
||||
})
|
||||
|
||||
// Mock the entitlements
|
||||
server.use(
|
||||
rest.get("/api/v2/entitlements", (req, res, ctx) => {
|
||||
|
||||
@@ -11,8 +11,15 @@ import { pageTitle } from "util/page"
|
||||
import { auditMachine } from "xServices/audit/auditXService"
|
||||
import { PaginationMachineRef } from "xServices/pagination/paginationXService"
|
||||
import { AuditPageView } from "./AuditPageView"
|
||||
import { RequirePermission } from "components/RequirePermission/RequirePermission"
|
||||
import { useReadPagePermissions } from "hooks/useReadPagePermissions"
|
||||
import { Loader } from "components/Loader/Loader"
|
||||
|
||||
const AuditPage: FC = () => {
|
||||
// we call the below hook to make sure the user has access to view the page
|
||||
const { data: permissions, isLoading: isLoadingPermissions } =
|
||||
useReadPagePermissions("audit_log")
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
const filter = searchParams.get("filter") ?? ""
|
||||
const [auditState, auditSend] = useMachine(auditMachine, {
|
||||
@@ -28,26 +35,34 @@ const AuditPage: FC = () => {
|
||||
|
||||
const { auditLogs, count, apiError } = auditState.context
|
||||
const paginationRef = auditState.context.paginationRef as PaginationMachineRef
|
||||
const { audit_log: isAuditLogVisible } = useFeatureVisibility()
|
||||
const { audit_log: isAuditLogEnabled } = useFeatureVisibility()
|
||||
|
||||
if (!permissions || isLoadingPermissions) {
|
||||
return <Loader />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Audit")}</title>
|
||||
</Helmet>
|
||||
<AuditPageView
|
||||
filter={filter}
|
||||
auditLogs={auditLogs}
|
||||
count={count}
|
||||
onFilter={(filter) => {
|
||||
auditSend("FILTER", { filter })
|
||||
}}
|
||||
paginationRef={paginationRef}
|
||||
isNonInitialPage={nonInitialPage(searchParams)}
|
||||
isAuditLogVisible={isAuditLogVisible}
|
||||
error={apiError}
|
||||
/>
|
||||
</>
|
||||
<RequirePermission
|
||||
isFeatureVisible={isAuditLogEnabled && permissions.readPagePermissions}
|
||||
>
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Audit")}</title>
|
||||
</Helmet>
|
||||
<AuditPageView
|
||||
filter={filter}
|
||||
auditLogs={auditLogs}
|
||||
count={count}
|
||||
onFilter={(filter) => {
|
||||
auditSend("FILTER", { filter })
|
||||
}}
|
||||
paginationRef={paginationRef}
|
||||
isNonInitialPage={nonInitialPage(searchParams)}
|
||||
isAuditLogVisible={isAuditLogEnabled}
|
||||
error={apiError}
|
||||
/>
|
||||
</>
|
||||
</RequirePermission>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ const { t } = i18next
|
||||
|
||||
// It renders the workspace page and waits for it be loaded
|
||||
const renderWorkspacePage = async () => {
|
||||
jest.spyOn(api, "checkAuthorization").mockResolvedValue({
|
||||
readPagePermissions: true,
|
||||
})
|
||||
jest.spyOn(api, "getTemplate").mockResolvedValueOnce(MockTemplate)
|
||||
jest.spyOn(api, "getTemplateVersionRichParameters").mockResolvedValueOnce([])
|
||||
renderWithAuth(<WorkspacePage />, {
|
||||
@@ -191,6 +194,9 @@ describe("WorkspacePage", () => {
|
||||
it("updates the parameters when they are missing during update", async () => {
|
||||
// Setup mocks
|
||||
const user = userEvent.setup()
|
||||
jest.spyOn(api, "checkAuthorization").mockResolvedValue({
|
||||
readPagePermissions: true,
|
||||
})
|
||||
jest
|
||||
.spyOn(api, "getWorkspaceByOwnerAndName")
|
||||
.mockResolvedValueOnce(MockOutdatedWorkspace)
|
||||
|
||||
@@ -6,11 +6,14 @@ import { Loader } from "components/Loader/Loader"
|
||||
import { FC, useEffect } from "react"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { firstOrItem } from "util/array"
|
||||
import { quotaMachine } from "xServices/quotas/quotasXService"
|
||||
import { workspaceMachine } from "xServices/workspace/workspaceXService"
|
||||
import { WorkspaceReadyPage } from "./WorkspaceReadyPage"
|
||||
import { quotaMachine } from "xServices/quotas/quotasXService"
|
||||
import { RequirePermission } from "components/RequirePermission/RequirePermission"
|
||||
import { useReadPagePermissions } from "hooks/useReadPagePermissions"
|
||||
|
||||
export const WorkspacePage: FC = () => {
|
||||
const styles = useStyles()
|
||||
const { username: usernameQueryParam, workspace: workspaceQueryParam } =
|
||||
useParams()
|
||||
const username = firstOrItem(usernameQueryParam, null)
|
||||
@@ -23,9 +26,13 @@ export const WorkspacePage: FC = () => {
|
||||
getTemplateParametersWarning,
|
||||
checkPermissionsError,
|
||||
} = workspaceState.context
|
||||
|
||||
// we call the below hook to make sure the user has access to view the page
|
||||
const { data: permissions, isLoading: isLoadingPermissions } =
|
||||
useReadPagePermissions("workspace", workspace?.id)
|
||||
|
||||
const [quotaState, quotaSend] = useMachine(quotaMachine)
|
||||
const { getQuotaError } = quotaState.context
|
||||
const styles = useStyles()
|
||||
|
||||
/**
|
||||
* Get workspace, template, and organization on mount and whenever workspaceId changes.
|
||||
@@ -41,47 +48,53 @@ export const WorkspacePage: FC = () => {
|
||||
username && quotaSend({ type: "GET_QUOTA", username })
|
||||
}, [username, quotaSend])
|
||||
|
||||
if (!permissions || isLoadingPermissions) {
|
||||
return <Loader />
|
||||
}
|
||||
|
||||
return (
|
||||
<ChooseOne>
|
||||
<Cond condition={workspaceState.matches("error")}>
|
||||
<div className={styles.error}>
|
||||
{Boolean(getWorkspaceError) && (
|
||||
<AlertBanner severity="error" error={getWorkspaceError} />
|
||||
)}
|
||||
{Boolean(getTemplateWarning) && (
|
||||
<AlertBanner severity="error" error={getTemplateWarning} />
|
||||
)}
|
||||
{Boolean(getTemplateParametersWarning) && (
|
||||
<AlertBanner
|
||||
severity="error"
|
||||
error={getTemplateParametersWarning}
|
||||
/>
|
||||
)}
|
||||
{Boolean(checkPermissionsError) && (
|
||||
<AlertBanner severity="error" error={checkPermissionsError} />
|
||||
)}
|
||||
{Boolean(getQuotaError) && (
|
||||
<AlertBanner severity="error" error={getQuotaError} />
|
||||
)}
|
||||
</div>
|
||||
</Cond>
|
||||
<Cond
|
||||
condition={
|
||||
Boolean(workspace) &&
|
||||
workspaceState.matches("ready") &&
|
||||
quotaState.matches("success")
|
||||
}
|
||||
>
|
||||
<WorkspaceReadyPage
|
||||
workspaceState={workspaceState}
|
||||
quotaState={quotaState}
|
||||
workspaceSend={workspaceSend}
|
||||
/>
|
||||
</Cond>
|
||||
<Cond>
|
||||
<Loader />
|
||||
</Cond>
|
||||
</ChooseOne>
|
||||
<RequirePermission isFeatureVisible={permissions.readPagePermissions}>
|
||||
<ChooseOne>
|
||||
<Cond condition={workspaceState.matches("error")}>
|
||||
<div className={styles.error}>
|
||||
{Boolean(getWorkspaceError) && (
|
||||
<AlertBanner severity="error" error={getWorkspaceError} />
|
||||
)}
|
||||
{Boolean(getTemplateWarning) && (
|
||||
<AlertBanner severity="error" error={getTemplateWarning} />
|
||||
)}
|
||||
{Boolean(getTemplateParametersWarning) && (
|
||||
<AlertBanner
|
||||
severity="error"
|
||||
error={getTemplateParametersWarning}
|
||||
/>
|
||||
)}
|
||||
{Boolean(checkPermissionsError) && (
|
||||
<AlertBanner severity="error" error={checkPermissionsError} />
|
||||
)}
|
||||
{Boolean(getQuotaError) && (
|
||||
<AlertBanner severity="error" error={getQuotaError} />
|
||||
)}
|
||||
</div>
|
||||
</Cond>
|
||||
<Cond
|
||||
condition={
|
||||
Boolean(workspace) &&
|
||||
workspaceState.matches("ready") &&
|
||||
quotaState.matches("success")
|
||||
}
|
||||
>
|
||||
<WorkspaceReadyPage
|
||||
workspaceState={workspaceState}
|
||||
quotaState={quotaState}
|
||||
workspaceSend={workspaceSend}
|
||||
/>
|
||||
</Cond>
|
||||
<Cond>
|
||||
<Loader />
|
||||
</Cond>
|
||||
</ChooseOne>
|
||||
</RequirePermission>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user