mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(ui): block Playground page for viewer roles on direct URL access (#35676)
This commit is contained in:
parent
46b6eae799
commit
b13e4eee5d
4 changed files with 77 additions and 11 deletions
|
|
@ -22,7 +22,7 @@ import {
|
|||
UserOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Text, TextInput, Title, Button as TremorButton } from "@tremor/react";
|
||||
import { Button, Input, Modal, Popover, Select, Spin, Tooltip, Typography, Upload } from "antd";
|
||||
import { Button, Input, Modal, Popover, Select, Spin, Tooltip, Upload } from "antd";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
|
|
@ -1016,16 +1016,6 @@ const ChatUI: React.FC<ChatUIProps> = ({
|
|||
NotificationsManager.success("Chat history cleared.");
|
||||
};
|
||||
|
||||
if (userRole && userRole === "Admin Viewer") {
|
||||
const { Title, Paragraph } = Typography;
|
||||
return (
|
||||
<div>
|
||||
<Title level={1}>Access Denied</Title>
|
||||
<Paragraph>Ask your proxy admin for access to test models</Paragraph>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const onModelChange = (value: string) => {
|
||||
setSelectedModel(value);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import PlaygroundPage from "./page";
|
||||
|
||||
const authState = { userRole: "Admin" };
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => ({
|
||||
token: "token-1",
|
||||
accessToken: "sk-test",
|
||||
userId: "user-1",
|
||||
userRole: authState.userRole,
|
||||
disabledPersonalKeyCreation: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/utils/proxyUtils", () => ({
|
||||
fetchProxySettings: vi.fn().mockResolvedValue(null),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/playground/components/chat_ui/ChatUI", () => ({
|
||||
default: () => <div data-testid="chat-ui" />,
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/playground/components/compareUI/CompareUI", () => ({
|
||||
default: () => <div data-testid="compare-ui" />,
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/playground/components/complianceUI/ComplianceUI", () => ({
|
||||
default: () => <div data-testid="compliance-ui" />,
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/playground/components/chat_ui/AgentBuilderView", () => ({
|
||||
default: () => <div data-testid="agent-builder" />,
|
||||
}));
|
||||
|
||||
describe("PlaygroundPage role guard", () => {
|
||||
beforeEach(() => {
|
||||
authState.userRole = "Admin";
|
||||
});
|
||||
|
||||
it.each(["Internal Viewer", "Admin Viewer"])("blocks the entire playground for %s", (role) => {
|
||||
authState.userRole = role;
|
||||
render(<PlaygroundPage />);
|
||||
|
||||
expect(screen.getByText("Access Denied")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("tab")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("chat-ui")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("compare-ui")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("compliance-ui")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("agent-builder")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each(["Admin", "Internal User", "Org Admin"])("renders the playground for %s", (role) => {
|
||||
authState.userRole = role;
|
||||
render(<PlaygroundPage />);
|
||||
|
||||
expect(screen.queryByText("Access Denied")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: "Chat" })).toBeInTheDocument();
|
||||
expect(screen.getByTestId("chat-ui")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -9,6 +9,7 @@ import { TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react";
|
|||
import { DeprecationBanner } from "@/components/DeprecationBanner";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import { fetchProxySettings } from "@/utils/proxyUtils";
|
||||
import { isViewOnlyRole } from "@/utils/roles";
|
||||
|
||||
interface ProxySettings {
|
||||
PROXY_BASE_URL?: string;
|
||||
|
|
@ -35,6 +36,17 @@ export default function PlaygroundPage() {
|
|||
initializeProxySettings();
|
||||
}, [accessToken]);
|
||||
|
||||
if (isViewOnlyRole(userRole)) {
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-2 p-8 text-center">
|
||||
<h1 className="text-2xl font-semibold">Access Denied</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Your role does not have access to the Playground. Ask your proxy admin for access to test models.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full w-full flex flex-col">
|
||||
<TabGroup className="w-full" style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }}>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ export const rolesWithWriteAccess = ["Internal User", "Admin", "proxy_admin"];
|
|||
// Per the Admin Viewer principle: read parity with Proxy Admin, no writes,
|
||||
// no cost-incurring actions (Playground stays gated by `rolesWithWriteAccess`).
|
||||
export const rolesAllowedToViewWriteScopedPages = [...rolesWithWriteAccess, "Admin Viewer", "proxy_admin_viewer"];
|
||||
export const viewOnlyRoles = ["Admin Viewer", "Internal Viewer"];
|
||||
export const isViewOnlyRole = (role: string): boolean => viewOnlyRoles.includes(role);
|
||||
|
||||
// Helper function to check if a role is in all_admin_roles
|
||||
export const isAdminRole = (role: string): boolean => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue