mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
Tool perms
This commit is contained in:
@@ -5,6 +5,7 @@ import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { buildIntegrationToolSchemas } from '@/lib/copilot/chat-payload'
|
||||
import { orchestrateCopilotStream } from '@/lib/copilot/orchestrator'
|
||||
import { generateWorkspaceContext } from '@/lib/copilot/workspace-context'
|
||||
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
||||
|
||||
const logger = createLogger('MothershipExecuteAPI')
|
||||
|
||||
@@ -40,9 +41,10 @@ export async function POST(req: NextRequest) {
|
||||
ExecuteRequestSchema.parse(body)
|
||||
|
||||
const effectiveChatId = chatId || crypto.randomUUID()
|
||||
const [workspaceContext, integrationTools] = await Promise.all([
|
||||
const [workspaceContext, integrationTools, userPermission] = await Promise.all([
|
||||
generateWorkspaceContext(workspaceId, userId),
|
||||
buildIntegrationToolSchemas(),
|
||||
getUserEntityPermissions(userId, 'workspace', workspaceId).catch(() => null),
|
||||
])
|
||||
|
||||
const requestPayload: Record<string, unknown> = {
|
||||
@@ -55,6 +57,7 @@ export async function POST(req: NextRequest) {
|
||||
isHosted: true,
|
||||
workspaceContext,
|
||||
...(integrationTools.length > 0 ? { integrationTools } : {}),
|
||||
...(userPermission ? { userPermission } : {}),
|
||||
}
|
||||
|
||||
const result = await orchestrateCopilotStream(requestPayload, {
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface SubagentOrchestratorOptions extends Omit<OrchestratorOptions, '
|
||||
userId: string
|
||||
workflowId?: string
|
||||
workspaceId?: string
|
||||
userPermission?: string
|
||||
onComplete?: (result: SubagentOrchestratorResult) => void | Promise<void>
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ export async function orchestrateSubagentStream(
|
||||
requestPayload: Record<string, unknown>,
|
||||
options: SubagentOrchestratorOptions
|
||||
): Promise<SubagentOrchestratorResult> {
|
||||
const { userId, workflowId, workspaceId } = options
|
||||
const { userId, workflowId, workspaceId, userPermission } = options
|
||||
const execContext = await buildExecutionContext(userId, workflowId, workspaceId)
|
||||
|
||||
const msgId = requestPayload?.messageId
|
||||
@@ -59,7 +60,12 @@ export async function orchestrateSubagentStream(
|
||||
'Content-Type': 'application/json',
|
||||
...(env.COPILOT_API_KEY ? { 'x-api-key': env.COPILOT_API_KEY } : {}),
|
||||
},
|
||||
body: JSON.stringify({ ...requestPayload, userId, stream: true }),
|
||||
body: JSON.stringify({
|
||||
...requestPayload,
|
||||
userId,
|
||||
stream: true,
|
||||
...(userPermission ? { userPermission } : {}),
|
||||
}),
|
||||
},
|
||||
context,
|
||||
execContext,
|
||||
|
||||
@@ -121,6 +121,14 @@ async function executeManageCustomTool(
|
||||
return { success: false, error: "Missing required 'operation' argument" }
|
||||
}
|
||||
|
||||
const writeOps: string[] = ['add', 'edit', 'delete']
|
||||
if (writeOps.includes(operation) && context.userPermission && context.userPermission !== 'write' && context.userPermission !== 'admin') {
|
||||
return {
|
||||
success: false,
|
||||
error: `Permission denied: '${operation}' on manage_custom_tool requires write access. You have '${context.userPermission}' permission.`,
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (operation === 'list') {
|
||||
const toolsForUser = await listCustomTools({
|
||||
@@ -481,6 +489,7 @@ async function executeServerToolDirect(
|
||||
const result = await routeExecution(toolName, enrichedParams, {
|
||||
userId: context.userId,
|
||||
workspaceId: context.workspaceId,
|
||||
userPermission: context.userPermission,
|
||||
})
|
||||
return { success: true, output: result }
|
||||
} catch (error) {
|
||||
|
||||
@@ -145,5 +145,6 @@ export interface ExecutionContext {
|
||||
userId: string
|
||||
workflowId: string
|
||||
workspaceId?: string
|
||||
userPermission?: string
|
||||
decryptedEnvVars?: Record<string, string>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { z } from 'zod'
|
||||
export interface ServerToolContext {
|
||||
userId: string
|
||||
workspaceId?: string
|
||||
userPermission?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,18 @@ export type ExecuteResponseSuccess = (typeof ExecuteResponseSuccessSchema)['_typ
|
||||
|
||||
const logger = createLogger('ServerToolRouter')
|
||||
|
||||
const WRITE_ACTIONS: Record<string, string[]> = {
|
||||
knowledge_base: ['create', 'add_document', 'delete'],
|
||||
user_table: ['create', 'insert', 'update', 'delete', 'delete_table'],
|
||||
manage_custom_tool: ['add', 'edit', 'delete'],
|
||||
}
|
||||
|
||||
function isActionAllowed(toolName: string, action: string, userPermission: string): boolean {
|
||||
const writeActions = WRITE_ACTIONS[toolName]
|
||||
if (!writeActions || !writeActions.includes(action)) return true
|
||||
return userPermission === 'write' || userPermission === 'admin'
|
||||
}
|
||||
|
||||
/** Registry of all server tools. Tools self-declare their validation schemas. */
|
||||
const serverToolRegistry: Record<string, BaseServerTool> = {
|
||||
[getBlocksMetadataServerTool.name]: getBlocksMetadataServerTool,
|
||||
@@ -53,6 +65,16 @@ export async function routeExecution(
|
||||
|
||||
logger.debug('Routing to tool', { toolName })
|
||||
|
||||
// Action-level permission enforcement for mixed read/write tools
|
||||
if (context?.userPermission && WRITE_ACTIONS[toolName]) {
|
||||
const action = (payload as Record<string, unknown>)?.action as string
|
||||
if (action && !isActionAllowed(toolName, action, context.userPermission)) {
|
||||
throw new Error(
|
||||
`Permission denied: '${action}' on ${toolName} requires write access. You have '${context.userPermission}' permission.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate input if tool declares a schema
|
||||
const args = tool.inputSchema ? tool.inputSchema.parse(payload ?? {}) : (payload ?? {})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user