-
Notifications
You must be signed in to change notification settings - Fork 2
Data Browser sidebar entry shown regardless of assumed role #1124
Description
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
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 byisStorageManager &&.{ label: 'Data Browser', icon: <Icon name="Bucket" />, onClick: () => { navigate('/buckets'); }, active: doesRouteMatch('/buckets') || doesRouteMatch('/accounts/:accountName/buckets/*') || doesRouteMatch('/accounts/:accountName/data/buckets/*'), },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'), },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 }; };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';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();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:
-
Extend role detection: The assumed role needs to be available in the sidebar context. Two approaches:
- Option A (preferred): Move the
DataServiceRoleProviderhigher in the component tree so it wraps the sidebar construction inInternalRoutes. Then useuseRolePathName()to get the current assumed role and check if it matchesdata-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.
- Option A (preferred): Move the
-
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') || ..., },
-
Also consider route-level protection: Add a redirect or guard on the
/bucketsroutes so that even if a user navigates directly via URL, they are redirected away when the assumed role isdata-consumer-role. -
Export the role constants: Export
DATA_CONSUMER_ROLEfromhooks.ts(it's currently not exported) so it can be referenced consistently.