diff --git a/backend/src/entities/cedar-authorization/cedar-authorization.service.ts b/backend/src/entities/cedar-authorization/cedar-authorization.service.ts index 1d9d1c99b..8ac9c97dc 100644 --- a/backend/src/entities/cedar-authorization/cedar-authorization.service.ts +++ b/backend/src/entities/cedar-authorization/cedar-authorization.service.ts @@ -107,7 +107,7 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On throw new HttpException({ message: Messages.CANNOT_CHANGE_ADMIN_GROUP }, HttpStatus.BAD_REQUEST); } - await this.validatePolicyReferences(cedarPolicy, connectionId, groupId); + await this.validatePolicyReferences(cedarPolicy, connectionId); const classicalPermissions = parseCedarPolicyToClassicalPermissions(cedarPolicy, connectionId, groupId); @@ -186,7 +186,8 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On const userGroups = await this.globalDbContext.groupRepository.findAllUserGroupsInConnection(connectionId, userId); if (userGroups.length === 0) return false; - const policies = await this.loadPoliciesForConnection(connectionId); + const userGroupIds = userGroups.map((g) => g.id); + const policies = await this.loadPoliciesForUser(connectionId, userId, userGroupIds); if (!policies) return false; const entities = buildCedarEntities(userId, userGroups, connectionId, tableName, dashboardId); @@ -210,17 +211,21 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On return false; } - private async loadPoliciesForConnection(connectionId: string): Promise { - const cached = Cacher.getCedarPolicyCache(connectionId); + private async loadPoliciesForUser(connectionId: string, userId: string, userGroupIds: string[]): Promise { + const cached = Cacher.getCedarPolicyCache(connectionId, userId); if (cached !== null) return cached; const groups = await this.globalDbContext.groupRepository.findAllGroupsInConnection(connectionId); - const policyTexts = groups.map((g) => g.cedarPolicy).filter(Boolean); + const userGroupIdSet = new Set(userGroupIds); + const policyTexts = groups + .filter((g) => userGroupIdSet.has(g.id)) + .map((g) => g.cedarPolicy) + .filter(Boolean); if (policyTexts.length === 0) return null; const combined = policyTexts.join('\n\n'); - Cacher.setCedarPolicyCache(connectionId, combined); + Cacher.setCedarPolicyCache(connectionId, userId, combined); return combined; } @@ -268,22 +273,7 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On private async validatePolicyReferences( cedarPolicy: string, connectionId: string, - groupId: string, ): Promise { - - const principalGroupIds = [ - ...cedarPolicy.matchAll(/principal\s+in\s+RocketAdmin::Group::"([^"]+)"/g), - ].map((m) => m[1]); - - for (const principalGroupId of principalGroupIds) { - if (principalGroupId !== groupId) { - throw new HttpException( - { message: Messages.CEDAR_POLICY_REFERENCES_FOREIGN_PRINCIPAL }, - HttpStatus.BAD_REQUEST, - ); - } - } - const connectionIds = [ ...cedarPolicy.matchAll(/resource\s*==\s*RocketAdmin::Connection::"([^"]+)"/g), ].map((m) => m[1]); diff --git a/backend/src/entities/cedar-authorization/cedar-entity-builder.ts b/backend/src/entities/cedar-authorization/cedar-entity-builder.ts index 52ac35bc4..b675ec8ca 100644 --- a/backend/src/entities/cedar-authorization/cedar-entity-builder.ts +++ b/backend/src/entities/cedar-authorization/cedar-entity-builder.ts @@ -19,7 +19,7 @@ export function buildCedarEntities( entities.push({ uid: { type: 'RocketAdmin::User', id: userId }, attrs: { suspended: false }, - parents: userGroups.map((g) => ({ type: 'RocketAdmin::Group', id: g.id })), + parents: [], }); // Group entities diff --git a/backend/src/entities/cedar-authorization/cedar-policy-generator.ts b/backend/src/entities/cedar-authorization/cedar-policy-generator.ts index eae23f419..3839446a9 100644 --- a/backend/src/entities/cedar-authorization/cedar-policy-generator.ts +++ b/backend/src/entities/cedar-authorization/cedar-policy-generator.ts @@ -2,18 +2,16 @@ import { AccessLevelEnum } from '../../enums/index.js'; import { IComplexPermission } from '../permission/permission.interface.js'; export function generateCedarPolicyForGroup( - groupId: string, connectionId: string, isMain: boolean, permissions: IComplexPermission, ): string { const policies: Array = []; - const groupRef = `RocketAdmin::Group::"${groupId}"`; const connectionRef = `RocketAdmin::Connection::"${connectionId}"`; if (isMain) { policies.push( - `permit(\n principal in ${groupRef},\n action,\n resource\n);`, + `permit(\n principal,\n action,\n resource\n);`, ); return policies.join('\n\n'); } @@ -22,14 +20,14 @@ export function generateCedarPolicyForGroup( const connAccess = permissions.connection.accessLevel; if (connAccess === AccessLevelEnum.edit) { policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"connection:read",\n resource == ${connectionRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == ${connectionRef}\n);`, ); policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"connection:edit",\n resource == ${connectionRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:edit",\n resource == ${connectionRef}\n);`, ); } else if (connAccess === AccessLevelEnum.readonly) { policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"connection:read",\n resource == ${connectionRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == ${connectionRef}\n);`, ); } @@ -38,14 +36,14 @@ export function generateCedarPolicyForGroup( const groupResourceRef = `RocketAdmin::Group::"${permissions.group.groupId}"`; if (groupAccess === AccessLevelEnum.edit) { policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"group:read",\n resource == ${groupResourceRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:read",\n resource == ${groupResourceRef}\n);`, ); policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"group:edit",\n resource == ${groupResourceRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:edit",\n resource == ${groupResourceRef}\n);`, ); } else if (groupAccess === AccessLevelEnum.readonly) { policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"group:read",\n resource == ${groupResourceRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:read",\n resource == ${groupResourceRef}\n);`, ); } @@ -59,7 +57,7 @@ export function generateCedarPolicyForGroup( if (access.read) { hasReadPermission = true; policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"dashboard:read",\n resource == ${dashboardRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:read",\n resource == ${dashboardRef}\n);`, ); } if (access.create) { @@ -67,24 +65,24 @@ export function generateCedarPolicyForGroup( } if (access.edit) { policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"dashboard:edit",\n resource == ${dashboardRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:edit",\n resource == ${dashboardRef}\n);`, ); } if (access.delete) { policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"dashboard:delete",\n resource == ${dashboardRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:delete",\n resource == ${dashboardRef}\n);`, ); } } const newDashboardRef = `RocketAdmin::Dashboard::"${connectionId}/__new__"`; if (hasReadPermission) { policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"dashboard:read",\n resource == ${newDashboardRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:read",\n resource == ${newDashboardRef}\n);`, ); } if (hasCreatePermission) { policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"dashboard:create",\n resource == ${newDashboardRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:create",\n resource == ${newDashboardRef}\n);`, ); } } @@ -96,22 +94,22 @@ export function generateCedarPolicyForGroup( const hasAnyAccess = access.visibility || access.add || access.delete || access.edit; if (hasAnyAccess) { policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"table:read",\n resource == ${tableRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == ${tableRef}\n);`, ); } if (access.add) { policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"table:add",\n resource == ${tableRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:add",\n resource == ${tableRef}\n);`, ); } if (access.edit) { policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"table:edit",\n resource == ${tableRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:edit",\n resource == ${tableRef}\n);`, ); } if (access.delete) { policies.push( - `permit(\n principal in ${groupRef},\n action == RocketAdmin::Action::"table:delete",\n resource == ${tableRef}\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:delete",\n resource == ${tableRef}\n);`, ); } } diff --git a/backend/src/entities/cedar-authorization/cedar-policy-parser.ts b/backend/src/entities/cedar-authorization/cedar-policy-parser.ts index c13d04d7f..7e08cfd2f 100644 --- a/backend/src/entities/cedar-authorization/cedar-policy-parser.ts +++ b/backend/src/entities/cedar-authorization/cedar-policy-parser.ts @@ -6,7 +6,6 @@ import { } from '../permission/permission.interface.js'; interface ParsedPermitStatement { - groupId: string | null; action: string | null; resourceType: string | null; resourceId: string | null; @@ -140,18 +139,12 @@ function extractPermitStatements(policyText: string): ParsedPermitStatement[] { function parsePermitBody(body: string): ParsedPermitStatement { const result: ParsedPermitStatement = { - groupId: null, action: null, resourceType: null, resourceId: null, isWildcard: false, }; - const principalMatch = body.match(/principal\s+in\s+RocketAdmin::Group::"([^"]+)"/); - if (principalMatch) { - result.groupId = principalMatch[1]; - } - const actionMatch = body.match(/action\s*==\s*RocketAdmin::Action::"([^"]+)"/); if (actionMatch) { result.action = actionMatch[1]; diff --git a/backend/src/entities/cedar-authorization/cedar-schema.ts b/backend/src/entities/cedar-authorization/cedar-schema.ts index 2a76064a2..032290985 100644 --- a/backend/src/entities/cedar-authorization/cedar-schema.ts +++ b/backend/src/entities/cedar-authorization/cedar-schema.ts @@ -2,7 +2,7 @@ export const CEDAR_SCHEMA = { RocketAdmin: { entityTypes: { User: { - memberOfTypes: ['Group'], + memberOfTypes: [], shape: { type: 'Record', attributes: { diff --git a/backend/src/entities/cedar-authorization/scripts/migrate-permissions-to-cedar.ts b/backend/src/entities/cedar-authorization/scripts/migrate-permissions-to-cedar.ts index e6a42df15..052ef48f0 100644 --- a/backend/src/entities/cedar-authorization/scripts/migrate-permissions-to-cedar.ts +++ b/backend/src/entities/cedar-authorization/scripts/migrate-permissions-to-cedar.ts @@ -50,7 +50,7 @@ export async function migratePermissionsToCedar(dataSource: DataSource): Promise tables: Array.from(tableMap.values()), }; - const cedarPolicy = generateCedarPolicyForGroup(group.id, connection.id, group.isMain, complexPermission); + const cedarPolicy = generateCedarPolicyForGroup(connection.id, group.isMain, complexPermission); group.cedarPolicy = cedarPolicy; await groupRepository.save(group); migratedCount++; diff --git a/backend/src/entities/connection/use-cases/create-connection.use.case.ts b/backend/src/entities/connection/use-cases/create-connection.use.case.ts index 044cec44b..c62257c4c 100644 --- a/backend/src/entities/connection/use-cases/create-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/create-connection.use.case.ts @@ -101,7 +101,6 @@ export class CreateConnectionUseCase ); await this._dbContext.permissionRepository.createdDefaultAdminPermissionsInGroup(createdAdminGroup); createdAdminGroup.cedarPolicy = generateCedarPolicyForGroup( - createdAdminGroup.id, savedConnection.id, true, { diff --git a/backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts b/backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts index 68421e778..22bb5ac71 100644 --- a/backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts @@ -37,7 +37,6 @@ export class CreateGroupInConnectionUseCase const newGroupEntity = buildNewGroupEntityForConnectionWithUser(connectionToUpdate, foundUser, title); const savedGroup = await this._dbContext.groupRepository.saveNewOrUpdatedGroup(newGroupEntity); savedGroup.cedarPolicy = generateCedarPolicyForGroup( - savedGroup.id, connectionId, false, { diff --git a/backend/src/entities/permission/use-cases/create-or-update-permissions.use.case.ts b/backend/src/entities/permission/use-cases/create-or-update-permissions.use.case.ts index 33c7a1cab..5ff56d044 100644 --- a/backend/src/entities/permission/use-cases/create-or-update-permissions.use.case.ts +++ b/backend/src/entities/permission/use-cases/create-or-update-permissions.use.case.ts @@ -189,7 +189,7 @@ export class CreateOrUpdatePermissionsUseCase ); // Generate and save Cedar policy for this group - const cedarPolicy = generateCedarPolicyForGroup(groupId, connectionId, groupToUpdate.isMain, resultPermissions); + const cedarPolicy = generateCedarPolicyForGroup(connectionId, groupToUpdate.isMain, resultPermissions); groupToUpdate.cedarPolicy = cedarPolicy; await this._dbContext.groupRepository.saveNewOrUpdatedGroup(groupToUpdate); Cacher.invalidateCedarPolicyCache(connectionId); diff --git a/backend/src/helpers/cache/cacher.ts b/backend/src/helpers/cache/cacher.ts index 3de7ba516..5b7d92d1c 100644 --- a/backend/src/helpers/cache/cacher.ts +++ b/backend/src/helpers/cache/cacher.ts @@ -66,17 +66,23 @@ export class Cacher { return userInvitations <= 10 && groupInvitations <= 10; } - public static getCedarPolicyCache(connectionId: string): string | null { - const cached = cedarPolicyCache.get(connectionId); + public static getCedarPolicyCache(connectionId: string, userId: string): string | null { + const cacheKey = `${connectionId}:${userId}`; + const cached = cedarPolicyCache.get(cacheKey); return cached !== undefined ? cached : null; } - public static setCedarPolicyCache(connectionId: string, policies: string): void { - cedarPolicyCache.set(connectionId, policies); + public static setCedarPolicyCache(connectionId: string, userId: string, policies: string): void { + const cacheKey = `${connectionId}:${userId}`; + cedarPolicyCache.set(cacheKey, policies); } public static invalidateCedarPolicyCache(connectionId: string): void { - cedarPolicyCache.delete(connectionId); + for (const key of cedarPolicyCache.keys()) { + if (key.startsWith(`${connectionId}:`)) { + cedarPolicyCache.delete(key); + } + } } public static async clearAllCache(): Promise { diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-cedar-entity-builder.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-cedar-entity-builder.test.ts index 1a5e51ee5..493a568a9 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-cedar-entity-builder.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-cedar-entity-builder.test.ts @@ -9,16 +9,14 @@ function makeGroup(id: string, isMain: boolean): GroupEntity { return { id, isMain } as unknown as GroupEntity; } -test('user entity has correct type, id, suspended=false, and group parents', (t) => { +test('user entity has correct type, id, suspended=false, and empty parents', (t) => { const groups = [makeGroup('g1', false), makeGroup('g2', true)]; const entities = buildCedarEntities(userId, groups, connectionId); const userEntity = entities.find((e) => e.uid.type === 'RocketAdmin::User'); t.truthy(userEntity); t.is(userEntity.uid.id, userId); t.is(userEntity.attrs.suspended, false); - t.is(userEntity.parents.length, 2); - t.deepEqual(userEntity.parents[0], { type: 'RocketAdmin::Group', id: 'g1' }); - t.deepEqual(userEntity.parents[1], { type: 'RocketAdmin::Group', id: 'g2' }); + t.deepEqual(userEntity.parents, []); }); test('group entities have correct type, isMain attribute, connectionId attribute, empty parents', (t) => { diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-generator.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-generator.test.ts index bc0db8bd7..6e1a1dd8c 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-generator.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-generator.test.ts @@ -16,8 +16,8 @@ function makePermissions(overrides: Partial = {}): IComplexP } test('isMain=true generates a single wildcard permit', (t) => { - const result = generateCedarPolicyForGroup(groupId, connectionId, true, makePermissions()); - t.true(result.includes('principal in RocketAdmin::Group::"test-group-id"')); + const result = generateCedarPolicyForGroup(connectionId, true, makePermissions()); + t.true(result.includes('principal,')); t.true(result.includes('action,')); t.true(result.includes('resource')); // Should be a single policy @@ -27,7 +27,6 @@ test('isMain=true generates a single wildcard permit', (t) => { test('connection:edit generates ONLY connection:read + connection:edit (not wildcard)', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -49,7 +48,6 @@ test('connection:edit generates ONLY connection:read + connection:edit (not wild test('connection:readonly generates only connection:read', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -63,14 +61,13 @@ test('connection:readonly generates only connection:read', (t) => { }); test('connection:none generates no connection policies', (t) => { - const result = generateCedarPolicyForGroup(groupId, connectionId, false, makePermissions()); + const result = generateCedarPolicyForGroup(connectionId, false, makePermissions()); t.false(result.includes('connection:read')); t.false(result.includes('connection:edit')); }); test('group:edit generates group:read + group:edit', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -85,7 +82,6 @@ test('group:edit generates group:read + group:edit', (t) => { test('group:readonly generates only group:read', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -100,7 +96,6 @@ test('group:readonly generates only group:read', (t) => { test('table with visibility=true only generates only table:read', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -122,7 +117,6 @@ test('table with visibility=true only generates only table:read', (t) => { test('table with all flags true generates table:read + table:add + table:edit + table:delete', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -144,7 +138,6 @@ test('table with all flags true generates table:read + table:add + table:edit + test('table with add=true only generates table:read + table:add (hasAnyAccess triggers table:read)', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -166,7 +159,6 @@ test('table with add=true only generates table:read + table:add (hasAnyAccess tr test('table with all flags false generates no policies for that table', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -183,13 +175,12 @@ test('table with all flags false generates no policies for that table', (t) => { }); test('all none + no tables returns empty string', (t) => { - const result = generateCedarPolicyForGroup(groupId, connectionId, false, makePermissions()); + const result = generateCedarPolicyForGroup(connectionId, false, makePermissions()); t.is(result, ''); }); test('multiple tables generate separate policies per table with correct resource refs', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -214,7 +205,6 @@ test('multiple tables generate separate policies per table with correct resource test('dashboard with read=true generates only dashboard:read', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -236,7 +226,6 @@ test('dashboard with read=true generates only dashboard:read', (t) => { test('dashboard with all flags true generates dashboard:read + dashboard:create + dashboard:edit + dashboard:delete', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -258,7 +247,6 @@ test('dashboard with all flags true generates dashboard:read + dashboard:create test('dashboard with all flags false generates no policies for that dashboard', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -276,7 +264,6 @@ test('dashboard with all flags false generates no policies for that dashboard', test('dashboard resource ref format uses connectionId/dashboardId', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -293,7 +280,6 @@ test('dashboard resource ref format uses connectionId/dashboardId', (t) => { test('resource ref format validation', (t) => { const result = generateCedarPolicyForGroup( - groupId, connectionId, false, makePermissions({ @@ -307,7 +293,7 @@ test('resource ref format validation', (t) => { ], }), ); - t.true(result.includes(`RocketAdmin::Group::"${groupId}"`)); + t.true(result.includes('principal,')); t.true(result.includes(`RocketAdmin::Connection::"${connectionId}"`)); t.true(result.includes(`RocketAdmin::Table::"${connectionId}/users"`)); }); diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-parser.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-parser.test.ts index 0da498da6..8289bb42d 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-parser.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-parser.test.ts @@ -6,7 +6,7 @@ const groupId = 'test-group-id'; const connectionId = 'test-connection-id'; test('parses connection:read into readonly access', (t) => { - const policy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`; + const policy = `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`; const result = parseCedarPolicyToClassicalPermissions(policy, connectionId, groupId); t.is(result.connection.accessLevel, AccessLevelEnum.readonly); @@ -15,8 +15,8 @@ test('parses connection:read into readonly access', (t) => { test('parses connection:read + connection:edit into edit access', (t) => { const policy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:edit",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:edit",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, ].join('\n\n'); const result = parseCedarPolicyToClassicalPermissions(policy, connectionId, groupId); @@ -24,7 +24,7 @@ test('parses connection:read + connection:edit into edit access', (t) => { }); test('parses group:read into readonly access', (t) => { - const policy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"group:read",\n resource == RocketAdmin::Group::"${groupId}"\n);`; + const policy = `permit(\n principal,\n action == RocketAdmin::Action::"group:read",\n resource == RocketAdmin::Group::"${groupId}"\n);`; const result = parseCedarPolicyToClassicalPermissions(policy, connectionId, groupId); t.is(result.group.accessLevel, AccessLevelEnum.readonly); @@ -33,8 +33,8 @@ test('parses group:read into readonly access', (t) => { test('parses group:read + group:edit into edit access', (t) => { const policy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"group:read",\n resource == RocketAdmin::Group::"${groupId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"group:edit",\n resource == RocketAdmin::Group::"${groupId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:read",\n resource == RocketAdmin::Group::"${groupId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:edit",\n resource == RocketAdmin::Group::"${groupId}"\n);`, ].join('\n\n'); const result = parseCedarPolicyToClassicalPermissions(policy, connectionId, groupId); @@ -42,7 +42,7 @@ test('parses group:read + group:edit into edit access', (t) => { }); test('parses table:read into visibility + readonly', (t) => { - const policy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`; + const policy = `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`; const result = parseCedarPolicyToClassicalPermissions(policy, connectionId, groupId); t.is(result.tables.length, 1); @@ -56,10 +56,10 @@ test('parses table:read into visibility + readonly', (t) => { test('parses table:read + table:add + table:edit + table:delete into full access', (t) => { const policy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:edit",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:delete",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:edit",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:delete",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, ].join('\n\n'); const result = parseCedarPolicyToClassicalPermissions(policy, connectionId, groupId); @@ -73,9 +73,9 @@ test('parses table:read + table:add + table:edit + table:delete into full access test('parses multiple tables separately', (t) => { const policy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/orders"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/orders"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/orders"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/orders"\n);`, ].join('\n\n'); const result = parseCedarPolicyToClassicalPermissions(policy, connectionId, groupId); @@ -91,10 +91,10 @@ test('parses multiple tables separately', (t) => { test('parses dashboard permissions', (t) => { const policy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"dashboard:read",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"dashboard:create",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"dashboard:edit",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"dashboard:delete",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:read",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:create",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:edit",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:delete",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, ].join('\n\n'); const result = parseCedarPolicyToClassicalPermissions(policy, connectionId, groupId); @@ -107,7 +107,7 @@ test('parses dashboard permissions', (t) => { }); test('parses wildcard policy (isMain) into full connection + group access', (t) => { - const policy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action,\n resource\n);`; + const policy = `permit(\n principal,\n action,\n resource\n);`; const result = parseCedarPolicyToClassicalPermissions(policy, connectionId, groupId); t.is(result.connection.accessLevel, AccessLevelEnum.edit); @@ -124,12 +124,12 @@ test('empty policy returns all none permissions', (t) => { test('complex policy with connection + group + table + dashboard permissions', (t) => { const policy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:edit",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"group:read",\n resource == RocketAdmin::Group::"${groupId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"dashboard:read",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:edit",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:read",\n resource == RocketAdmin::Group::"${groupId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/users"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:read",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, ].join('\n\n'); const result = parseCedarPolicyToClassicalPermissions(policy, connectionId, groupId); diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-cedar-save-policy-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-cedar-save-policy-e2e.test.ts index 7dfe7e4e3..91aaa0a54 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-cedar-save-policy-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-cedar-save-policy-e2e.test.ts @@ -77,8 +77,8 @@ test.serial( const tableName = testData.firstTableInfo.testTableName; const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const response = await request(app.getHttpServer()) @@ -118,14 +118,14 @@ test.serial( const tableName = testData.firstTableInfo.testTableName; const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:edit",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"group:read",\n resource == RocketAdmin::Group::"${groupId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"group:edit",\n resource == RocketAdmin::Group::"${groupId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:edit",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:delete",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:edit",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:read",\n resource == RocketAdmin::Group::"${groupId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:edit",\n resource == RocketAdmin::Group::"${groupId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:edit",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:delete",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const response = await request(app.getHttpServer()) @@ -164,8 +164,8 @@ test.serial( // Save cedar policy with connection:read + table:read const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const savePolicyResponse = await request(app.getHttpServer()) @@ -204,8 +204,8 @@ test.serial( // Save cedar policy with only connection:read + table:read (no table:add) const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const savePolicyResponse = await request(app.getHttpServer()) @@ -246,9 +246,9 @@ test.serial( // Save cedar policy with connection:read + table:read + table:add const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const savePolicyResponse = await request(app.getHttpServer()) @@ -284,7 +284,7 @@ test.serial( const connectionId = testData.connections.firstId; const groupId = testData.groups.createdGroupId; - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`; + const cedarPolicy = `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`; // Simple user does not have connection edit access const response = await request(app.getHttpServer()) @@ -351,7 +351,7 @@ test.serial( const connectionId = testData.connections.firstId; const adminGroupId = testData.groups.firstAdminGroupId; - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${adminGroupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`; + const cedarPolicy = `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`; const response = await request(app.getHttpServer()) .post(`/connection/cedar-policy/${connectionId}`) @@ -377,7 +377,7 @@ test.serial( const secondConnectionId = testData.connections.secondId; const groupId = testData.groups.createdGroupId; - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${secondConnectionId}"\n);`; + const cedarPolicy = `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${secondConnectionId}"\n);`; // Group belongs to first connection, but we're sending to second connection const response = await request(app.getHttpServer()) @@ -405,10 +405,10 @@ test.serial( const groupId = testData.groups.createdGroupId; const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"dashboard:read",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"dashboard:create",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"dashboard:edit",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:read",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:create",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:edit",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, ].join('\n\n'); const response = await request(app.getHttpServer()) @@ -446,12 +446,12 @@ test.serial( // First: save policy with full table access const fullPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:edit",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:edit",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:delete",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:edit",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:edit",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:delete",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const firstResponse = await request(app.getHttpServer()) @@ -467,8 +467,8 @@ test.serial( // Second: save policy with only read access (overwrite) const readOnlyPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const secondResponse = await request(app.getHttpServer()) @@ -492,61 +492,6 @@ test.serial( //****************************** CEDAR POLICY REFERENCE VALIDATION TESTS ****************************** -test.serial( - `${currentTest} should reject cedar policy that references a different group as principal`, - async (t) => { - try { - const testData = await createConnectionsAndInviteNewUserInNewGroupWithGroupPermissions(app); - const connectionId = testData.connections.firstId; - const groupId = testData.groups.createdGroupId; - const adminGroupId = testData.groups.firstAdminGroupId; - - // Policy references adminGroupId as principal instead of the target groupId - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${adminGroupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`; - - const response = await request(app.getHttpServer()) - .post(`/connection/cedar-policy/${connectionId}`) - .send({ cedarPolicy, groupId }) - .set('Cookie', testData.users.adminUserToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - t.is(response.status, 400); - t.is(JSON.parse(response.text).message, Messages.CEDAR_POLICY_REFERENCES_FOREIGN_PRINCIPAL); - } catch (error) { - console.error(error); - throw error; - } - }, -); - -test.serial( - `${currentTest} should reject cedar policy that references a non-existent group as principal`, - async (t) => { - try { - const testData = await createConnectionsAndInviteNewUserInNewGroupWithGroupPermissions(app); - const connectionId = testData.connections.firstId; - const groupId = testData.groups.createdGroupId; - const fakeGroupId = '00000000-0000-4000-a000-000000000099'; - - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${fakeGroupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`; - - const response = await request(app.getHttpServer()) - .post(`/connection/cedar-policy/${connectionId}`) - .send({ cedarPolicy, groupId }) - .set('Cookie', testData.users.adminUserToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - t.is(response.status, 400); - t.is(JSON.parse(response.text).message, Messages.CEDAR_POLICY_REFERENCES_FOREIGN_PRINCIPAL); - } catch (error) { - console.error(error); - throw error; - } - }, -); - test.serial( `${currentTest} should reject cedar policy that references a foreign connection`, async (t) => { @@ -557,7 +502,7 @@ test.serial( const groupId = testData.groups.createdGroupId; // Policy references secondConnectionId as the resource - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${secondConnectionId}"\n);`; + const cedarPolicy = `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${secondConnectionId}"\n);`; const response = await request(app.getHttpServer()) .post(`/connection/cedar-policy/${connectionId}`) @@ -586,8 +531,8 @@ test.serial( // Policy grants group:edit access to a group from the second connection const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"group:edit",\n resource == RocketAdmin::Group::"${secondAdminGroupId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:edit",\n resource == RocketAdmin::Group::"${secondAdminGroupId}"\n);`, ].join('\n\n'); const response = await request(app.getHttpServer()) @@ -617,7 +562,7 @@ test.serial( const tableName = testData.firstTableInfo.testTableName; // Policy references a table prefixed with the second connection ID - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${secondConnectionId}/${tableName}"\n);`; + const cedarPolicy = `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${secondConnectionId}/${tableName}"\n);`; const response = await request(app.getHttpServer()) .post(`/connection/cedar-policy/${connectionId}`) @@ -645,7 +590,7 @@ test.serial( const groupId = testData.groups.createdGroupId; // Policy references a dashboard prefixed with the second connection ID - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"dashboard:read",\n resource == RocketAdmin::Dashboard::"${secondConnectionId}/dash-1"\n);`; + const cedarPolicy = `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:read",\n resource == RocketAdmin::Dashboard::"${secondConnectionId}/dash-1"\n);`; const response = await request(app.getHttpServer()) .post(`/connection/cedar-policy/${connectionId}`) @@ -674,8 +619,8 @@ test.serial( // Policy grants group:read access to the admin group of the SAME connection - this is valid const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"group:read",\n resource == RocketAdmin::Group::"${adminGroupId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:read",\n resource == RocketAdmin::Group::"${adminGroupId}"\n);`, ].join('\n\n'); const response = await request(app.getHttpServer()) diff --git a/backend/test/ava-tests/saas-tests/saas-cedar-save-policy-e2e.test.ts b/backend/test/ava-tests/saas-tests/saas-cedar-save-policy-e2e.test.ts index dd4416a42..5c126c10f 100644 --- a/backend/test/ava-tests/saas-tests/saas-cedar-save-policy-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/saas-cedar-save-policy-e2e.test.ts @@ -71,8 +71,8 @@ test.serial( const tableName = testData.firstTableInfo.testTableName; const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const response = await request(app.getHttpServer()) @@ -113,14 +113,14 @@ test.serial( const tableName = testData.firstTableInfo.testTableName; const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:edit",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"group:read",\n resource == RocketAdmin::Group::"${groupId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"group:edit",\n resource == RocketAdmin::Group::"${groupId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:edit",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:delete",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:edit",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:read",\n resource == RocketAdmin::Group::"${groupId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:edit",\n resource == RocketAdmin::Group::"${groupId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:edit",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:delete",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const response = await request(app.getHttpServer()) @@ -160,8 +160,8 @@ test.serial( // Save cedar policy with connection:read + table:read const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const savePolicyResponse = await request(app.getHttpServer()) @@ -201,8 +201,8 @@ test.serial( // Save cedar policy with only connection:read + table:read (no table:add) const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const savePolicyResponse = await request(app.getHttpServer()) @@ -244,9 +244,9 @@ test.serial( // Save cedar policy with connection:read + table:read + table:add const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const savePolicyResponse = await request(app.getHttpServer()) @@ -283,7 +283,7 @@ test.serial( const connectionId = testData.connections.firstId; const groupId = testData.groups.createdGroupId; - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`; + const cedarPolicy = `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`; // Simple user has readonly access, not edit const response = await request(app.getHttpServer()) @@ -354,7 +354,7 @@ test.serial( const secondConnectionId = testData.connections.secondId; const groupId = testData.groups.createdGroupId; - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${secondConnectionId}"\n);`; + const cedarPolicy = `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${secondConnectionId}"\n);`; // Group belongs to first connection, but we're sending to second connection const response = await request(app.getHttpServer()) @@ -383,10 +383,10 @@ test.serial( const groupId = testData.groups.createdGroupId; const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"dashboard:read",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"dashboard:create",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"dashboard:edit",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:read",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:create",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"dashboard:edit",\n resource == RocketAdmin::Dashboard::"${connectionId}/dash-1"\n);`, ].join('\n\n'); const response = await request(app.getHttpServer()) @@ -425,12 +425,12 @@ test.serial( // First: save policy with full table access const fullPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:edit",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:edit",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:delete",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:edit",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:add",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:edit",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:delete",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const firstResponse = await request(app.getHttpServer()) @@ -446,8 +446,8 @@ test.serial( // Second: save policy with only read access (overwrite) const readOnlyPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${connectionId}/${tableName}"\n);`, ].join('\n\n'); const secondResponse = await request(app.getHttpServer()) @@ -471,34 +471,6 @@ test.serial( //****************************** CEDAR POLICY REFERENCE VALIDATION TESTS ****************************** -test.serial( - `${currentTest} should reject cedar policy that references a different group as principal`, - async (t) => { - try { - const testData = await createConnectionsAndInviteNewUserInNewGroupWithTableDifferentConnectionGroupReadOnlyPermissions(app); - const connectionId = testData.connections.firstId; - const groupId = testData.groups.createdGroupId; - const adminGroupId = testData.groups.firstAdminGroupId; - - // Policy references adminGroupId as principal instead of the target groupId - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${adminGroupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`; - - const response = await request(app.getHttpServer()) - .post(`/connection/cedar-policy/${connectionId}`) - .send({ cedarPolicy, groupId }) - .set('Cookie', testData.users.adminUserToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - t.is(response.status, 400); - t.is(JSON.parse(response.text).message, Messages.CEDAR_POLICY_REFERENCES_FOREIGN_PRINCIPAL); - } catch (error) { - console.error(error); - throw error; - } - }, -); - test.serial( `${currentTest} should reject cedar policy that references a foreign connection`, async (t) => { @@ -509,7 +481,7 @@ test.serial( const groupId = testData.groups.createdGroupId; // Policy references secondConnectionId as the resource - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${secondConnectionId}"\n);`; + const cedarPolicy = `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${secondConnectionId}"\n);`; const response = await request(app.getHttpServer()) .post(`/connection/cedar-policy/${connectionId}`) @@ -538,8 +510,8 @@ test.serial( // Policy grants group:edit access to a group from the second connection const cedarPolicy = [ - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, - `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"group:edit",\n resource == RocketAdmin::Group::"${secondAdminGroupId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"connection:read",\n resource == RocketAdmin::Connection::"${connectionId}"\n);`, + `permit(\n principal,\n action == RocketAdmin::Action::"group:edit",\n resource == RocketAdmin::Group::"${secondAdminGroupId}"\n);`, ].join('\n\n'); const response = await request(app.getHttpServer()) @@ -568,7 +540,7 @@ test.serial( const groupId = testData.groups.createdGroupId; const tableName = testData.firstTableInfo.testTableName; - const cedarPolicy = `permit(\n principal in RocketAdmin::Group::"${groupId}",\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${secondConnectionId}/${tableName}"\n);`; + const cedarPolicy = `permit(\n principal,\n action == RocketAdmin::Action::"table:read",\n resource == RocketAdmin::Table::"${secondConnectionId}/${tableName}"\n);`; const response = await request(app.getHttpServer()) .post(`/connection/cedar-policy/${connectionId}`)