diff --git a/backend/src/entities/cedar-authorization/cedar-policy-parser.ts b/backend/src/entities/cedar-authorization/cedar-policy-parser.ts index 7e08cfd2f..f8475434c 100644 --- a/backend/src/entities/cedar-authorization/cedar-policy-parser.ts +++ b/backend/src/entities/cedar-authorization/cedar-policy-parser.ts @@ -95,7 +95,10 @@ function extractPermitStatements(policyText: string): ParsedPermitStatement[] { let i = permitIndex + permitKeyword.length; // Skip whitespace after "permit" - while (i < policyText.length && (policyText[i] === ' ' || policyText[i] === '\t' || policyText[i] === '\n' || policyText[i] === '\r')) { + while ( + i < policyText.length && + (policyText[i] === ' ' || policyText[i] === '\t' || policyText[i] === '\n' || policyText[i] === '\r') + ) { i++; } @@ -122,7 +125,10 @@ function extractPermitStatements(policyText: string): ParsedPermitStatement[] { const body = policyText.slice(bodyStart, i); // Skip past ')' and optional whitespace, expect ';' let j = i + 1; - while (j < policyText.length && (policyText[j] === ' ' || policyText[j] === '\t' || policyText[j] === '\n' || policyText[j] === '\r')) { + while ( + j < policyText.length && + (policyText[j] === ' ' || policyText[j] === '\t' || policyText[j] === '\n' || policyText[j] === '\r') + ) { j++; } diff --git a/frontend/src/app/lib/cedar-policy-items.ts b/frontend/src/app/lib/cedar-policy-items.ts index 2b29237d9..cc2aeb652 100644 --- a/frontend/src/app/lib/cedar-policy-items.ts +++ b/frontend/src/app/lib/cedar-policy-items.ts @@ -132,10 +132,7 @@ export function policyItemsToCedarPolicy(items: CedarPolicyItem[], connectionId: return policies.join('\n\n'); } - const actionRef = - item.action === 'table:*' || item.action === 'dashboard:*' - ? `action like RocketAdmin::Action::"${item.action}"` - : `action == RocketAdmin::Action::"${item.action}"`; + const actionRef = buildActionRef(item.action); let resource: string; if (item.action.startsWith('table:')) { @@ -154,9 +151,24 @@ export function policyItemsToCedarPolicy(items: CedarPolicyItem[], connectionId: return policies.join('\n\n'); } +const TABLE_ACTIONS = ['table:read', 'table:add', 'table:edit', 'table:delete']; +const DASHBOARD_ACTIONS = ['dashboard:read', 'dashboard:create', 'dashboard:edit', 'dashboard:delete']; + +function buildActionRef(action: string): string { + if (action === 'table:*') { + const list = TABLE_ACTIONS.map((a) => `RocketAdmin::Action::"${a}"`).join(', '); + return `action in [${list}]`; + } + if (action === 'dashboard:*') { + const list = DASHBOARD_ACTIONS.map((a) => `RocketAdmin::Action::"${a}"`).join(', '); + return `action in [${list}]`; + } + return `action == RocketAdmin::Action::"${action}"`; +} + function buildResourceRef(type: string, connectionId: string, id: string | undefined): string { if (id === '*') { - return `resource like RocketAdmin::${type}::"${connectionId}/*"`; + return `resource`; } return `resource == RocketAdmin::${type}::"${connectionId}/${id}"`; } diff --git a/frontend/src/app/lib/cedar-policy-parser.ts b/frontend/src/app/lib/cedar-policy-parser.ts index 4b3ce21c0..8f938297d 100644 --- a/frontend/src/app/lib/cedar-policy-parser.ts +++ b/frontend/src/app/lib/cedar-policy-parser.ts @@ -3,6 +3,7 @@ import { CedarPolicyItem } from './cedar-policy-items'; interface ParsedPermitStatement { action: string | null; + actions: string[] | null; resourceType: string | null; resourceId: string | null; isWildcard: boolean; @@ -225,12 +226,18 @@ function extractPermitStatements(policyText: string): ParsedPermitStatement[] { } } - return results; + return results.flatMap(expandActionIn); +} + +function expandActionIn(stmt: ParsedPermitStatement): ParsedPermitStatement[] { + if (!stmt.actions || stmt.actions.length === 0) return [stmt]; + return stmt.actions.map((action) => ({ ...stmt, action, actions: null })); } function parsePermitBody(body: string): ParsedPermitStatement { const result: ParsedPermitStatement = { action: null, + actions: null, resourceType: null, resourceId: null, isWildcard: false, @@ -240,9 +247,14 @@ function parsePermitBody(body: string): ParsedPermitStatement { if (actionMatch) { result.action = actionMatch[1]; } else { - const actionClause = body.match(/,\s*(action)\s*,/); - if (actionClause) { - result.isWildcard = true; + const actionInMatch = body.match(/action\s+in\s*\[([^\]]+)\]/); + if (actionInMatch) { + result.actions = [...actionInMatch[1].matchAll(/RocketAdmin::Action::"([^"]+)"/g)].map((m) => m[1]); + } else { + const actionClause = body.match(/,\s*(action)\s*,/); + if (actionClause) { + result.isWildcard = true; + } } } @@ -261,7 +273,7 @@ function parsePermitBody(body: string): ParsedPermitStatement { } function extractResourceSuffix(resourceId: string | null, connectionId: string): string | null { - if (!resourceId) return null; + if (!resourceId) return '*'; const prefix = `${connectionId}/`; if (resourceId.startsWith(prefix)) { return resourceId.slice(prefix.length);