Skip to content

Commit 6122df6

Browse files
authored
feature: gate audit log by permissions (#3464)
* pairing * restricting audit route resolvees #3460 * updated tests * fixing lint * useSelector instead of useActor
1 parent 4e6645a commit 6122df6

File tree

7 files changed

+164
-126
lines changed

7 files changed

+164
-126
lines changed

coderd/rbac/builtin.go

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ var (
8888
// Should be able to read all template details, even in orgs they
8989
// are not in.
9090
ResourceTemplate: {ActionRead},
91+
ResourceAuditLog: {ActionRead},
9192
}),
9293
}
9394
},

coderd/rbac/object.go

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ var (
2222
Type: "workspace",
2323
}
2424

25+
// ResourceAuditLog
26+
// read = access audit log
27+
ResourceAuditLog = Object{
28+
Type: "audit_log",
29+
}
30+
2531
// ResourceTemplate CRUD. Org owner only.
2632
// create/delete = Make or delete a new template
2733
// update = Update the template, make new template versions

site/src/AppRouter.tsx

+119-111
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
import { FC, lazy, Suspense } from "react"
1+
import { useSelector } from "@xstate/react"
2+
import { FC, lazy, Suspense, useContext } from "react"
23
import { Navigate, Route, Routes } from "react-router-dom"
4+
import { selectPermissions } from "xServices/auth/authSelectors"
5+
import { XServiceContext } from "xServices/StateContext"
36
import { AuthAndFrame } from "./components/AuthAndFrame/AuthAndFrame"
47
import { RequireAuth } from "./components/RequireAuth/RequireAuth"
58
import { SettingsLayout } from "./components/SettingsLayout/SettingsLayout"
@@ -27,167 +30,172 @@ const WorkspacesPage = lazy(() => import("./pages/WorkspacesPage/WorkspacesPage"
2730
const CreateWorkspacePage = lazy(() => import("./pages/CreateWorkspacePage/CreateWorkspacePage"))
2831
const AuditPage = lazy(() => import("./pages/AuditPage/AuditPage"))
2932

30-
export const AppRouter: FC = () => (
31-
<Suspense fallback={<></>}>
32-
<Routes>
33-
<Route
34-
index
35-
element={
36-
<RequireAuth>
37-
<IndexPage />
38-
</RequireAuth>
39-
}
40-
/>
33+
export const AppRouter: FC = () => {
34+
const xServices = useContext(XServiceContext)
35+
const permissions = useSelector(xServices.authXService, selectPermissions)
4136

42-
<Route path="login" element={<LoginPage />} />
43-
<Route path="healthz" element={<HealthzPage />} />
44-
<Route
45-
path="cli-auth"
46-
element={
47-
<RequireAuth>
48-
<CliAuthenticationPage />
49-
</RequireAuth>
50-
}
51-
/>
52-
53-
<Route path="workspaces">
37+
return (
38+
<Suspense fallback={<></>}>
39+
<Routes>
5440
<Route
5541
index
5642
element={
57-
<AuthAndFrame>
58-
<WorkspacesPage />
59-
</AuthAndFrame>
43+
<RequireAuth>
44+
<IndexPage />
45+
</RequireAuth>
6046
}
6147
/>
62-
</Route>
6348

64-
<Route path="templates">
49+
<Route path="login" element={<LoginPage />} />
50+
<Route path="healthz" element={<HealthzPage />} />
6551
<Route
66-
index
52+
path="cli-auth"
6753
element={
68-
<AuthAndFrame>
69-
<TemplatesPage />
70-
</AuthAndFrame>
54+
<RequireAuth>
55+
<CliAuthenticationPage />
56+
</RequireAuth>
7157
}
7258
/>
7359

74-
<Route path=":template">
60+
<Route path="workspaces">
7561
<Route
7662
index
7763
element={
7864
<AuthAndFrame>
79-
<TemplatePage />
65+
<WorkspacesPage />
8066
</AuthAndFrame>
8167
}
8268
/>
83-
<Route
84-
path="workspace"
85-
element={
86-
<RequireAuth>
87-
<CreateWorkspacePage />
88-
</RequireAuth>
89-
}
90-
/>
9169
</Route>
92-
</Route>
93-
94-
<Route path="users">
95-
<Route
96-
index
97-
element={
98-
<AuthAndFrame>
99-
<UsersPage />
100-
</AuthAndFrame>
101-
}
102-
/>
103-
<Route
104-
path="create"
105-
element={
106-
<RequireAuth>
107-
<CreateUserPage />
108-
</RequireAuth>
109-
}
110-
/>
111-
</Route>
11270

113-
{/* REMARK: Route under construction
114-
Eventually, we should gate this page
115-
with permissions and licensing */}
116-
<Route path="/audit">
117-
<Route
118-
index
119-
element={
120-
process.env.NODE_ENV === "production" ? (
121-
<Navigate to="/workspaces" />
122-
) : (
71+
<Route path="templates">
72+
<Route
73+
index
74+
element={
12375
<AuthAndFrame>
124-
<AuditPage />
76+
<TemplatesPage />
12577
</AuthAndFrame>
126-
)
127-
}
128-
></Route>
129-
</Route>
78+
}
79+
/>
13080

131-
<Route path="settings" element={<SettingsLayout />}>
132-
<Route path="account" element={<AccountPage />} />
133-
<Route path="security" element={<SecurityPage />} />
134-
<Route path="ssh-keys" element={<SSHKeysPage />} />
135-
</Route>
81+
<Route path=":template">
82+
<Route
83+
index
84+
element={
85+
<AuthAndFrame>
86+
<TemplatePage />
87+
</AuthAndFrame>
88+
}
89+
/>
90+
<Route
91+
path="workspace"
92+
element={
93+
<RequireAuth>
94+
<CreateWorkspacePage />
95+
</RequireAuth>
96+
}
97+
/>
98+
</Route>
99+
</Route>
136100

137-
<Route path="/@:username">
138-
<Route path=":workspace">
101+
<Route path="users">
139102
<Route
140103
index
141104
element={
142105
<AuthAndFrame>
143-
<WorkspacePage />
106+
<UsersPage />
144107
</AuthAndFrame>
145108
}
146109
/>
147110
<Route
148-
path="schedule"
111+
path="create"
149112
element={
150113
<RequireAuth>
151-
<WorkspaceSchedulePage />
114+
<CreateUserPage />
152115
</RequireAuth>
153116
}
154117
/>
118+
</Route>
155119

120+
{/* REMARK: Route under construction
121+
Eventually, we should gate this page
122+
with permissions and licensing */}
123+
<Route path="/audit">
156124
<Route
157-
path="terminal"
125+
index
158126
element={
159-
<RequireAuth>
160-
<TerminalPage />
161-
</RequireAuth>
127+
process.env.NODE_ENV === "production" || !permissions?.viewAuditLog ? (
128+
<Navigate to="/workspaces" />
129+
) : (
130+
<AuthAndFrame>
131+
<AuditPage />
132+
</AuthAndFrame>
133+
)
162134
}
163-
/>
135+
></Route>
136+
</Route>
137+
138+
<Route path="settings" element={<SettingsLayout />}>
139+
<Route path="account" element={<AccountPage />} />
140+
<Route path="security" element={<SecurityPage />} />
141+
<Route path="ssh-keys" element={<SSHKeysPage />} />
142+
</Route>
164143

165-
<Route path="apps">
144+
<Route path="/@:username">
145+
<Route path=":workspace">
166146
<Route
167-
path=":app/*"
147+
index
168148
element={
169149
<AuthAndFrame>
170-
<WorkspaceAppErrorPage />
150+
<WorkspacePage />
171151
</AuthAndFrame>
172152
}
173153
/>
174-
</Route>
154+
<Route
155+
path="schedule"
156+
element={
157+
<RequireAuth>
158+
<WorkspaceSchedulePage />
159+
</RequireAuth>
160+
}
161+
/>
175162

176-
<Route
177-
path="builds/:buildNumber"
178-
element={
179-
<AuthAndFrame>
180-
<WorkspaceBuildPage />
181-
</AuthAndFrame>
182-
}
183-
/>
163+
<Route
164+
path="terminal"
165+
element={
166+
<RequireAuth>
167+
<TerminalPage />
168+
</RequireAuth>
169+
}
170+
/>
171+
172+
<Route path="apps">
173+
<Route
174+
path=":app/*"
175+
element={
176+
<AuthAndFrame>
177+
<WorkspaceAppErrorPage />
178+
</AuthAndFrame>
179+
}
180+
/>
181+
</Route>
182+
183+
<Route
184+
path="builds/:buildNumber"
185+
element={
186+
<AuthAndFrame>
187+
<WorkspaceBuildPage />
188+
</AuthAndFrame>
189+
}
190+
/>
191+
</Route>
184192
</Route>
185-
</Route>
186193

187-
{/* Using path="*"" means "match anything", so this route
194+
{/* Using path="*"" means "match anything", so this route
188195
acts like a catch-all for URLs that we don't have explicit
189196
routes for. */}
190-
<Route path="*" element={<NotFoundPage />} />
191-
</Routes>
192-
</Suspense>
193-
)
197+
<Route path="*" element={<NotFoundPage />} />
198+
</Routes>
199+
</Suspense>
200+
)
201+
}

site/src/components/Navbar/Navbar.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ import { NavbarView } from "../NavbarView/NavbarView"
66
export const Navbar: React.FC = () => {
77
const xServices = useContext(XServiceContext)
88
const [authState, authSend] = useActor(xServices.authXService)
9-
const { me } = authState.context
9+
const { me, permissions } = authState.context
1010
const onSignOut = () => authSend("SIGN_OUT")
1111

12-
return <NavbarView user={me} onSignOut={onSignOut} />
12+
return (
13+
<NavbarView
14+
user={me}
15+
onSignOut={onSignOut}
16+
canViewAuditLog={permissions?.viewAuditLog ?? false}
17+
/>
18+
)
1319
}

0 commit comments

Comments
 (0)