Skip to content

Data Browser sidebar entry shown regardless of assumed role #1124

@eve-scality

Description

@eve-scality

Analysis Report

Root Cause

The Data Browser sidebar entry in InternalRoutes (src/react/Routes.tsx) is rendered unconditionally without any role-based guard. Other sidebar items like 'Locations' and 'Data Services' are gated behind isStorageManager, and 'Truststore' is gated behind isPlatformAdmin && isMetalK8sEnabled, but the Data Browser entry has no such conditional check.

The useAuthGroups() hook (src/react/utils/hooks.ts) that drives sidebar visibility only returns isStorageManager and isPlatformAdmin flags — there is no flag for the storage-consumer/data-consumer role. Meanwhile, the role constants DATA_CONSUMER_ROLE = 'data-consumer-role' and DATA_ACCESSOR_ROLE = 'data-accessor-role' are defined but never used to gate sidebar visibility.

Additionally, the useRolePathName() hook exists and can extract the currently assumed role path/name from the role ARN, but it is not used in the sidebar navigation logic. Furthermore, useRolePathName() depends on useDataServiceRole() which requires the DataServiceRoleProvider context — but the sidebar config is assembled in InternalRoutes which is outside the DataServiceRoleProvider wrapper (the provider only wraps ReauthDialog at line 373), so using role-based checks in the sidebar would require restructuring the provider hierarchy or using a different mechanism.

Affected Component

  • Repo: scality/zenko-ui
  • Path: src/react/Routes.tsx
  • Version: latest

Evidence

  1. src/react/Routes.tsx:331 — The Data Browser sidebar entry is rendered unconditionally — no role check guards its visibility. Compare with 'Locations' (line 343) and 'Data Services' (line 360) which are gated by isStorageManager &&.
          {
            label: 'Data Browser',
            icon: <Icon name="Bucket" />,
            onClick: () => {
              navigate('/buckets');
            },
            active:
              doesRouteMatch('/buckets') ||
              doesRouteMatch('/accounts/:accountName/buckets/*') ||
              doesRouteMatch('/accounts/:accountName/data/buckets/*'),
          },
    
  2. src/react/Routes.tsx:343 — Other sidebar items correctly use boolean short-circuit pattern to conditionally render based on the user's role. The Data Browser entry is missing this pattern.
          isStorageManager && {
            label: 'Locations',
            icon: <Icon name="Location" />,
            onClick: () => {
              navigate('/locations');
            },
            active: doesRouteMatch('/locations'),
          },
    
  3. src/react/utils/hooks.ts:250 — The useAuthGroups hook only exposes isStorageManager and isPlatformAdmin. There is no flag to detect whether the user has assumed a storage-consumer/data-consumer role, so the sidebar logic has no way to check for this role.
    export const useAuthGroups = () => {
      const { useAuth } = useShellHooks();
      const user = useAuth();
      const userGroups = user.userData?.groups || [];
    
      const isStorageManager = userGroups.includes('StorageManager');
      const isPlatformAdmin = userGroups.includes('PlatformAdmin');
    
      return { isStorageManager, isPlatformAdmin };
    };
    
  4. src/react/utils/hooks.ts:138 — Role constants for data-consumer-role and data-accessor-role exist but are not exported or used in any sidebar visibility logic.
    export const STORAGE_MANAGER_ROLE = 'storage-manager-role';
    export const STORAGE_ACCOUNT_OWNER_ROLE = 'storage-account-owner-role';
    const DATA_CONSUMER_ROLE = 'data-consumer-role';
    const DATA_ACCESSOR_ROLE = 'data-accessor-role';
    
  5. src/react/Routes.tsx:268 — InternalRoutes only destructures isStorageManager and isPlatformAdmin from useAuthGroups. No role-based check for the assumed data service role is performed.
      const { isStorageManager, isPlatformAdmin } = useAuthGroups();
    
  6. src/react/Routes.tsx:373 — The DataServiceRoleProvider (which provides the assumed role context via useDataServiceRole) only wraps ReauthDialog — the sidebar config is assembled outside this provider, so useRolePathName() cannot be called in the sidebar construction without restructuring.
          <DataServiceRoleProvider>
            <ReauthDialog />
          </DataServiceRoleProvider>
    

Impact

Low severity. When a user assumes the scality-internal/storage-consumer-role (or data-consumer-role), the Data Browser sidebar entry remains visible even though the role grants no permissions to use it. This leads to a confusing UX where users can navigate to the Data Browser but cannot perform any actions. No data integrity or security issue — the backend correctly enforces permissions — but it degrades user experience.

Confidence

high

Recommendation

Two changes are needed:

  1. Extend role detection: The assumed role needs to be available in the sidebar context. Two approaches:

    • Option A (preferred): Move the DataServiceRoleProvider higher in the component tree so it wraps the sidebar construction in InternalRoutes. Then use useRolePathName() to get the current assumed role and check if it matches data-consumer-role.
    • Option B: Use useAuthGroups() — but this hook checks Keycloak groups, not the assumed IAM role, so it may not be the right mechanism. The assumed role is set per-account via the DataServiceRole context.
  2. Gate the Data Browser entry: Once the assumed role is accessible, add a conditional check similar to the existing pattern:

    !isDataConsumerRole && {
      label: 'Data Browser',
      icon: <Icon name="Bucket" />,
      onClick: () => { navigate('/buckets'); },
      active: doesRouteMatch('/buckets') || ...,
    },
  3. Also consider route-level protection: Add a redirect or guard on the /buckets routes so that even if a user navigates directly via URL, they are redirected away when the assumed role is data-consumer-role.

  4. Export the role constants: Export DATA_CONSUMER_ROLE from hooks.ts (it's currently not exported) so it can be referenced consistently.

Metadata

Metadata

Assignees

No one assigned

    Labels

    cerebro-analyzedIssue created by Cerebro automated analysis

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions