UNPKG

cline-sdk

Version:

Comprehensive SDK for Cline - AI-powered development assistant with database integration, execution modes, and custom functions

398 lines 16.1 kB
"use strict"; /** * Database Row Level Security (RLS) Tool * * Manages PostgreSQL Row Level Security policies and settings */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DatabaseRLSTool = void 0; class DatabaseRLSTool { constructor(dbManager) { this.dbManager = dbManager; } async execute(input) { const startTime = Date.now(); try { const { action, schemaName, tableName } = input; // Validate required parameters if (!schemaName || !tableName) { return { success: false, error: "schemaName and tableName are required", executionTime: Date.now() - startTime, }; } switch (action) { case "enableRLS": return this.enableRLS(schemaName, tableName, input.force, startTime); case "disableRLS": return this.disableRLS(schemaName, tableName, input.force, startTime); case "createPolicy": return this.createPolicy(input, startTime); case "dropPolicy": return this.dropPolicy(schemaName, tableName, input.policyName, startTime); case "listPolicies": return this.listPolicies(schemaName, tableName, startTime); case "testPolicy": return this.testPolicy(input, startTime); default: return { success: false, error: `Unknown action: ${action}`, executionTime: Date.now() - startTime, }; } } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error", executionTime: Date.now() - startTime, }; } } async enableRLS(schemaName, tableName, force = false, startTime) { const warnings = []; // Check if RLS is already enabled const checkResult = await this.dbManager.executeQuery(` SELECT relrowsecurity FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 `, [schemaName, tableName]); if (!checkResult.success) { return { success: false, error: `Table not found: ${schemaName}.${tableName}`, executionTime: Date.now() - startTime, }; } const isEnabled = checkResult.data?.[0]?.relrowsecurity; if (isEnabled && !force) { return { success: true, message: `Row Level Security is already enabled on ${schemaName}.${tableName}`, executionTime: Date.now() - startTime, }; } // Enable RLS const enableQuery = `ALTER TABLE "${schemaName}"."${tableName}" ENABLE ROW LEVEL SECURITY`; const result = await this.dbManager.executeQuery(enableQuery); if (!result.success) { return { success: false, error: `Failed to enable RLS: ${result.error}`, executionTime: Date.now() - startTime, }; } if (!isEnabled) { warnings.push("RLS is now enabled but no policies exist. All rows will be hidden until policies are created."); warnings.push("Consider creating appropriate policies using the createPolicy action."); } return { success: true, message: `Row Level Security enabled on ${schemaName}.${tableName}`, executionTime: Date.now() - startTime, warnings, }; } async disableRLS(schemaName, tableName, force = false, startTime) { const warnings = []; // Check current policies const policiesResult = await this.dbManager.executeQuery(` SELECT count(*) as policy_count FROM pg_policy pol JOIN pg_class c ON c.oid = pol.polrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 `, [schemaName, tableName]); const policyCount = parseInt(policiesResult.data?.[0]?.policy_count || "0"); if (policyCount > 0 && !force) { warnings.push(`Table has ${policyCount} RLS policies that will remain but be inactive.`); warnings.push("Consider dropping policies first or use force=true to disable anyway."); } // Disable RLS const disableQuery = `ALTER TABLE "${schemaName}"."${tableName}" DISABLE ROW LEVEL SECURITY`; const result = await this.dbManager.executeQuery(disableQuery); if (!result.success) { return { success: false, error: `Failed to disable RLS: ${result.error}`, executionTime: Date.now() - startTime, }; } return { success: true, message: `Row Level Security disabled on ${schemaName}.${tableName}`, executionTime: Date.now() - startTime, warnings, }; } async createPolicy(input, startTime) { const { schemaName, tableName, policyName, command = "ALL", roles = [], using, withCheck } = input; if (!policyName) { return { success: false, error: "policyName is required for createPolicy action", executionTime: Date.now() - startTime, }; } // Build policy SQL let policySQL = `CREATE POLICY "${policyName}" ON "${schemaName}"."${tableName}"`; if (command !== "ALL") { policySQL += ` FOR ${command}`; } if (roles.length > 0) { const rolesList = roles.map((role) => `"${role}"`).join(", "); policySQL += ` TO ${rolesList}`; } if (using) { policySQL += ` USING (${using})`; } if (withCheck) { policySQL += ` WITH CHECK (${withCheck})`; } // Check if policy already exists const existsResult = await this.dbManager.executeQuery(` SELECT polname FROM pg_policy pol JOIN pg_class c ON c.oid = pol.polrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 AND pol.polname = $3 `, [schemaName, tableName, policyName]); if (existsResult.success && existsResult.data && existsResult.data.length > 0) { return { success: false, error: `Policy '${policyName}' already exists on ${schemaName}.${tableName}`, executionTime: Date.now() - startTime, }; } // Create the policy const result = await this.dbManager.executeQuery(policySQL); if (!result.success) { return { success: false, error: `Failed to create policy: ${result.error}`, executionTime: Date.now() - startTime, }; } return { success: true, message: `Policy '${policyName}' created on ${schemaName}.${tableName}`, data: { policyName, command, roles, using, withCheck, sql: policySQL, }, executionTime: Date.now() - startTime, }; } async dropPolicy(schemaName, tableName, policyName, startTime) { if (!policyName) { return { success: false, error: "policyName is required for dropPolicy action", executionTime: Date.now() - startTime, }; } // Check if policy exists const existsResult = await this.dbManager.executeQuery(` SELECT polname FROM pg_policy pol JOIN pg_class c ON c.oid = pol.polrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 AND pol.polname = $3 `, [schemaName, tableName, policyName]); if (!existsResult.success || !existsResult.data || existsResult.data.length === 0) { return { success: false, error: `Policy '${policyName}' not found on ${schemaName}.${tableName}`, executionTime: Date.now() - startTime, }; } // Drop the policy const dropSQL = `DROP POLICY "${policyName}" ON "${schemaName}"."${tableName}"`; const result = await this.dbManager.executeQuery(dropSQL); if (!result.success) { return { success: false, error: `Failed to drop policy: ${result.error}`, executionTime: Date.now() - startTime, }; } return { success: true, message: `Policy '${policyName}' dropped from ${schemaName}.${tableName}`, executionTime: Date.now() - startTime, }; } async listPolicies(schemaName, tableName, startTime) { // Get RLS status const rlsResult = await this.dbManager.executeQuery(` SELECT relrowsecurity, relforcerowsecurity FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 `, [schemaName, tableName]); if (!rlsResult.success || !rlsResult.data || rlsResult.data.length === 0) { return { success: false, error: `Table not found: ${schemaName}.${tableName}`, executionTime: Date.now() - startTime, }; } const rlsInfo = rlsResult.data[0]; // Get policies const policiesResult = await this.dbManager.executeQuery(` SELECT pol.polname as policy_name, pol.polcmd as command, array_to_string(pol.polroles::regrole[], ', ') as roles, pg_get_expr(pol.polqual, pol.polrelid) as using_clause, pg_get_expr(pol.polwithcheck, pol.polrelid) as with_check_clause, pol.polpermissive as is_permissive FROM pg_policy pol JOIN pg_class c ON c.oid = pol.polrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 ORDER BY pol.polname `, [schemaName, tableName]); const policies = policiesResult.data || []; return { success: true, data: { table: `${schemaName}.${tableName}`, rlsEnabled: rlsInfo.relrowsecurity, rlsForced: rlsInfo.relforcerowsecurity, policyCount: policies.length, policies: policies.map((p) => ({ name: p.policy_name, command: p.command, roles: p.roles || "public", using: p.using_clause, withCheck: p.with_check_clause, permissive: p.is_permissive, description: this.getPolicyDescription(p), })), }, executionTime: Date.now() - startTime, }; } getPolicyDescription(policy) { let desc = `${policy.command} policy for ${policy.roles || "all roles"}`; if (policy.using_clause) { desc += ` with condition: ${policy.using_clause}`; } if (policy.with_check_clause) { desc += ` and check: ${policy.with_check_clause}`; } return desc; } async testPolicy(input, startTime) { const { schemaName, tableName, testUserId, policyName } = input; if (!testUserId) { return { success: false, error: "testUserId is required for testPolicy action", executionTime: Date.now() - startTime, }; } const warnings = []; warnings.push("Policy testing requires careful consideration of your specific use case."); warnings.push("Consider testing with actual user sessions and roles."); // Get current policies const policiesResult = await this.listPolicies(schemaName, tableName, startTime); if (!policiesResult.success) { return policiesResult; } // Provide guidance on testing const testGuidance = { currentPolicies: policiesResult.data, testingSuggestions: [ `Connect as user '${testUserId}' and run: SELECT * FROM "${schemaName}"."${tableName}"`, `Check which rows are visible with current policies`, `Test INSERT/UPDATE/DELETE operations if applicable`, `Use EXPLAIN to see how RLS affects query plans`, ], exampleQueries: [ `-- Test as specific user SET ROLE '${testUserId}'; SELECT * FROM "${schemaName}"."${tableName}"; RESET ROLE;`, `-- Check policy effectiveness EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM "${schemaName}"."${tableName}";`, ], }; return { success: true, message: `Policy testing guidance for ${schemaName}.${tableName}`, data: testGuidance, executionTime: Date.now() - startTime, warnings, }; } getDefinition() { return { name: "DatabaseRLS", description: "Manage PostgreSQL Row Level Security (RLS) policies. Enable/disable RLS, create/drop policies, and test policy effectiveness.", inputSchema: { type: "object", properties: { action: { type: "string", description: "The RLS action to perform", enum: ["enableRLS", "disableRLS", "createPolicy", "dropPolicy", "listPolicies", "testPolicy"], }, schemaName: { type: "string", description: "Name of the database schema", }, tableName: { type: "string", description: "Name of the table", }, policyName: { type: "string", description: "Name of the RLS policy (required for policy operations)", }, command: { type: "string", description: "SQL command the policy applies to", enum: ["SELECT", "INSERT", "UPDATE", "DELETE", "ALL"], default: "ALL", }, roles: { type: "array", description: "Database roles the policy applies to", items: { type: "string", }, }, using: { type: "string", description: "USING expression for the policy (boolean expression)", }, withCheck: { type: "string", description: "WITH CHECK expression for the policy (boolean expression)", }, testUserId: { type: "string", description: "User ID to test policies with", }, force: { type: "boolean", description: "Force the operation even if warnings exist", default: false, }, }, required: ["action", "schemaName", "tableName"], }, }; } } exports.DatabaseRLSTool = DatabaseRLSTool; //# sourceMappingURL=database-rls-tool.js.map