From 641ad723f122d32d97235d515b0d74d79add8f66 Mon Sep 17 00:00:00 2001 From: Suresh Chaudhary Date: Thu, 7 May 2026 16:00:02 +0530 Subject: [PATCH 1/2] fix: RBAc checks the sidebar and hides the resource if it is not permitted --- .../enterprise/lib/contexts/rbacContext.tsx | 3 ++ ui/components/sidebar.tsx | 29 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx b/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx index 02a1c11ff27..5f5147bc773 100644 --- a/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx +++ b/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx @@ -26,6 +26,9 @@ export enum RbacResource { PromptRepository = "PromptRepository", PromptDeploymentStrategy = "PromptDeploymentStrategy", AccessProfiles = "AccessProfiles", + APIKeys = "APIKeys", + Inference = "Inference", + Metrics = "Metrics", } // RBAC Operation Names (must match backend definitions) diff --git a/ui/components/sidebar.tsx b/ui/components/sidebar.tsx index cf0bbad8f8e..508cca0a9df 100644 --- a/ui/components/sidebar.tsx +++ b/ui/components/sidebar.tsx @@ -19,10 +19,8 @@ import { LogOut, Logs, Network, - PanelLeft, PanelLeftClose, PanelLeftOpen, - PanelRight, Plug, Puzzle, ScrollText, @@ -67,7 +65,6 @@ import { useGetVersionQuery, useLogoutMutation, } from "@/lib/store"; -import { cn } from "@/lib/utils"; import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import type { UserInfo } from "@enterprise/lib/store/utils/tokenManager"; import { getUserInfo } from "@enterprise/lib/store/utils/tokenManager"; @@ -583,6 +580,7 @@ export default function AppSidebar() { const hasClusterConfigAccess = useRbac(RbacResource.Cluster, RbacOperation.View); const isAdaptiveRoutingAllowed = useRbac(RbacResource.AdaptiveRouter, RbacOperation.View); const hasSettingsAccess = useRbac(RbacResource.Settings, RbacOperation.View); + const hasAPIKeyAccess = useRbac(RbacResource.APIKeys, RbacOperation.View); const hasPromptRepositoryAccess = useRbac(RbacResource.PromptRepository, RbacOperation.View); const hasAccessProfilesAccess = useRbac(RbacResource.AccessProfiles, RbacOperation.View); const hasAnyGovernanceAccess = @@ -625,7 +623,7 @@ export default function AppSidebar() { url: "/workspace/mcp-logs", icon: MCPIcon, description: "MCP tool execution logs", - hasAccess: hasLogsAccess, + hasAccess: hasMCPGatewayAccess, }, { title: "Connectors", @@ -910,7 +908,7 @@ export default function AppSidebar() { url: "/workspace/config/api-keys", icon: KeyRound, description: "API keys management", - hasAccess: hasSettingsAccess, + hasAccess: hasAPIKeyAccess, }, { title: "Performance Tuning", @@ -950,11 +948,26 @@ export default function AppSidebar() { ], ); + const accessibleItems: SidebarItem[] = useMemo(() => { + return items + .map((item) => { + const hadSubItems = !!item.subItems?.length; + if (hadSubItems) { + const visibleSubItems = item.subItems!.filter((sub) => sub.hasAccess !== false); + if (visibleSubItems.length === 0) return null; + return { ...item, subItems: visibleSubItems, hasAccess: true }; + } + if (item.hasAccess === false) return null; + return item; + }) + .filter(Boolean) as SidebarItem[]; + }, [items]); + const filteredItems: SidebarItem[] = useMemo(() => { const query = searchQuery.trim().toLowerCase(); - if (!query) return items; + if (!query) return accessibleItems; - return items + return accessibleItems .map((item) => { const parentMatches = item.title.toLowerCase().includes(query); if (parentMatches) return item; @@ -970,7 +983,7 @@ export default function AppSidebar() { return null; }) .filter(Boolean) as SidebarItem[]; - }, [items, searchQuery]); + }, [accessibleItems, searchQuery]); const { data: version } = useGetVersionQuery(); const { resolvedTheme } = useTheme(); From 9585ac653ac224f9f549f2d35ef396daef0233ac Mon Sep 17 00:00:00 2001 From: Suresh Chaudhary Date: Thu, 7 May 2026 16:20:41 +0530 Subject: [PATCH 2/2] fix: RBAc checks for model provider page --- ui/app/workspace/config/layout.tsx | 14 ++-- ui/app/workspace/providers/page.tsx | 6 +- .../views/modelProviderKeysTableView.tsx | 64 ++++++++++--------- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/ui/app/workspace/config/layout.tsx b/ui/app/workspace/config/layout.tsx index 04e68b03aa6..93a2c2e2912 100644 --- a/ui/app/workspace/config/layout.tsx +++ b/ui/app/workspace/config/layout.tsx @@ -1,4 +1,4 @@ -import { createFileRoute, Outlet, useChildMatches } from "@tanstack/react-router"; +import { createFileRoute, Outlet, useChildMatches, useLocation } from "@tanstack/react-router"; import FullPageLoader from "@/components/fullPageLoader"; import { NoPermissionView } from "@/components/noPermissionView"; import { useGetCoreConfigQuery } from "@/lib/store"; @@ -6,11 +6,17 @@ import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import ConfigPage from "./page"; function RouteComponent() { - const hasConfigAccess = useRbac(RbacResource.Settings, RbacOperation.View); - const { isLoading } = useGetCoreConfigQuery({ fromDB: true }, { skip: !hasConfigAccess }); + const pathname = useLocation({ select: (l) => l.pathname }); + const hasSettingsAccess = useRbac(RbacResource.Settings, RbacOperation.View); + const hasAPIKeysAccess = useRbac(RbacResource.APIKeys, RbacOperation.View); const childMatches = useChildMatches(); - if (!hasConfigAccess) { + const isAPIKeysRoute = pathname.startsWith("/workspace/config/api-keys"); + const requiredAccess = isAPIKeysRoute ? hasAPIKeysAccess : hasSettingsAccess; + + const { isLoading } = useGetCoreConfigQuery({ fromDB: true }, { skip: !requiredAccess }); + + if (!requiredAccess) { return ; } diff --git a/ui/app/workspace/providers/page.tsx b/ui/app/workspace/providers/page.tsx index cb2a2dc936a..1dd6f6dad62 100644 --- a/ui/app/workspace/providers/page.tsx +++ b/ui/app/workspace/providers/page.tsx @@ -17,8 +17,8 @@ import { import { KnownProvider, ModelProviderName, ProviderStatus } from "@/lib/types/config"; import { cn } from "@/lib/utils"; import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; -import { AlertCircle } from "lucide-react"; import { useNavigate } from "@tanstack/react-router"; +import { AlertCircle } from "lucide-react"; import { useQueryState } from "nuqs"; import { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; @@ -240,7 +240,7 @@ export default function Providers() { })} )} -
+ {hasProviderCreateAccess ?
setShowCustomProviderSheet(true)} /> -
+
: null} diff --git a/ui/app/workspace/providers/views/modelProviderKeysTableView.tsx b/ui/app/workspace/providers/views/modelProviderKeysTableView.tsx index 4cc49f5d91a..ff8588eb85a 100644 --- a/ui/app/workspace/providers/views/modelProviderKeysTableView.tsx +++ b/ui/app/workspace/providers/views/modelProviderKeysTableView.tsx @@ -106,7 +106,7 @@ export default function ModelProviderKeysTableView({ provider, className, header
Configured {entityLabelPlural}
{headerActions} - {!isKeyless && ( + {!isKeyless && hasUpdateProviderAccess ? ( - )} + ) : null}
@@ -152,7 +152,7 @@ export default function ModelProviderKeysTableView({ provider, className, header key={key.id} data-testid={`key-row-${key.name}`} className="text-sm transition-colors hover:bg-white" - onClick={() => {}} + onClick={() => { }} >
@@ -258,34 +258,36 @@ export default function ModelProviderKeysTableView({ provider, className, header
- - - - - - { - setShowAddNewKeyDialog({ show: true, keyId: key.id }); - }} - disabled={!hasUpdateProviderAccess} - > - - Edit - - { - setShowDeleteKeyDialog({ show: true, keyId: key.id }); - }} - disabled={!hasDeleteProviderAccess} - > - - Delete - - - + {hasUpdateProviderAccess || hasDeleteProviderAccess ? + + + + + + { + setShowAddNewKeyDialog({ show: true, keyId: key.id }); + }} + disabled={!hasUpdateProviderAccess} + > + + Edit + + { + setShowDeleteKeyDialog({ show: true, keyId: key.id }); + }} + disabled={!hasDeleteProviderAccess} + > + + Delete + + + : null + }