feat(frontend): RBAC permission gating via /authz/me/permissions#13543
feat(frontend): RBAC permission gating via /authz/me/permissions#13543erichare wants to merge 2 commits into
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThis PR introduces a complete frontend RBAC permission-gating layer. It defines permission types and API contracts, implements a React Context-based provider with fail-open semantics, adds a backend query hook, and applies permission checks across flow toolbar, folder operations, flow lists, and deployments to gate UI controls based on user permissions. ChangesFrontend Permission-Gating System
Sequence Diagram(s)sequenceDiagram
participant User
participant FlowToolbarOptions
participant PermissionsProvider
participant useGetEffectivePermissions as Query Hook
participant API
participant DeployButton
participant PlaygroundButton
User->>FlowToolbarOptions: Mount toolbar in flow editor
FlowToolbarOptions->>PermissionsProvider: Wrap with resourceType=flow, resourceIds=[currentFlowId]
PermissionsProvider->>useGetEffectivePermissions: Fetch effective permissions
useGetEffectivePermissions->>API: POST /api/v1/authz/me/permissions
API-->>useGetEffectivePermissions: {permissions: {flowId: ["execute", "write"]}}
PermissionsProvider->>PermissionsProvider: buildPermissionMap() normalizes to {flowid: ["execute", "write"]}
PermissionsProvider-->>DeployButton: can(flowId, "execute") = true
PermissionsProvider-->>PlaygroundButton: can(flowId, "execute") = true
DeployButton-->>User: Deploy button enabled
PlaygroundButton-->>User: Run button enabled
User->>DeployButton: Click deploy
DeployButton->>API: Execute deployment
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 7 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (7 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Test Coverage AdvisorNo source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsx`:
- Around line 54-57: The dropdown trigger is being disabled by a share-only
permission check (canShare from usePermissions/can(flowId, "write")), which
blocks all entries; change the gating so canShare only controls publish/share
menu items and not the trigger. Add/derive explicit permissions (e.g., canWrite
or canPublish for share, canExport for export, canEmbed for embed, canMCP for
MCP or at minimum a general canView/canUse flag) and apply those booleans to
each menu item's disabled prop (publish-related items use canShare/canWrite,
export uses canExport, embed uses canEmbed, etc.); ensure the dropdown trigger
(the component that opens the menu) is enabled whenever any of these action
permissions is true (or when a general view/use permission is present) so API
access, export, MCP, and embed are not blocked by canShare.
In
`@src/frontend/src/components/core/flowToolbarComponent/components/flow-toolbar-options.tsx`:
- Around line 20-23: The PermissionsProvider here is missing the domain prop so
toolbar permission checks differ from HomePage; update the PermissionsProvider
invocation (the component named PermissionsProvider that currently passes
resourceType="flow" and resourceIds={currentFlowId ? [currentFlowId] : []}) to
also pass the same domain string used elsewhere for project-scoped checks (e.g.,
domain={`project:${currentProjectId}`} or the equivalent value available in this
component), ensuring the toolbar uses the same domain-scoped permissions as
HomePage.
In
`@src/frontend/src/pages/MainPage/pages/deploymentsPage/components/deployments-content.tsx`:
- Around line 66-69: The resourceIds prop on PermissionsProvider is mapping
deployments to deployment.id without filtering out falsy values; update the
mapping for resourceIds (inside the PermissionsProvider usage near
deployments-content.tsx) to defensively coerce or filter out
null/undefined/empty ids (i.e., map each deployment to deployment.id ?? "" or
similar and then .filter(Boolean)) so PermissionsProvider only receives valid
non-empty identifiers from the deployments array.
In `@src/frontend/src/pages/MainPage/pages/homePage/index.tsx`:
- Around line 326-329: PermissionsProvider is only receiving a domain when
folderId is present, causing permission checks to be unscoped on the implicit
default collection; change the domain prop so it uses the same collection
resolution as the page load (use folderId ?? myCollectionId) - e.g., set domain
to `project:${folderId ?? myCollectionId}` when appropriate instead of passing
undefined, keeping resourceType="flow" and resourceIds={data.flows.map(f=>f.id)}
intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 6eaa2884-d576-4fd7-9316-161966e6e6c1
📒 Files selected for processing (20)
src/frontend/src/components/core/flowToolbarComponent/components/deploy-button.tsxsrc/frontend/src/components/core/flowToolbarComponent/components/deploy-dropdown.tsxsrc/frontend/src/components/core/flowToolbarComponent/components/flow-toolbar-options.tsxsrc/frontend/src/components/core/flowToolbarComponent/components/playground-button.tsxsrc/frontend/src/components/core/folderSidebarComponent/components/sideBarFolderButtons/components/select-options.tsxsrc/frontend/src/components/core/folderSidebarComponent/components/sideBarFolderButtons/index.tsxsrc/frontend/src/contexts/__tests__/permissionsContext.test.tsxsrc/frontend/src/contexts/permissionsContext.tsxsrc/frontend/src/controllers/API/helpers/constants.tssrc/frontend/src/controllers/API/queries/permissions/__tests__/use-get-effective-permissions.test.tssrc/frontend/src/controllers/API/queries/permissions/index.tssrc/frontend/src/controllers/API/queries/permissions/use-get-effective-permissions.tssrc/frontend/src/pages/MainPage/components/dropdown/index.tsxsrc/frontend/src/pages/MainPage/components/list/index.tsxsrc/frontend/src/pages/MainPage/pages/deploymentsPage/components/deployments-content.tsxsrc/frontend/src/pages/MainPage/pages/deploymentsPage/components/deployments-table.tsxsrc/frontend/src/pages/MainPage/pages/homePage/index.tsxsrc/frontend/src/types/permissions/index.tssrc/frontend/src/utils/__tests__/permissionUtils.test.tssrc/frontend/src/utils/permissionUtils.ts
The backend POST /api/v1/authz/me/permissions endpoint had zero frontend consumers, so the UI showed every action regardless of the user's RBAC permissions — denials surfaced only as failed requests. This adds the OSS permission-gating primitive that consumes it. - useGetEffectivePermissions: typed React Query hook for the batch endpoint. Modeled as a cached query keyed by the requested resource set (POST body); caps resource_ids at 500 and omits actions/domain when defaulted. - PermissionsProvider context + usePermissions().can(id, action). Providers are mounted per list/surface (homePage, folder sidebar, deployments-content, flow toolbar); leaf components are pure consumers. - Gating applied to flows (Edit/Export/Duplicate/Delete + drag-to-move and the canvas Run/Share/Deploy), projects/folders (Rename/Download/Delete), and deployments (Test/Update/Delete). - Fail-open by design: when permission data is absent (loading, errored, or no provider) or authz is off (OSS pass-through returns every action for every id), all controls stay enabled — no change for non-RBAC installs. - Standalone tests for the pure gating logic, the hook request shape, and provider/component gating (fail-open default, denied-action gating, and that a gated control does not fire its handler). The Share modal (3.9) and Roles/Audit admin UIs (4.6/4.7) are FE(EE) and out of scope; this builds the OSS primitive those EE surfaces also depend on. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- deploy-dropdown: gate only the publish controls on write permission; the Share menu trigger stays enabled so API access, export, MCP, and embed remain available to read-only users - flow-toolbar-options: scope the toolbar permissions query to the flow's project domain, matching the HomePage list scoping - deployments-content: defensively filter falsy deployment ids before passing them to the permissions query - homePage: scope default-collection permissions to myCollectionId so the implicit route evaluates the same project domain as the explicit folder route
4c992ca to
45635cd
Compare
Problem
The backend
POST /api/v1/authz/me/permissionsendpoint is implemented (authz_me.py, backed byget_effective_permissions) and returns{ resource_id: [allowed_actions] }, but the frontend had zero consumers — a repo-wide search ofsrc/frontend/srcfinds no reference to the endpoint or any RBAC permission concept. The six components undersrc/frontend/src/components/authorization/are pre-existing authentication guards (login/admin/settings/store/playground), not RBAC permission gates.Result: even with an EE enforcer active, the UI showed every action regardless of the user's permissions; users discovered denials only via a failed request.
This PR adds the OSS permission-gating primitive that consumes the endpoint. The EE Share modal (3.9) and Roles/Audit admin UIs (4.6/4.7) are out of scope and build on this primitive.
What changed
Data layer
types/permissions/index.ts— types mirroring the backend contract (EffectivePermissionsRequest/Response, resource-type + action vocabularies).controllers/API/queries/permissions/use-get-effective-permissions.ts— typed React Query hook for the batch endpoint. Modeled as a cached query (not a mutation): it is a side-effect-free batch read keyed by the requested resource set, so each list fetches its visible-resource permissions once and the gate reads from cache. Capsresource_idsat 500 (matches the backend), omitsactions/domainwhen defaulted, and is disabled when there are no ids.controllers/API/helpers/constants.ts— registers theauthz/me/permissionsURL.Gating logic (pure, framework-free → unit-testable)
utils/permissionUtils.ts—buildPermissionMap(case-normalizing) +canPerformAction. Fail-open: returnstruewhen data is absent (loading / errored / no provider) or the resource was not evaluated; a present resource is gated strictly against its returned action list (an empty list denies everything).Distribution
contexts/permissionsContext.tsx—PermissionsProviderbatches one query for all resource ids in a list/surface and exposesusePermissions().can(resourceId, action). With no provider mounted,canis the fail-open default. Providers are mounted in containers (homePage, folder sidebar,deployments-content, flow toolbar); leaf components stay pure consumers.Gated affordances
write, Export→read, Duplicate→create, Delete→delete, drag-to-Move→writeexecute, Share→write, Deploy→executewrite, Download→read, Delete→deleteexecute, Update→write, Delete→deleteGraceful default (non-RBAC installs)
The OSS pass-through
batch_enforcereturnsTruefor every request, soget_effective_permissionsreturns every requested action for every id. Gating on membership in that list therefore keeps all controls enabled when authz is off. Combined with fail-open on missing/loading/error data, no behavior changes for non-RBAC installs.Acceptance criteria
disabled) and don't fire requests (covered by a provider+component test).Test plan
New standalone tests (21 cases, all green):
utils/__tests__/permissionUtils.test.ts— map building + fail-open/strict gating + case-insensitivity + empty-list deny-all.contexts/__tests__/permissionsContext.test.tsx— no-provider fail-open, pass-through all-enabled, denied gating, loading fail-open, and a component test asserting a denied control is disabled and its handler never fires.controllers/API/queries/permissions/__tests__/use-get-effective-permissions.test.ts— POST body shape, defaulted-field omission, the no-ids skip, and the 500-id cap.Regression sweep over every touched directory: 886 tests / 66 suites pass, including the existing
deployments-tableanddeploy-buttonsuites. New files are biome-clean and tsc-clean (biome no-anypre-commit hook passes).🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes