cline-sdk
Version:
Comprehensive SDK for Cline - AI-powered development assistant with database integration, execution modes, and custom functions
398 lines • 16.1 kB
JavaScript
;
/**
* 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