Skip to content

Add RBAC management UI with alpha feature flag#7284

Open
thabofletcher wants to merge 19 commits intomainfrom
rbac-ui
Open

Add RBAC management UI with alpha feature flag#7284
thabofletcher wants to merge 19 commits intomainfrom
rbac-ui

Conversation

@thabofletcher
Copy link
Contributor

@thabofletcher thabofletcher commented Feb 1, 2026

Ticket: N/A - Internal infrastructure for RBAC system

Description Of Changes

Adds the admin UI components for the new database-driven RBAC system, protected by the alphaRbac feature flag (disabled by default).

Related PRs (merge order):

  1. #7296 - feat: add pluggable permission checker for RBAC extensibility (rbac-api)
  2. #7285 - RBAC DB migration (rbac-migration)
  3. This PR (Add RBAC management UI with alpha feature flag #7284 - rbac-ui)
  4. fidesplus#3028 - feat: Add RBAC system with database-driven roles and permissions (API endpoints)

Testing: Full end-to-end testing should be done with fidesplus#3028 after PRs #7296 and #7285 are merged.

Safe to merge independently: Yes. The UI is protected by the alphaRbac feature flag which is disabled by default (development: true, test: false, production: false). Users will not see any changes until the flag is explicitly enabled.

Code Changes

  • Add alphaRbac feature flag (development only, follows alpha naming convention)
  • Add TypeScript types for RBAC roles, permissions, constraints, and evaluation
  • Add RBAC RTK Query slice with all API endpoints (/api/v1/plus/rbac/*)
  • Add RBAC tag types to api.slice.ts for cache invalidation
  • Add RBAC routes to navigation config (requires Plus license + feature flag)
  • Add RBAC pages:
    • /settings/rbac - Role management dashboard
    • /settings/rbac/roles/new - Create custom role
    • /settings/rbac/roles/[id] - Edit role and permissions
  • Integrate RBAC into user management:
    • UserManagementTabs.tsx - Shows "Roles" tab instead of "Permissions" when RBAC enabled
    • RolesForm.tsx - New role assignment form using RBAC APIs
    • useUserManagementTable.tsx - Displays RBAC roles in user table
    • PermissionsForm.tsx - Conditionally hides legacy permission selector
  • Add CommonSubscriptions.tsx integration to prefetch RBAC permissions

Steps to Confirm

  1. Verify feature flag is disabled by default: check flags.json has alphaRbac.test: false
  2. Run frontend tests: cd clients && npm run test:ci
  3. Run lints: cd clients && npm run lint:fix
  4. Verify existing cypress tests pass (they look for "Permissions" tab which is visible when flag disabled)

To test with RBAC enabled:

  1. Merge PRs feat: add pluggable permission checker for RBAC extensibility #7296 and RBAC DB migration #7285 first
  2. Start fidesplus with RBAC enabled: FIDESPLUS__RBAC__ENABLED=true
  3. Enable feature flag in browser console:
    localStorage.setItem('featureFlags', JSON.stringify({alphaRbac: true}));
    location.reload();
  4. Navigate to Settings > Role Management

Pre-Merge Checklist

  • Issue requirements met
  • All CI pipelines succeeded
  • CHANGELOG.md updated
    • Add a db-migration This indicates that a change includes a database migration label to the entry if your change includes a DB migration
    • Add a high-risk This issue suggests changes that have a high-probability of breaking existing code label to the entry if your change includes a high-risk change (i.e. potential for performance impact or unexpected regression) that should be flagged
    • Updates unreleased work already in Changelog, no new entry necessary
  • UX feedback:
    • All UX related changes have been reviewed by a designer
    • No UX review needed (alpha feature, internal only)
  • Followup issues:
    • Followup issues created
    • No followup issues
  • Database migrations:
    • Ensure that your downrev is up to date with the latest revision on main
    • Ensure that your downgrade() migration is correct and works
      • If a downgrade migration is not possible for this change, please call this out in the PR description!
    • No migrations
  • Documentation:
    • Documentation complete, PR opened in fidesdocs
    • Documentation issue created in fidesdocs
    • If there are any new client scopes created as part of the pull request, remember to update public-facing documentation that references our scope registry
    • No documentation updates required (alpha feature behind flag)

This commit adds the admin UI components for the new database-driven RBAC system:

- Add rbacManagement feature flag (development/test only)
- Add TypeScript types for RBAC roles, permissions, constraints, and evaluation
- Add RBAC RTK Query slice with all API endpoints
- Add RBAC tag types to api.slice.ts
- Add RBAC routes to navigation config
- Add RBAC pages:
  - /settings/rbac - Role management dashboard
  - /settings/rbac/roles/new - Create custom role
  - /settings/rbac/roles/[id] - Edit role and permissions

The RBAC management feature is protected by the rbacManagement feature flag
and requires Plus license. UI supports:
- Viewing all roles (system and custom)
- Creating new custom roles with optional parent inheritance
- Editing role details and permissions
- Permission matrix with inherited permissions display
- Deleting custom roles (system roles protected)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link
Contributor

vercel bot commented Feb 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
fides-plus-nightly Ready Ready Preview, Comment Feb 3, 2026 3:30am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
fides-privacy-center Ignored Ignored Feb 3, 2026 3:30am

Request Review

When the rbacManagement feature flag is enabled, the Users page now:
- Fetches roles from the RBAC system instead of using legacy permissions
- Creates RBAC user role assignments when saving
- Removes RBAC role assignments when roles are deselected
- Falls back to legacy permission system when flag is disabled

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move useMemo hooks (initialValues, targetUserIsOwner) before early returns
to comply with React's Rules of Hooks. Hooks must be called unconditionally
and in the same order on every render.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The UserPermissionsCell component now checks the rbacManagement feature
flag and fetches user roles from RBAC API instead of legacy permissions
when enabled. Falls back to legacy permissions when disabled.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The updatePermissionsRbac function was not saving managed systems,
causing system assignments to not persist. Added the same system
saving logic that exists in updatePermissionsLegacy.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Change scopes from USER_PERMISSION_UPDATE/READ to USER_PERMISSION_ASSIGN_OWNERS
to ensure only Owners can access the RBAC management UI.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
thabofletcher and others added 3 commits February 1, 2026 16:05
- Create new RolesForm.tsx that displays database roles (including custom roles)
- Update UserManagementTabs to conditionally show "Roles" tab when RBAC enabled
- Tab uses RolesForm (RBAC) or PermissionsForm (legacy) based on feature flag
- Fix Tag color values to use valid fidesui color names
- RolesForm shows role descriptions, system/custom badges, and handles role assignment

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add system management hooks (useGetUserManagedSystemsQuery, useUpdateUserManagedSystemsMutation)
- Add AssignSystemsModal and AssignSystemsDeleteTable components
- Show "Assign systems +" button when a role that supports system assignment is selected
- Save system assignments along with role changes
- Define ROLES_WITHOUT_SYSTEM_ASSIGNMENT for roles that cannot have systems assigned

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Stop event propagation on checkbox click to prevent double-toggle
when clicking the checkbox (card onClick was also firing).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
thabofletcher and others added 2 commits February 1, 2026 20:02
- Add useGetMyRBACPermissionsQuery to rbac.slice.ts for fetching user's RBAC permissions
- Update CommonSubscriptions to fetch RBAC permissions when rbacManagement flag is enabled
- Update selectThisUsersScopes selector to prefer RBAC permissions when available

This ensures the UI shows correct navigation items based on RBAC-derived
permissions rather than legacy role-based permissions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add read-only mode to SystemInformationForm for users without SYSTEM_UPDATE
  permission, preventing accidental API errors and providing clear UX feedback
- Add warning alert in RBAC role detail page explaining that system:read
  permission currently grants access to ALL systems (no per-system filtering)
- Wrap form fields in fieldset to automatically disable all inputs when read-only
- Hide save button when user doesn't have update permissions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add curly braces to single-line if statements
- Replace for-of loops with Promise.all for async operations
- Fix no-param-reassign by using spread operator in reducers
- Add aria-labels to form controls for accessibility
- Replace anchor element with Button for proper semantics
- Escape apostrophe in JSX text
- Fix prettier formatting issues

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
thabofletcher and others added 4 commits February 2, 2026 18:09
The rbacManagement feature flag was set to test: true, which caused the
UserManagementTabs component to display "Roles" instead of "Permissions".
This broke existing cypress tests that look for the "Permissions" tab.

Setting test: false ensures existing tests pass while RBAC development
continues behind the feature flag.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rename feature flag from rbacManagement to alphaRbac to follow alpha
  naming convention (like alphaPrivacyNoticesSandbox)
- Remove "Management" from name since RBAC covers roles, permissions,
  and user assignments - not just role management
- Remove "Fine-grained per-system access control is not yet implemented"
  sentence from system permissions alert

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Clarify that all system:* permissions (create, read, update, delete)
apply globally, not just system:read. Point users to system_manager
permissions for per-system access control.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@thabofletcher thabofletcher requested review from lucanovera and removed request for a team February 3, 2026 03:13
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 3, 2026

Greptile Overview

Greptile Summary

This PR adds an alpha-gated RBAC management UI to the admin client, including:

  • A new alphaRbac feature flag (dev-only enabled by default) and gated navigation/routes under /settings/rbac.
  • New RBAC API model types and an RTK Query slice for /api/v1/plus/rbac/* endpoints (roles, permissions, constraints, assignments, evaluation).
  • New pages for role listing, role creation, and role editing/permission assignment.
  • Integration into user management so the UI can display RBAC-derived roles/permissions and prefetch effective permissions.

Most changes fit cleanly into existing patterns (feature-flagged nav, RTK Query baseApi injection, separate pages under pages/settings). The main correctness risks are in the “bridge” logic that mixes RBAC data into legacy permission/role flows (selectors and PermissionsForm), where edge cases can cause incorrect fallback behavior or type mismatches.

Confidence Score: 3/5

  • This PR is reasonably safe to merge behind the alpha feature flag, but has a few correctness/UX edge cases worth fixing first.
  • Core additions (flag, routes, RTK slice, pages) follow established patterns and are gated by alphaRbac. However, the RBAC/legacy interop has at least one real correctness risk (empty RBAC permission set falling back to legacy scopes) and a couple of places where role keys/types can drift (custom roles vs RoleRegistryEnum, memoization staleness).
  • clients/admin-ui/src/features/user-management/user-management.slice.ts; clients/admin-ui/src/features/user-management/PermissionsForm.tsx; clients/admin-ui/src/features/user-management/useUserManagementTable.tsx; clients/admin-ui/src/pages/settings/rbac/roles/new.tsx

Important Files Changed

Filename Overview
changelog/7284.yaml Adds changelog entry for RBAC UI alpha feature.
clients/admin-ui/src/features/common/CommonSubscriptions.tsx Prefetches RBAC effective permissions when alphaRbac flag is enabled.
clients/admin-ui/src/features/common/nav/nav-config.tsx Adds Role Management nav entry gated by Plus + alphaRbac flag + owner-assign scope.
clients/admin-ui/src/features/rbac/rbac.slice.ts Introduces RBAC RTK Query endpoints for roles, permissions, constraints, user-role assignments, and my effective permissions.
clients/admin-ui/src/features/system/SystemInformationForm.tsx Refactors system form to support read-only mode based on SYSTEM_UPDATE scope; adds data steward assignment sync logic and alerts.
clients/admin-ui/src/features/user-management/PermissionsForm.tsx Adds RBAC-mode logic to role assignment within legacy PermissionsForm; risk of type mismatch and inconsistent role-key handling.
clients/admin-ui/src/features/user-management/useUserManagementTable.tsx Updates user table permissions cell to show RBAC-derived role labels when alphaRbac is enabled; memo deps may become stale.
clients/admin-ui/src/features/user-management/user-management.slice.ts Updates selectors to optionally source current user scopes from RBAC effective-permissions cache when alphaRbac is enabled.
clients/admin-ui/src/flags.json Adds alphaRbac feature flag (dev-only enabled) to gate RBAC UI.
clients/admin-ui/src/pages/settings/rbac/index.tsx Adds RBAC Role Management table page with create/delete actions; delete loading is global.
clients/admin-ui/src/pages/settings/rbac/roles/[id].tsx Adds role detail/edit page with permission assignment UI; permission grouping reducer is inefficient.
clients/admin-ui/src/types/api/index.ts Exports new RBAC API model types from central types index.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

1. Use proper RTK Query selector instead of fragile string-based cache key
   lookup in selectThisUsersScopes. No circular import risk exists.

2. Add "RBAC Roles" to providesTags for getMyRBACPermissions so user
   permissions are invalidated when role definitions change.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@thabofletcher
Copy link
Contributor Author

Addressed both Greptile comments in commit 94ef4f2:

  1. user-management.slice.ts: Fixed the fragile string-based cache key lookup. Investigated and confirmed there's no circular import risk - other files in user-management already import from rbac.slice. Now using the proper RTK Query selector pattern.

  2. rbac.slice.ts: Added "RBAC Roles" to providesTags for getMyRBACPermissions so user permissions are invalidated when role definitions change.

@thabofletcher
Copy link
Contributor Author

@greptileai

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

12 files reviewed, 7 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 241 to +254
export const selectThisUsersScopes: (state: RootState) => ScopeRegistryEnum[] =
createSelector([(RootState) => RootState, selectUser], (RootState, user) => {
if (!user) {
return emptyScopes;
}
const permissions = userApi.endpoints.getUserPermissions.select(user.id)(
RootState,
).data;
createSelector(
[(RootState) => RootState, selectUser, selectEnvFlags],
(RootState, user, flags) => {
// When RBAC is enabled, prefer RBAC-derived permissions
if (flags.alphaRbac) {
const rbacPermissions =
rbacApi.endpoints.getMyRBACPermissions.select(undefined)(
RootState,
)?.data;

if (rbacPermissions && rbacPermissions.length > 0) {
return rbacPermissions as ScopeRegistryEnum[];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] selectThisUsersScopes treats an empty RBAC permission set as “not loaded” and silently falls back to legacy permissions, which can grant UI access when RBAC is enabled but the user legitimately has 0 permissions (or the RBAC endpoint returns []). This is most likely for brand-new users/roles or when the RBAC service is up but returns no scopes.

Concretely, the gate if (rbacPermissions && rbacPermissions.length > 0) means [] will fall back to user/{id}/permission, potentially rendering screens based on legacy scopes even though RBAC is enabled.

Suggestion: distinguish “query not loaded / errored” from “loaded empty”, and if RBAC is enabled + query succeeded, return the RBAC permissions even if empty.

Also appears in: clients/admin-ui/src/features/common/CommonSubscriptions.tsx (RBAC prefetch assumes selector uses cache).

Comment on lines +137 to +148
// Use RBAC roles when enabled, otherwise fall back to legacy permissions
// Must be defined here (before early returns) to satisfy React's Rules of Hooks
const initialValues = useMemo(() => {
if (isRbacEnabled) {
return currentRbacRoleKeys.length > 0
? { roles: currentRbacRoleKeys as RoleRegistryEnum[] }
: defaultInitialValues;
}
return userPermissions?.roles
? { roles: userPermissions.roles }
: defaultInitialValues;
}, [isRbacEnabled, currentRbacRoleKeys, userPermissions?.roles]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] In RBAC mode, initialValues casts role keys to RoleRegistryEnum[] (currentRbacRoleKeys as RoleRegistryEnum[]). This is unsound: RBAC supports custom roles whose key is not in RoleRegistryEnum, and even for system roles this relies on string compatibility. It can lead to form state/type mismatches and incorrect filtering/selection behavior.

This is most visible if a user has a custom role assigned: the form will still treat it as a RoleRegistryEnum and downstream logic (e.g., role filtering and RoleOption rendering keyed off ROLES) won’t represent it correctly.

Consider either (a) keeping a separate RBAC-only form value type of string[] role keys, or (b) gating this legacy form entirely when alphaRbac is on (since RolesForm exists).

Comment on lines +74 to +83
return permissions.reduce<Record<string, RBACPermission[]>>((acc, perm) => {
const resourceType = perm.resource_type || "general";
const result = { ...acc };
if (!result[resourceType]) {
result[resourceType] = [];
}
result[resourceType].push(perm);
return result;
}, {});
}, [permissions]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] groupedPermissions rebuilds a new object on every reduce iteration via const result = { ...acc }, which is unnecessarily expensive and defeats the point of reducing.

You can safely mutate the accumulator in a reducer here (since it’s local) to avoid O(n^2) object copying. Also, this pattern makes it harder to preserve stable references for memoization.

This same pattern shows up in a few other new reducers/maps in this PR (e.g., building role maps) where mutation would be clearer and cheaper.

Comment on lines +112 to +133
{
title: "Actions",
key: "actions",
render: (_: unknown, record: RBACRole) => (
<Space>
<Button
size="small"
onClick={() => router.push(`/settings/rbac/roles/${record.id}`)}
>
Edit
</Button>
{!record.is_system_role && (
<Button
size="small"
danger
onClick={() => handleDeleteRole(record)}
loading={isDeleting}
>
Delete
</Button>
)}
</Space>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] isDeleting is a single mutation-level loading flag but it’s used for every row’s Delete button. If you click delete on one role, all Delete buttons will show loading (and will stay loading if multiple deletes are triggered). This is confusing UX and can also allow accidental double-click deletes on other rows.

Consider tracking the deletingRoleId locally (or using originalArgs from the mutation state) so only the clicked row shows a spinner/disabled state.

Comment on lines +30 to +39
// Filter out system roles from parent options
const parentRoleOptions = useMemo(() => {
if (!allRoles) {
return [];
}
return allRoles.map((r) => ({
label: `${r.name} (${r.key})`,
value: r.id,
}));
}, [allRoles]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Comment says “Filter out system roles from parent options”, but the implementation includes all roles (return allRoles.map(...)). If system roles should not be selectable as parents, this currently violates that rule.

This becomes user-visible when creating a custom role: system roles will appear in the Parent Role dropdown despite the comment/intent.

If system roles are actually allowed as parents, the comment should be corrected to avoid misleading future changes.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 3, 2026

Additional Comments (2)

clients/admin-ui/src/features/user-management/useUserManagementTable.tsx
[P1] The columns useMemo omits several dependencies that affect rendering. It only depends on [canUserDelete, canUserUpdate, loggedInUser], but the columns closure also captures USER_MANAGEMENT_ROUTE (constant), and more importantly UserPermissionsCell behavior depends on alphaRbac (via useFlags inside the cell) and RBAC query data. Because columns won’t be regenerated when the feature flag flips at runtime, the table can show stale “Permissions” labels or renderers until a reload.

Even if flags typically change only on reload, the codebase supports toggling flags via localStorage; this makes the missing dependency observable.

Suggestion: include flags.alphaRbac in the memo deps (or avoid memoizing columns based on runtime flags), and consider whether title: "Permissions" should reflect RBAC mode (e.g. “Roles”).


clients/admin-ui/src/features/system/SystemInformationForm.tsx
[P3] handleResult calls toast.closeAll() on success, which can hide unrelated error/warning toasts from other async operations (e.g., the new data steward assignment warnings emitted later in this handler). This is especially noticeable because this function now does multiple independent async operations after save.

Prefer closing only the toast(s) you created for this submission, or avoid closing all toasts globally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant