Merge pull request #2422 from automatisch/aut-1515

feat(migrations): introduce manage permissions instead of create, update, delete, publish
This commit is contained in:
Ali BARIN
2025-04-15 11:15:27 +02:00
committed by GitHub
60 changed files with 581 additions and 556 deletions

View File

@@ -112,5 +112,6 @@
"src/" "src/"
], ],
"ext": "js" "ext": "js"
} },
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
} }

View File

@@ -74,7 +74,7 @@ describe('PATCH /api/v1/admin/roles/:roleId', () => {
it('should return the updated role with sanitized permissions', async () => { it('should return the updated role with sanitized permissions', async () => {
const validPermission = { const validPermission = {
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
conditions: ['isCreator'], conditions: ['isCreator'],
}; };

View File

@@ -22,7 +22,7 @@ describe('POST /api/v1/apps/:appKey/connections', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: role.id, roleId: role.id,
}); });

View File

@@ -15,14 +15,7 @@ describe('DELETE /api/v1/connections/:connectionId', () => {
currentUserRole = await currentUser.$relatedQuery('role'); currentUserRole = await currentUser.$relatedQuery('role');
await createPermission({ await createPermission({
action: 'delete', action: 'manage',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -14,7 +14,7 @@ describe('POST /api/v1/connections/:connectionId/auth-url', () => {
currentUser = await createUser(); currentUser = await createUser();
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUser.roleId, roleId: currentUser.roleId,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -32,7 +32,7 @@ describe('POST /api/v1/connections/:connectionId/reset', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -68,7 +68,7 @@ describe('POST /api/v1/connections/:connectionId/reset', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -84,7 +84,7 @@ describe('POST /api/v1/connections/:connectionId/reset', () => {
const notExistingConnectionUUID = Crypto.randomUUID(); const notExistingConnectionUUID = Crypto.randomUUID();
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -98,7 +98,7 @@ describe('POST /api/v1/connections/:connectionId/reset', () => {
it('should return bad request response for invalid UUID', async () => { it('should return bad request response for invalid UUID', async () => {
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -32,7 +32,7 @@ describe('POST /api/v1/connections/:connectionId/test', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -63,7 +63,7 @@ describe('POST /api/v1/connections/:connectionId/test', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -88,7 +88,7 @@ describe('POST /api/v1/connections/:connectionId/test', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -109,7 +109,7 @@ describe('POST /api/v1/connections/:connectionId/test', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -34,7 +34,7 @@ describe('PATCH /api/v1/connections/:connectionId', () => {
const currentUserConnection = await createConnection(connectionData); const currentUserConnection = await createConnection(connectionData);
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -72,7 +72,7 @@ describe('PATCH /api/v1/connections/:connectionId', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -88,7 +88,7 @@ describe('PATCH /api/v1/connections/:connectionId', () => {
const notExistingConnectionUUID = Crypto.randomUUID(); const notExistingConnectionUUID = Crypto.randomUUID();
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -102,7 +102,7 @@ describe('PATCH /api/v1/connections/:connectionId', () => {
it('should return bad request response for invalid UUID', async () => { it('should return bad request response for invalid UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -26,7 +26,7 @@ describe('POST /api/v1/connections/:connectionId/verify', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -54,7 +54,7 @@ describe('POST /api/v1/connections/:connectionId/verify', () => {
const notExistingConnectionUUID = Crypto.randomUUID(); const notExistingConnectionUUID = Crypto.randomUUID();
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -68,7 +68,7 @@ describe('POST /api/v1/connections/:connectionId/verify', () => {
it('should return bad request response for invalid UUID', async () => { it('should return bad request response for invalid UUID', async () => {
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -20,7 +20,7 @@ describe('POST /api/v1/flows', () => {
it('should create an empty flow when no templateId is provided', async () => { it('should create an empty flow when no templateId is provided', async () => {
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -42,7 +42,7 @@ describe('POST /api/v1/flows', () => {
it('should create a flow from template when templateId is provided', async () => { it('should create a flow from template when templateId is provided', async () => {
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -36,7 +36,7 @@ describe('POST /api/v1/flows/:flowId/steps', () => {
await createPermission({ await createPermission({
roleId: currentUser.roleId, roleId: currentUser.roleId,
subject: 'Flow', subject: 'Flow',
action: 'update', action: 'manage',
conditions: ['isCreator'], conditions: ['isCreator'],
}); });
@@ -78,7 +78,7 @@ describe('POST /api/v1/flows/:flowId/steps', () => {
await createPermission({ await createPermission({
roleId: currentUser.roleId, roleId: currentUser.roleId,
subject: 'Flow', subject: 'Flow',
action: 'update', action: 'manage',
conditions: [], conditions: [],
}); });
@@ -109,7 +109,7 @@ describe('POST /api/v1/flows/:flowId/steps', () => {
await createPermission({ await createPermission({
roleId: currentUser.roleId, roleId: currentUser.roleId,
subject: 'Flow', subject: 'Flow',
action: 'update', action: 'manage',
conditions: ['isCreator'], conditions: ['isCreator'],
}); });
@@ -133,7 +133,7 @@ describe('POST /api/v1/flows/:flowId/steps', () => {
await createPermission({ await createPermission({
roleId: currentUser.roleId, roleId: currentUser.roleId,
subject: 'Flow', subject: 'Flow',
action: 'update', action: 'manage',
conditions: ['isCreator'], conditions: ['isCreator'],
}); });
@@ -159,7 +159,7 @@ describe('POST /api/v1/flows/:flowId/steps', () => {
await createPermission({ await createPermission({
roleId: currentUser.roleId, roleId: currentUser.roleId,
subject: 'Flow', subject: 'Flow',
action: 'update', action: 'manage',
conditions: ['isCreator'], conditions: ['isCreator'],
}); });

View File

@@ -28,7 +28,7 @@ describe('DELETE /api/v1/flows/:flowId', () => {
}); });
await createPermission({ await createPermission({
action: 'delete', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -52,7 +52,7 @@ describe('DELETE /api/v1/flows/:flowId', () => {
}); });
await createPermission({ await createPermission({
action: 'delete', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -73,7 +73,7 @@ describe('DELETE /api/v1/flows/:flowId', () => {
}); });
await createPermission({ await createPermission({
action: 'delete', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -96,7 +96,7 @@ describe('DELETE /api/v1/flows/:flowId', () => {
}); });
await createPermission({ await createPermission({
action: 'delete', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -48,7 +48,7 @@ describe('POST /api/v1/flows/:flowId/duplicate', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -106,7 +106,7 @@ describe('POST /api/v1/flows/:flowId/duplicate', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -143,7 +143,7 @@ describe('POST /api/v1/flows/:flowId/duplicate', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -169,7 +169,7 @@ describe('POST /api/v1/flows/:flowId/duplicate', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -190,7 +190,7 @@ describe('POST /api/v1/flows/:flowId/duplicate', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -56,7 +56,7 @@ describe('POST /api/v1/flows/:flowId/export', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -113,7 +113,7 @@ describe('POST /api/v1/flows/:flowId/export', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -141,7 +141,7 @@ describe('POST /api/v1/flows/:flowId/export', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -167,7 +167,7 @@ describe('POST /api/v1/flows/:flowId/export', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -188,7 +188,7 @@ describe('POST /api/v1/flows/:flowId/export', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -48,7 +48,7 @@ describe('POST /api/v1/flows/import', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -124,7 +124,7 @@ describe('POST /api/v1/flows/import', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -206,7 +206,7 @@ describe('POST /api/v1/flows/import', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -279,7 +279,7 @@ describe('POST /api/v1/flows/import', () => {
}); });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -330,7 +330,7 @@ describe('POST /api/v1/flows/import', () => {
const currentUserFlow = await createFlow({ userId: currentUser.id }); const currentUserFlow = await createFlow({ userId: currentUser.id });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -45,7 +45,7 @@ describe('PATCH /api/v1/flows/:flowId/folder', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -77,7 +77,7 @@ describe('PATCH /api/v1/flows/:flowId/folder', () => {
const anotherUserFlow = await createFlow({ userId: anotherUser.id }); const anotherUserFlow = await createFlow({ userId: anotherUser.id });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -96,7 +96,7 @@ describe('PATCH /api/v1/flows/:flowId/folder', () => {
const anotherUserFolder = await createFolder({ userId: anotherUser.id }); const anotherUserFolder = await createFolder({ userId: anotherUser.id });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -111,7 +111,7 @@ describe('PATCH /api/v1/flows/:flowId/folder', () => {
it('should return not found response for not existing flow UUID', async () => { it('should return not found response for not existing flow UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -130,7 +130,7 @@ describe('PATCH /api/v1/flows/:flowId/folder', () => {
const flow = await createFlow({ userId: currentUser.id }); const flow = await createFlow({ userId: currentUser.id });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -147,7 +147,7 @@ describe('PATCH /api/v1/flows/:flowId/folder', () => {
it('should return bad request response for invalid flow UUID', async () => { it('should return bad request response for invalid flow UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
}); });
@@ -160,7 +160,7 @@ describe('PATCH /api/v1/flows/:flowId/folder', () => {
it('should return bad request response for invalid folder UUID', async () => { it('should return bad request response for invalid folder UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
}); });

View File

@@ -51,7 +51,7 @@ describe('PATCH /api/v1/flows/:flowId/status', () => {
}); });
await createPermission({ await createPermission({
action: 'publish', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -114,7 +114,7 @@ describe('PATCH /api/v1/flows/:flowId/status', () => {
}); });
await createPermission({ await createPermission({
action: 'publish', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -152,7 +152,7 @@ describe('PATCH /api/v1/flows/:flowId/status', () => {
}); });
await createPermission({ await createPermission({
action: 'publish', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -178,7 +178,7 @@ describe('PATCH /api/v1/flows/:flowId/status', () => {
}); });
await createPermission({ await createPermission({
action: 'publish', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -199,7 +199,7 @@ describe('PATCH /api/v1/flows/:flowId/status', () => {
}); });
await createPermission({ await createPermission({
action: 'publish', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -29,7 +29,7 @@ describe('PATCH /api/v1/flows/:flowId', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -65,7 +65,7 @@ describe('PATCH /api/v1/flows/:flowId', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -98,7 +98,7 @@ describe('PATCH /api/v1/flows/:flowId', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -121,7 +121,7 @@ describe('PATCH /api/v1/flows/:flowId', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -144,7 +144,7 @@ describe('PATCH /api/v1/flows/:flowId', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -18,7 +18,7 @@ describe('POST /api/v1/folders', () => {
it('should return created flow', async () => { it('should return created flow', async () => {
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],

View File

@@ -21,7 +21,7 @@ describe('DELETE /api/v1/folders/:folderId', () => {
const currentUserFolder = await createFolder({ userId: currentUser.id }); const currentUserFolder = await createFolder({ userId: currentUser.id });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
}); });
@@ -34,7 +34,7 @@ describe('DELETE /api/v1/folders/:folderId', () => {
it('should return not found response for not existing folder UUID', async () => { it('should return not found response for not existing folder UUID', async () => {
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
}); });
@@ -49,7 +49,7 @@ describe('DELETE /api/v1/folders/:folderId', () => {
it('should return bad request response for invalid UUID', async () => { it('should return bad request response for invalid UUID', async () => {
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
}); });

View File

@@ -22,7 +22,7 @@ describe('PATCH /api/v1/folders/:folderId', () => {
const currentUserFolder = await createFolder({ userId: currentUser.id }); const currentUserFolder = await createFolder({ userId: currentUser.id });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
}); });
@@ -47,7 +47,7 @@ describe('PATCH /api/v1/folders/:folderId', () => {
it('should return not found response for not existing folder UUID', async () => { it('should return not found response for not existing folder UUID', async () => {
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
}); });
@@ -62,7 +62,7 @@ describe('PATCH /api/v1/folders/:folderId', () => {
it('should return bad request response for invalid UUID', async () => { it('should return bad request response for invalid UUID', async () => {
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
}); });
@@ -77,7 +77,7 @@ describe('PATCH /api/v1/folders/:folderId', () => {
const currentUserFolder = await createFolder({ userId: currentUser.id }); const currentUserFolder = await createFolder({ userId: currentUser.id });
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
}); });

View File

@@ -63,7 +63,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -102,7 +102,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -156,7 +156,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -177,7 +177,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
it('should return not found response for not existing step UUID', async () => { it('should return not found response for not existing step UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -200,7 +200,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
it('should return not found response for existing step UUID without app key', async () => { it('should return not found response for existing step UUID without app key', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -223,7 +223,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
it('should return bad request response for invalid UUID', async () => { it('should return bad request response for invalid UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],

View File

@@ -37,7 +37,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -78,7 +78,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -102,7 +102,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
it('should return not found response for not existing step UUID', async () => { it('should return not found response for not existing step UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -125,7 +125,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
it('should return not found response for existing step UUID without app key', async () => { it('should return not found response for existing step UUID without app key', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -149,7 +149,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
it('should return bad request response for invalid UUID', async () => { it('should return bad request response for invalid UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],

View File

@@ -41,7 +41,7 @@ describe('DELETE /api/v1/steps/:stepId', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -76,7 +76,7 @@ describe('DELETE /api/v1/steps/:stepId', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -90,7 +90,7 @@ describe('DELETE /api/v1/steps/:stepId', () => {
it('should return not found response for not existing step UUID', async () => { it('should return not found response for not existing step UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -113,7 +113,7 @@ describe('DELETE /api/v1/steps/:stepId', () => {
it('should return bad request response for invalid step UUID', async () => { it('should return bad request response for invalid step UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],

View File

@@ -54,7 +54,7 @@ describe('GET /api/v1/steps/:stepId/previous-steps', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -108,7 +108,7 @@ describe('GET /api/v1/steps/:stepId/previous-steps', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -129,7 +129,7 @@ describe('GET /api/v1/steps/:stepId/previous-steps', () => {
it('should return not found response for not existing step UUID', async () => { it('should return not found response for not existing step UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -152,7 +152,7 @@ describe('GET /api/v1/steps/:stepId/previous-steps', () => {
it('should return bad request response for invalid UUID', async () => { it('should return bad request response for invalid UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],

View File

@@ -69,7 +69,7 @@ describe('POST /api/v1/steps/:stepId/test', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -140,7 +140,7 @@ describe('POST /api/v1/steps/:stepId/test', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -165,7 +165,7 @@ describe('POST /api/v1/steps/:stepId/test', () => {
it('should return not found response for not existing step UUID', async () => { it('should return not found response for not existing step UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -188,7 +188,7 @@ describe('POST /api/v1/steps/:stepId/test', () => {
it('should return bad request response for invalid step UUID', async () => { it('should return bad request response for invalid step UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],

View File

@@ -46,7 +46,7 @@ describe('PATCH /api/v1/steps/:stepId', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUser.roleId, roleId: currentUser.roleId,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -96,7 +96,7 @@ describe('PATCH /api/v1/steps/:stepId', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUser.roleId, roleId: currentUser.roleId,
conditions: [], conditions: [],
@@ -145,7 +145,7 @@ describe('PATCH /api/v1/steps/:stepId', () => {
}); });
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUser.roleId, roleId: currentUser.roleId,
conditions: ['isCreator'], conditions: ['isCreator'],
@@ -169,7 +169,7 @@ describe('PATCH /api/v1/steps/:stepId', () => {
it('should return not found response for not existing step UUID', async () => { it('should return not found response for not existing step UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUser.roleId, roleId: currentUser.roleId,
conditions: [], conditions: [],
@@ -192,7 +192,7 @@ describe('PATCH /api/v1/steps/:stepId', () => {
it('should return bad request response for invalid step UUID', async () => { it('should return bad request response for invalid step UUID', async () => {
await createPermission({ await createPermission({
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUser.roleId, roleId: currentUser.roleId,
conditions: [], conditions: [],

View File

@@ -24,7 +24,7 @@ describe('GET /api/v1/templates', () => {
it('should return templates when templates are enabled and user has create flow permission', async () => { it('should return templates when templates are enabled and user has create flow permission', async () => {
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],
@@ -45,7 +45,7 @@ describe('GET /api/v1/templates', () => {
it('should return 403 when templates are disabled', async () => { it('should return 403 when templates are disabled', async () => {
await createPermission({ await createPermission({
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
roleId: currentUserRole.id, roleId: currentUserRole.id,
conditions: [], conditions: [],

View File

@@ -0,0 +1,110 @@
export async function up(knex) {
const roles = await knex('roles').select('id', 'name');
// Define the required actions for each subject
const subjectActionMap = {
Connection: ['create', 'delete', 'update'],
Flow: ['create', 'delete', 'publish', 'update'],
User: ['create', 'delete', 'update'],
Role: ['create', 'delete', 'update'],
SamlAuthProvider: ['create', 'delete', 'update'],
Config: ['update'],
App: ['create', 'delete', 'update'],
};
for (const role of roles) {
for (const [subject, actions] of Object.entries(subjectActionMap)) {
const rolePermissions = await knex('permissions')
.where({ role_id: role.id, subject })
.whereIn('action', actions)
.select('id', 'action', 'conditions');
const actionCounts = rolePermissions.reduce((counts, perm) => {
counts[perm.action] = (counts[perm.action] || 0) + 1;
return counts;
}, {});
let allActionsExist = true;
for (const action of actions) {
if (actionCounts[action] !== 1) {
allActionsExist = false;
break;
}
}
// Determine if any of the permissions has the 'isCreator' condition
const hasIsCreatorCondition = rolePermissions.some(
(perm) => perm.conditions && perm.conditions.includes('isCreator')
);
// Delete the existing permissions for the required actions
await knex('permissions')
.where({ role_id: role.id, subject })
.whereIn('action', actions)
.del();
// If all required actions exist, insert a new permission with the 'manage' action
if (allActionsExist) {
await knex('permissions').insert({
role_id: role.id,
subject,
action: 'manage',
conditions: JSON.stringify(
hasIsCreatorCondition ? ['isCreator'] : []
),
});
}
}
}
return;
}
export async function down(knex) {
const roles = await knex('roles').select('id', 'name');
// Define the required actions for each subject
const subjectActionMap = {
Connection: ['create', 'delete', 'update'],
Flow: ['create', 'delete', 'publish', 'update'],
User: ['create', 'delete', 'update'],
Role: ['create', 'delete', 'update'],
SamlAuthProvider: ['create', 'delete', 'update'],
Config: ['update'],
App: ['create', 'delete', 'update'],
};
for (const role of roles) {
for (const [subject, actions] of Object.entries(subjectActionMap)) {
// Find the 'manage' permission for the subject
const managePermission = await knex('permissions')
.where({ role_id: role.id, subject, action: 'manage' })
.first();
if (managePermission) {
// Determine if the 'manage' permission has the 'isCreator' condition
const hasIsCreatorCondition =
managePermission.conditions.includes('isCreator');
// Delete the 'manage' permission
await knex('permissions')
.where({ role_id: role.id, subject, action: 'manage' })
.del();
// Restore the original permissions for the subject
const restoredPermissions = actions.map((action) => ({
role_id: role.id,
subject,
action,
conditions: JSON.stringify(
hasIsCreatorCondition ? ['isCreator'] : []
),
}));
await knex('permissions').insert(restoredPermissions);
}
}
}
return;
}

View File

@@ -22,19 +22,19 @@ const authorizationList = {
subject: 'Flow', subject: 'Flow',
}, },
'POST /api/v1/flows/': { 'POST /api/v1/flows/': {
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'PATCH /api/v1/flows/:flowId': { 'PATCH /api/v1/flows/:flowId': {
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'DELETE /api/v1/flows/:flowId': { 'DELETE /api/v1/flows/:flowId': {
action: 'delete', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'GET /api/v1/templates/': { 'GET /api/v1/templates/': {
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'GET /api/v1/steps/:stepId/connection': { 'GET /api/v1/steps/:stepId/connection': {
@@ -42,23 +42,23 @@ const authorizationList = {
subject: 'Flow', subject: 'Flow',
}, },
'PATCH /api/v1/steps/:stepId': { 'PATCH /api/v1/steps/:stepId': {
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'POST /api/v1/steps/:stepId/test': { 'POST /api/v1/steps/:stepId/test': {
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'GET /api/v1/steps/:stepId/previous-steps': { 'GET /api/v1/steps/:stepId/previous-steps': {
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'POST /api/v1/steps/:stepId/dynamic-fields': { 'POST /api/v1/steps/:stepId/dynamic-fields': {
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'POST /api/v1/steps/:stepId/dynamic-data': { 'POST /api/v1/steps/:stepId/dynamic-data': {
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'GET /api/v1/connections/:connectionId/flows': { 'GET /api/v1/connections/:connectionId/flows': {
@@ -66,11 +66,11 @@ const authorizationList = {
subject: 'Flow', subject: 'Flow',
}, },
'POST /api/v1/connections/:connectionId/test': { 'POST /api/v1/connections/:connectionId/test': {
action: 'update', action: 'manage',
subject: 'Connection', subject: 'Connection',
}, },
'POST /api/v1/connections/:connectionId/verify': { 'POST /api/v1/connections/:connectionId/verify': {
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
}, },
'GET /api/v1/apps/:appKey/flows': { 'GET /api/v1/apps/:appKey/flows': {
@@ -94,59 +94,59 @@ const authorizationList = {
subject: 'Execution', subject: 'Execution',
}, },
'DELETE /api/v1/steps/:stepId': { 'DELETE /api/v1/steps/:stepId': {
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'PATCH /api/v1/connections/:connectionId': { 'PATCH /api/v1/connections/:connectionId': {
action: 'update', action: 'manage',
subject: 'Connection', subject: 'Connection',
}, },
'DELETE /api/v1/connections/:connectionId': { 'DELETE /api/v1/connections/:connectionId': {
action: 'delete', action: 'manage',
subject: 'Connection', subject: 'Connection',
}, },
'POST /api/v1/connections/:connectionId/reset': { 'POST /api/v1/connections/:connectionId/reset': {
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
}, },
'PATCH /api/v1/flows/:flowId/status': { 'PATCH /api/v1/flows/:flowId/status': {
action: 'publish', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'POST /api/v1/flows/:flowId/duplicate': { 'POST /api/v1/flows/:flowId/duplicate': {
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'POST /api/v1/flows/:flowId/export': { 'POST /api/v1/flows/:flowId/export': {
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'POST /api/v1/flows/import': { 'POST /api/v1/flows/import': {
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'POST /api/v1/flows/:flowId/steps': { 'POST /api/v1/flows/:flowId/steps': {
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'POST /api/v1/apps/:appKey/connections': { 'POST /api/v1/apps/:appKey/connections': {
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
}, },
'POST /api/v1/connections/:connectionId/auth-url': { 'POST /api/v1/connections/:connectionId/auth-url': {
action: 'create', action: 'manage',
subject: 'Connection', subject: 'Connection',
}, },
'POST /api/v1/folders/': { 'POST /api/v1/folders/': {
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'PATCH /api/v1/folders/:folderId': { 'PATCH /api/v1/folders/:folderId': {
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'DELETE /api/v1/folders/:folderId': { 'DELETE /api/v1/folders/:folderId': {
action: 'create', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'GET /api/v1/folders/': { 'GET /api/v1/folders/': {
@@ -154,7 +154,7 @@ const authorizationList = {
subject: 'Flow', subject: 'Flow',
}, },
'PATCH /api/v1/flows/:flowId/folder': { 'PATCH /api/v1/flows/:flowId/folder': {
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
}, },
'GET /api/v1/flows/:flowId/folder': { 'GET /api/v1/flows/:flowId/folder': {

View File

@@ -17,56 +17,22 @@ const permissionCatalog = {
conditions: [ conditions: [
{ {
key: 'isCreator', key: 'isCreator',
label: 'Is creator' label: 'Is creator',
} },
], ],
actions: [ actions: [
{
label: 'Create',
key: 'create',
subjects: [
Connection.key,
Flow.key,
]
},
{ {
label: 'Read', label: 'Read',
key: 'read', key: 'read',
subjects: [ subjects: [Connection.key, Execution.key, Flow.key],
Connection.key,
Execution.key,
Flow.key,
]
}, },
{ {
label: 'Update', label: 'Manage',
key: 'update', key: 'manage',
subjects: [ subjects: [Connection.key, Flow.key],
Connection.key,
Flow.key,
]
}, },
{
label: 'Delete',
key: 'delete',
subjects: [
Connection.key,
Flow.key,
]
},
{
label: 'Publish',
key: 'publish',
subjects: [
Flow.key,
]
}
], ],
subjects: [ subjects: [Connection, Flow, Execution],
Connection,
Flow,
Execution
]
}; };
export default permissionCatalog; export default permissionCatalog;

View File

@@ -14,10 +14,10 @@ describe('Permission model', () => {
it('filter should return only valid permissions based on permission catalog', () => { it('filter should return only valid permissions based on permission catalog', () => {
const permissions = [ const permissions = [
{ action: 'read', subject: 'Flow', conditions: ['isCreator'] }, { action: 'read', subject: 'Flow', conditions: ['isCreator'] },
{ action: 'delete', subject: 'Connection', conditions: [] }, { action: 'manage', subject: 'Connection', conditions: [] },
{ action: 'publish', subject: 'Flow', conditions: ['isCreator'] }, { action: 'manage', subject: 'Flow', conditions: ['isCreator'] },
{ action: 'update', subject: 'Execution', conditions: [] }, // Invalid subject { action: 'manage', subject: 'Execution', conditions: [] }, // Invalid subject
{ action: 'read', subject: 'Execution', conditions: ['invalid'] }, // Invalid condition { action: 'manage', subject: 'Execution', conditions: ['invalid'] }, // Invalid condition
{ action: 'invalid', subject: 'Execution', conditions: [] }, // Invalid action { action: 'invalid', subject: 'Execution', conditions: [] }, // Invalid action
]; ];
@@ -25,15 +25,15 @@ describe('Permission model', () => {
expect(result).toStrictEqual([ expect(result).toStrictEqual([
{ action: 'read', subject: 'Flow', conditions: ['isCreator'] }, { action: 'read', subject: 'Flow', conditions: ['isCreator'] },
{ action: 'delete', subject: 'Connection', conditions: [] }, { action: 'manage', subject: 'Connection', conditions: [] },
{ action: 'publish', subject: 'Flow', conditions: ['isCreator'] }, { action: 'manage', subject: 'Flow', conditions: ['isCreator'] },
]); ]);
}); });
describe('findAction', () => { describe('findAction', () => {
it('should return action from permission catalog', () => { it('should return action from permission catalog', () => {
const action = Permission.findAction('create'); const action = Permission.findAction('manage');
expect(action.key).toStrictEqual('create'); expect(action.key).toStrictEqual('manage');
}); });
it('should return undefined for invalid actions', () => { it('should return undefined for invalid actions', () => {
@@ -45,7 +45,7 @@ describe('Permission model', () => {
describe('isSubjectValid', () => { describe('isSubjectValid', () => {
it('should return true for valid subjects', () => { it('should return true for valid subjects', () => {
const validAction = permissionCatalog.actions.find( const validAction = permissionCatalog.actions.find(
(action) => action.key === 'create' (action) => action.key === 'manage'
); );
const validSubject = Permission.isSubjectValid('Connection', validAction); const validSubject = Permission.isSubjectValid('Connection', validAction);
@@ -54,7 +54,7 @@ describe('Permission model', () => {
it('should return false for invalid subjects', () => { it('should return false for invalid subjects', () => {
const validAction = permissionCatalog.actions.find( const validAction = permissionCatalog.actions.find(
(action) => action.key === 'create' (action) => action.key === 'manage'
); );
const invalidSubject = Permission.isSubjectValid( const invalidSubject = Permission.isSubjectValid(

View File

@@ -166,7 +166,7 @@ describe('Role model', () => {
description: 'Updated description', description: 'Updated description',
permissions: [ permissions: [
{ {
action: 'update', action: 'manage',
subject: 'Flow', subject: 'Flow',
conditions: [], conditions: [],
}, },

View File

@@ -1,31 +1,16 @@
const getPermissionsCatalogMock = async () => { const getPermissionsCatalogMock = async () => {
const data = { const data = {
actions: [ actions: [
{
key: 'create',
label: 'Create',
subjects: ['Connection', 'Flow'],
},
{ {
key: 'read', key: 'read',
label: 'Read', label: 'Read',
subjects: ['Connection', 'Execution', 'Flow'], subjects: ['Connection', 'Execution', 'Flow'],
}, },
{ {
key: 'update', key: 'manage',
label: 'Update', label: 'Manage',
subjects: ['Connection', 'Flow'], subjects: ['Connection', 'Flow'],
}, },
{
key: 'delete',
label: 'Delete',
subjects: ['Connection', 'Flow'],
},
{
key: 'publish',
label: 'Publish',
subjects: ['Flow'],
},
], ],
conditions: [ conditions: [
{ {

View File

@@ -14,98 +14,22 @@ export class AdminCreateRolePage extends AuthenticatedPage {
this.nameInput = page.getByTestId('name-input'); this.nameInput = page.getByTestId('name-input');
this.descriptionInput = page.getByTestId('description-input'); this.descriptionInput = page.getByTestId('description-input');
this.createButton = page.getByTestId('create-button'); this.createButton = page.getByTestId('create-button');
this.connectionRow = page.getByTestId('Connection-permission-row');
this.executionRow = page.getByTestId('Execution-permission-row');
this.flowRow = page.getByTestId('Flow-permission-row');
this.pageTitle = page.getByTestId('create-role-title'); this.pageTitle = page.getByTestId('create-role-title');
this.permissionsCatalog = page.getByTestId('permissions-catalog'); this.permissionsCatalog = page.getByTestId('permissions-catalog');
}
/** this.connectionPermissionRow = page.getByTestId(
* @param {('Connection'|'Execution'|'Flow')} subject 'Connection-permission-row'
*/ );
getRoleConditionsModal(subject) { this.flowPermissionRow = page.getByTestId('Flow-permission-row');
return new RoleConditionsModal(this.page, subject); this.executionPermissionRow = page.getByTestId('Execution-permission-row');
} this.isCreatorReadCheckbox = page
.getByTestId('isCreator-read-checkbox')
async getPermissionConfigs() { .locator('input');
const subjects = ['Connection', 'Flow', 'Execution']; this.readCheckbox = page.getByTestId('read-checkbox').locator('input');
const permissionConfigs = []; this.isCreatorManageCheckbox = page
for (let subject of subjects) { .getByTestId('isCreator-manage-checkbox')
const row = this.getSubjectRow(subject); .locator('input');
const actionInputs = await this.getRowInputs(row); this.manageCheckbox = page.getByTestId('manage-checkbox').locator('input');
Object.keys(actionInputs).forEach((action) => {
permissionConfigs.push({
action,
locator: actionInputs[action],
subject,
row,
});
});
}
return permissionConfigs;
}
/**
*
* @param {(
* 'Connection' | 'Flow' | 'Execution'
* )} subject
*/
getSubjectRow(subject) {
const k = `${subject.toLowerCase()}Row`;
if (this[k]) {
return this[k];
} else {
throw 'Unknown row';
}
}
/**
* @param {import('@playwright/test').Locator} row
*/
async getRowInputs(row) {
const inputs = {
// settingsButton: row.getByTestId('permission-settings-button')
};
for (let input of ['create', 'read', 'update', 'delete', 'publish']) {
const testId = `${input}-checkbox`;
if ((await row.getByTestId(testId).count()) > 0) {
inputs[input] = row.getByTestId(testId).locator('input');
}
}
return inputs;
}
/**
* @param {import('@playwright/test').Locator} row
*/
async clickPermissionSettings(row) {
await row.getByTestId('permission-settings-button').click();
}
/**
*
* @param {string} subject
* @param {'create'|'read'|'update'|'delete'|'publish'} action
* @param {boolean} val
*/
async updateAction(subject, action, val) {
const row = await this.getSubjectRow(subject);
const inputs = await this.getRowInputs(row);
if (inputs[action]) {
if (await inputs[action].isChecked()) {
if (!val) {
await inputs[action].click();
}
} else {
if (val) {
await inputs[action].click();
}
}
} else {
throw new Error(`${subject} does not have action ${action}`);
}
} }
async waitForPermissionsCatalogToVisible() { async waitForPermissionsCatalogToVisible() {

View File

@@ -1,69 +1,55 @@
const { test, expect } = require('../../fixtures/index'); const { test, expect } = require('../../fixtures/index');
test( test('Check Own permissions when All are checked', async ({
'Role permissions conform with role conditions ', adminRolesPage,
async({ adminRolesPage, adminCreateRolePage }) => { adminCreateRolePage,
await adminRolesPage.navigateTo(); }) => {
await adminRolesPage.createRoleButton.click(); await adminRolesPage.navigateTo();
await adminRolesPage.createRoleButton.click();
await adminCreateRolePage.waitForPermissionsCatalogToVisible();
/* await adminCreateRolePage.connectionPermissionRow
example config: { .locator(adminCreateRolePage.readCheckbox)
action: 'read', .check();
subject: 'connection', await expect(
row: page.getByTestId('connection-permission-row'), adminCreateRolePage.connectionPermissionRow.locator(
locator: row.getByTestId('read-checkbox') adminCreateRolePage.isCreatorReadCheckbox
} )
*/ ).toBeChecked();
const permissionConfigs =
await adminCreateRolePage.getPermissionConfigs();
await test.step( await adminCreateRolePage.flowPermissionRow
'Iterate over each permission config and make sure role conditions conform', .locator(adminCreateRolePage.readCheckbox)
async () => { .check();
for (let config of permissionConfigs) { await expect(
await config.locator.click(); adminCreateRolePage.flowPermissionRow.locator(
await adminCreateRolePage.clickPermissionSettings(config.row); adminCreateRolePage.isCreatorReadCheckbox
const modal = adminCreateRolePage.getRoleConditionsModal( )
config.subject ).toBeChecked();
);
await expect(modal.modal).toBeVisible();
const conditions = await modal.getAvailableConditions();
for (let conditionAction of Object.keys(conditions)) {
if (conditionAction === config.action) {
await expect(conditions[conditionAction]).not.toBeDisabled();
} else {
await expect(conditions[conditionAction]).toBeDisabled();
}
}
await modal.close();
await config.locator.click();
}
}
);
}
);
test( await adminCreateRolePage.executionPermissionRow
'Default role permissions conforms with role conditions', .locator(adminCreateRolePage.readCheckbox)
async({ adminRolesPage, adminCreateRolePage }) => { .check();
await adminRolesPage.navigateTo(); await expect(
await adminRolesPage.createRoleButton.click(); adminCreateRolePage.executionPermissionRow.locator(
adminCreateRolePage.isCreatorReadCheckbox
)
).toBeChecked();
const subjects = ['Connection', 'Execution', 'Flow']; await adminCreateRolePage.connectionPermissionRow
for (let subject of subjects) { .locator(adminCreateRolePage.manageCheckbox)
const row = adminCreateRolePage.getSubjectRow(subject); .check();
const modal = adminCreateRolePage.getRoleConditionsModal(subject); await expect(
await adminCreateRolePage.clickPermissionSettings(row); adminCreateRolePage.connectionPermissionRow.locator(
await expect(modal.modal).toBeVisible(); adminCreateRolePage.isCreatorManageCheckbox
const availableConditions = await modal.getAvailableConditions(); )
const conditions = ['create', 'read', 'update', 'delete', 'publish']; ).toBeChecked();
for (let condition of conditions) {
if (availableConditions[condition]) {
await expect(availableConditions[condition]).toBeDisabled();
}
}
await modal.close();
}
} await adminCreateRolePage.flowPermissionRow
); .locator(adminCreateRolePage.manageCheckbox)
.check();
await expect(
adminCreateRolePage.flowPermissionRow.locator(
adminCreateRolePage.isCreatorManageCheckbox
)
).toBeChecked();
});

View File

@@ -31,7 +31,7 @@ export default (
<Route <Route
path={URLS.CREATE_USER} path={URLS.CREATE_USER}
element={ element={
<Can I="create" a="User"> <Can I="manage" a="User">
<CreateUser /> <CreateUser />
</Can> </Can>
} }
@@ -40,7 +40,7 @@ export default (
<Route <Route
path={URLS.USER_PATTERN} path={URLS.USER_PATTERN}
element={ element={
<Can I="update" a="User"> <Can I="manage" a="User">
<EditUser /> <EditUser />
</Can> </Can>
} }
@@ -58,7 +58,7 @@ export default (
<Route <Route
path={URLS.CREATE_ROLE} path={URLS.CREATE_ROLE}
element={ element={
<Can I="create" a="Role"> <Can I="manage" a="Role">
<CreateRole /> <CreateRole />
</Can> </Can>
} }
@@ -67,7 +67,7 @@ export default (
<Route <Route
path={URLS.ROLE_PATTERN} path={URLS.ROLE_PATTERN}
element={ element={
<Can I="update" a="Role"> <Can I="manage" a="Role">
<EditRole /> <EditRole />
</Can> </Can>
} }
@@ -76,7 +76,7 @@ export default (
<Route <Route
path={URLS.USER_INTERFACE} path={URLS.USER_INTERFACE}
element={ element={
<Can I="update" a="Config"> <Can I="manage" a="Config">
<UserInterface /> <UserInterface />
</Can> </Can>
} }
@@ -86,8 +86,8 @@ export default (
path={URLS.AUTHENTICATION} path={URLS.AUTHENTICATION}
element={ element={
<Can I="read" a="SamlAuthProvider"> <Can I="read" a="SamlAuthProvider">
<Can I="update" a="SamlAuthProvider"> <Can I="manage" a="SamlAuthProvider">
<Can I="create" a="SamlAuthProvider"> <Can I="manage" a="SamlAuthProvider">
<Authentication /> <Authentication />
</Can> </Can>
</Can> </Can>
@@ -98,7 +98,7 @@ export default (
<Route <Route
path={URLS.ADMIN_APPS} path={URLS.ADMIN_APPS}
element={ element={
<Can I="update" a="App"> <Can I="manage" a="App">
<AdminApplications /> <AdminApplications />
</Can> </Can>
} }
@@ -107,7 +107,7 @@ export default (
<Route <Route
path={`${URLS.ADMIN_APP_PATTERN}/*`} path={`${URLS.ADMIN_APP_PATTERN}/*`}
element={ element={
<Can I="update" a="App"> <Can I="manage" a="App">
<AdminApplication /> <AdminApplication />
</Can> </Can>
} }
@@ -116,7 +116,7 @@ export default (
<Route <Route
path={`${URLS.ADMIN_TEMPLATES}/*`} path={`${URLS.ADMIN_TEMPLATES}/*`}
element={ element={
<Can I="update" a="Config"> <Can I="manage" a="Config">
<AdminTemplates /> <AdminTemplates />
</Can> </Can>
} }
@@ -125,7 +125,7 @@ export default (
<Route <Route
path={`${URLS.ADMIN_CREATE_TEMPLATE_PATTERN}/*`} path={`${URLS.ADMIN_CREATE_TEMPLATE_PATTERN}/*`}
element={ element={
<Can I="update" a="Config"> <Can I="manage" a="Config">
<AdminCreateTemplate /> <AdminCreateTemplate />
</Can> </Can>
} }
@@ -134,7 +134,7 @@ export default (
<Route <Route
path={`${URLS.ADMIN_UPDATE_TEMPLATE_PATTERN}/*`} path={`${URLS.ADMIN_UPDATE_TEMPLATE_PATTERN}/*`}
element={ element={
<Can I="update" a="Config"> <Can I="manage" a="Config">
<AdminUpdateTemplate /> <AdminUpdateTemplate />
</Can> </Can>
} }

View File

@@ -93,15 +93,15 @@ function SettingsLayout() {
const closeDrawer = () => setDrawerOpen(false); const closeDrawer = () => setDrawerOpen(false);
const drawerLinks = createDrawerLinks({ const drawerLinks = createDrawerLinks({
canCreateFlows: currentUserAbility.can('create', 'Flow'), canCreateFlows: currentUserAbility.can('manage', 'Flow'),
canReadUser: currentUserAbility.can('read', 'User'), canReadUser: currentUserAbility.can('read', 'User'),
canReadRole: currentUserAbility.can('read', 'Role'), canReadRole: currentUserAbility.can('read', 'Role'),
canUpdateConfig: currentUserAbility.can('update', 'Config'), canUpdateConfig: currentUserAbility.can('manage', 'Config'),
canManageSamlAuthProvider: canManageSamlAuthProvider: currentUserAbility.can(
currentUserAbility.can('read', 'SamlAuthProvider') && 'manage',
currentUserAbility.can('update', 'SamlAuthProvider') && 'SamlAuthProvider',
currentUserAbility.can('create', 'SamlAuthProvider'), ),
canUpdateApp: currentUserAbility.can('update', 'App'), canUpdateApp: currentUserAbility.can('manage', 'App'),
}); });
const drawerBottomLinks = [ const drawerBottomLinks = [

View File

@@ -41,7 +41,7 @@ function AdminTemplateContextMenu(props) {
hideBackdrop={false} hideBackdrop={false}
anchorEl={anchorEl} anchorEl={anchorEl}
> >
<Can I="delete" a="Flow" passThrough> <Can I="manage" a="Flow" passThrough>
{(allowed) => ( {(allowed) => (
<MenuItem disabled={!allowed} onClick={onTemplateDelete}> <MenuItem disabled={!allowed} onClick={onTemplateDelete}>
{formatMessage('adminTemplateContextMenu.delete')} {formatMessage('adminTemplateContextMenu.delete')}

View File

@@ -51,7 +51,7 @@ function ContextMenu(props) {
)} )}
</Can> </Can>
<Can I="update" a="Connection" passThrough> <Can I="manage" a="Connection" passThrough>
{(allowed) => ( {(allowed) => (
<MenuItem <MenuItem
onClick={createActionHandler({ type: 'test' })} onClick={createActionHandler({ type: 'test' })}
@@ -62,7 +62,7 @@ function ContextMenu(props) {
)} )}
</Can> </Can>
<Can I="create" a="Connection" passThrough> <Can I="manage" a="Connection" passThrough>
{(allowed) => ( {(allowed) => (
<MenuItem <MenuItem
component={Link} component={Link}
@@ -79,7 +79,7 @@ function ContextMenu(props) {
)} )}
</Can> </Can>
<Can I="delete" a="Connection" passThrough> <Can I="manage" a="Connection" passThrough>
{(allowed) => ( {(allowed) => (
<MenuItem <MenuItem
onClick={createActionHandler({ type: 'delete' })} onClick={createActionHandler({ type: 'delete' })}

View File

@@ -20,7 +20,7 @@ function AppConnections(props) {
if (!hasConnections) { if (!hasConnections) {
return ( return (
<Can I="create" a="Connection" passThrough> <Can I="manage" a="Connection" passThrough>
{(allowed) => ( {(allowed) => (
<NoResultFound <NoResultFound
text={formatMessage('app.noConnections')} text={formatMessage('app.noConnections')}

View File

@@ -37,7 +37,7 @@ function AppFlows(props) {
if (!hasFlows) { if (!hasFlows) {
return ( return (
<Can I="create" a="Flow" passThrough> <Can I="manage" a="Flow" passThrough>
{(allowed) => ( {(allowed) => (
<NoResultFound <NoResultFound
text={formatMessage('app.noFlows')} text={formatMessage('app.noFlows')}

View File

@@ -53,7 +53,7 @@ function DeleteRoleButton(props) {
return ( return (
<> <>
<Can I="delete" a="Role" passThrough> <Can I="manage" a="Role" passThrough>
{(allowed) => ( {(allowed) => (
<IconButton <IconButton
disabled={!allowed || disabled} disabled={!allowed || disabled}

View File

@@ -126,7 +126,7 @@ export default function EditorLayout() {
)} )}
</Can> </Can>
<Can I="publish" a="Flow" passThrough> <Can I="manage" a="Flow" passThrough>
{(allowed) => ( {(allowed) => (
<Button <Button
disabled={!allowed || !flow} disabled={!allowed || !flow}

View File

@@ -130,7 +130,7 @@ function ContextMenu(props) {
)} )}
</Can> </Can>
<Can I="create" a="Flow" passThrough> <Can I="manage" a="Flow" passThrough>
{(allowed) => ( {(allowed) => (
<MenuItem disabled={!allowed} onClick={onFlowDuplicate}> <MenuItem disabled={!allowed} onClick={onFlowDuplicate}>
{formatMessage('flow.duplicate')} {formatMessage('flow.duplicate')}
@@ -139,7 +139,7 @@ function ContextMenu(props) {
</Can> </Can>
{isCurrentUserAdmin && ( {isCurrentUserAdmin && (
<Can I="create" a="Flow" passThrough> <Can I="manage" a="Flow" passThrough>
{(allowed) => ( {(allowed) => (
<MenuItem disabled={!allowed} onClick={onCreateTemplate}> <MenuItem disabled={!allowed} onClick={onCreateTemplate}>
{formatMessage('flow.createTemplateFromFlow')} {formatMessage('flow.createTemplateFromFlow')}
@@ -148,7 +148,7 @@ function ContextMenu(props) {
</Can> </Can>
)} )}
<Can I="update" a="Flow" passThrough> <Can I="manage" a="Flow" passThrough>
{(allowed) => ( {(allowed) => (
<MenuItem disabled={!allowed} onClick={onFlowFolderUpdate}> <MenuItem disabled={!allowed} onClick={onFlowFolderUpdate}>
{formatMessage('flow.moveTo')} {formatMessage('flow.moveTo')}
@@ -164,7 +164,7 @@ function ContextMenu(props) {
)} )}
</Can> </Can>
<Can I="delete" a="Flow" passThrough> <Can I="manage" a="Flow" passThrough>
{(allowed) => ( {(allowed) => (
<MenuItem disabled={!allowed} onClick={onFlowDelete}> <MenuItem disabled={!allowed} onClick={onFlowDelete}>
{formatMessage('flow.delete')} {formatMessage('flow.delete')}

View File

@@ -19,7 +19,7 @@ export default function FlowsButtons() {
const theme = useTheme(); const theme = useTheme();
const { data: config } = useAutomatischConfig(); const { data: config } = useAutomatischConfig();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md')); const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'));
const canCreateFlow = currentUserAbility.can('create', 'Flow'); const canCreateFlow = currentUserAbility.can('manage', 'Flow');
const enableTemplates = config?.data.enableTemplates === true; const enableTemplates = config?.data.enableTemplates === true;
const createFlowButtonData = { const createFlowButtonData = {

View File

@@ -1,51 +0,0 @@
import React from 'react';
import { useFormContext } from 'react-hook-form';
import PropTypes from 'prop-types';
import ControlledCheckbox from 'components/ControlledCheckbox';
const ActionField = ({ action, subject, disabled, name, syncIsCreator }) => {
const { formState, resetField } = useFormContext();
const actionDefaultValue =
formState.defaultValues?.[name]?.[subject.key]?.[action.key].value;
const conditionFieldName = `${name}.${subject.key}.${action.key}.conditions.isCreator`;
const conditionFieldTouched =
formState.touchedFields?.[name]?.[subject.key]?.[action.key]?.conditions
?.isCreator === true;
const handleSyncIsCreator = (newValue) => {
if (
syncIsCreator &&
actionDefaultValue === false &&
!conditionFieldTouched
) {
resetField(conditionFieldName, { defaultValue: newValue });
}
};
return (
<ControlledCheckbox
disabled={disabled}
name={`${name}.${subject.key}.${action.key}.value`}
dataTest={`${action.key.toLowerCase()}-checkbox`}
onChange={(e, value) => {
handleSyncIsCreator(value);
}}
/>
);
};
ActionField.propTypes = {
action: PropTypes.shape({
key: PropTypes.string.isRequired,
subjects: PropTypes.arrayOf(PropTypes.string).isRequired,
}),
subject: PropTypes.shape({
key: PropTypes.string.isRequired,
}).isRequired,
disabled: PropTypes.bool,
name: PropTypes.string.isRequired,
syncIsCreator: PropTypes.bool,
};
export default ActionField;

View File

@@ -0,0 +1,63 @@
import React from 'react';
import { useFormContext } from 'react-hook-form';
import PropTypes from 'prop-types';
import ControlledCheckbox from 'components/ControlledCheckbox';
const AllEntitiesPermissions = ({
action,
subject,
disabled,
name,
syncIsCreator,
}) => {
const { getValues, formState, resetField } = useFormContext();
const fieldName = `${name}.${subject.key}.${action.key}.allEntities`;
const defaultValue =
formState.defaultValues?.[name]?.[subject.key]?.[action.key].allEntities;
const ownEntitiesFieldName = `${name}.${subject.key}.${action.key}.ownEntities`;
const ownEntitiesFieldTouched =
formState.touchedFields?.[name]?.[subject.key]?.[action.key]
?.ownEntities === true;
const currentValue = getValues(fieldName);
React.useEffect(() => {
if (currentValue === true) {
resetField(ownEntitiesFieldName, { defaultValue: true });
}
}, [ownEntitiesFieldName, currentValue]);
const handleSyncIsCreator = (newValue) => {
if (syncIsCreator && defaultValue === false && !ownEntitiesFieldTouched) {
resetField(ownEntitiesFieldName, { defaultValue: newValue });
}
if (newValue === true) {
resetField(ownEntitiesFieldName, { defaultValue: true });
}
};
return (
<ControlledCheckbox
disabled={disabled}
name={fieldName}
dataTest={`${action.key.toLowerCase()}-checkbox`}
/>
);
};
AllEntitiesPermissions.propTypes = {
action: PropTypes.shape({
key: PropTypes.string.isRequired,
subjects: PropTypes.arrayOf(PropTypes.string).isRequired,
}),
subject: PropTypes.shape({
key: PropTypes.string.isRequired,
}).isRequired,
disabled: PropTypes.bool,
name: PropTypes.string.isRequired,
syncIsCreator: PropTypes.bool,
};
export default AllEntitiesPermissions;

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { useFormContext } from 'react-hook-form';
import PropTypes from 'prop-types';
import ControlledCheckbox from 'components/ControlledCheckbox';
const OwnEntitiesPermission = ({ action, subject, disabled, name }) => {
const { getValues, resetField } = useFormContext();
const fieldName = `${name}.${subject.key}.${action.key}.ownEntities`;
const allEntitiesFieldName = `${name}.${subject.key}.${action.key}.allEntities`;
const currentValue = getValues(fieldName);
React.useEffect(() => {
if (currentValue === false) {
resetField(allEntitiesFieldName, { defaultValue: false });
}
}, [allEntitiesFieldName, currentValue]);
return (
<ControlledCheckbox
name={fieldName}
disabled={disabled}
dataTest={`isCreator-${action.key.toLowerCase()}-checkbox`}
/>
);
};
OwnEntitiesPermission.propTypes = {
action: PropTypes.shape({
key: PropTypes.string.isRequired,
subjects: PropTypes.arrayOf(PropTypes.string).isRequired,
}),
subject: PropTypes.shape({
key: PropTypes.string.isRequired,
}).isRequired,
disabled: PropTypes.bool,
name: PropTypes.string.isRequired,
};
export default OwnEntitiesPermission;

View File

@@ -1,7 +1,5 @@
import { import {
IconButton,
Skeleton, Skeleton,
Stack,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
@@ -10,7 +8,6 @@ import {
TableRow, TableRow,
Typography, Typography,
} from '@mui/material'; } from '@mui/material';
import SettingsIcon from '@mui/icons-material/Settings';
import ControlledCheckbox from 'components/ControlledCheckbox'; import ControlledCheckbox from 'components/ControlledCheckbox';
@@ -21,7 +18,7 @@ const PermissionCatalogFieldLoader = () => {
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell component="th" /> <TableCell component="th" />
{[...Array(5)].map((row, index) => ( {[...Array(4)].map((row, index) => (
<TableCell key={index} component="th"> <TableCell key={index} component="th">
<Skeleton /> <Skeleton />
</TableCell> </TableCell>
@@ -30,27 +27,26 @@ const PermissionCatalogFieldLoader = () => {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{[...Array(3)].map((row, index) => ( {[...Array(3)].map((row, subjectIndex) => (
<TableRow key={index} sx={{ '&:last-child td': { border: 0 } }}> <TableRow
key={subjectIndex}
sx={{ '&:last-child td': { border: 0 } }}
>
<TableCell scope="row"> <TableCell scope="row">
<Skeleton width={40} /> <Skeleton width={40} />
</TableCell> </TableCell>
{[...Array(5)].map((action, index) => ( {[...Array(4)].map(
<TableCell key={index} align="center"> (action, actionIndex) =>
<Typography variant="subtitle2"> (subjectIndex !== 2 ||
<ControlledCheckbox name="value" disabled /> (actionIndex !== 3 && actionIndex !== 2)) && (
</Typography> <TableCell key={actionIndex} align="center">
</TableCell> <Typography variant="subtitle2">
))} <ControlledCheckbox name="value" disabled />
</Typography>
<TableCell> </TableCell>
<Stack direction="row" gap={1} justifyContent="right"> ),
<IconButton color="info" size="small" disabled> )}
<SettingsIcon />
</IconButton>
</Stack>
</TableCell>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>

View File

@@ -1,8 +1,4 @@
import PropTypes from 'prop-types';
import SettingsIcon from '@mui/icons-material/Settings';
import IconButton from '@mui/material/IconButton';
import Paper from '@mui/material/Paper'; import Paper from '@mui/material/Paper';
import Stack from '@mui/material/Stack';
import Table from '@mui/material/Table'; import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody'; import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell'; import TableCell from '@mui/material/TableCell';
@@ -10,12 +6,14 @@ import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead'; import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow'; import TableRow from '@mui/material/TableRow';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import PropTypes from 'prop-types';
import * as React from 'react'; import * as React from 'react';
import usePermissionCatalog from 'hooks/usePermissionCatalog.ee'; import usePermissionCatalog from 'hooks/usePermissionCatalog.ee';
import PermissionSettings from './PermissionSettings.ee'; import useFormatMessage from 'hooks/useFormatMessage';
import AllEntitiesPermissions from './AllEntitiesPermissions';
import ConditionField from './OwnEntitiesPermission';
import PermissionCatalogFieldLoader from './PermissionCatalogFieldLoader'; import PermissionCatalogFieldLoader from './PermissionCatalogFieldLoader';
import ActionField from './ActionField';
const PermissionCatalogField = ({ const PermissionCatalogField = ({
name = 'permissions', name = 'permissions',
@@ -23,10 +21,10 @@ const PermissionCatalogField = ({
syncIsCreator = false, syncIsCreator = false,
loading = false, loading = false,
}) => { }) => {
const formatMessage = useFormatMessage();
const { data, isLoading: isPermissionCatalogLoading } = const { data, isLoading: isPermissionCatalogLoading } =
usePermissionCatalog(); usePermissionCatalog();
const permissionCatalog = data?.data; const permissionCatalog = data?.data;
const [dialogName, setDialogName] = React.useState();
if (isPermissionCatalogLoading || loading) if (isPermissionCatalogLoading || loading)
return <PermissionCatalogFieldLoader />; return <PermissionCatalogFieldLoader />;
@@ -39,24 +37,44 @@ const PermissionCatalogField = ({
<TableCell component="th" /> <TableCell component="th" />
{permissionCatalog?.actions.map((action) => ( {permissionCatalog?.actions.map((action) => (
<TableCell component="th" key={action.key}> <React.Fragment key={action.key}>
<Typography <TableCell component="th" key={action.key}>
component="div" <Typography
variant="subtitle1" component="div"
align="center" variant="subtitle2"
sx={{ align="center"
color: 'text.secondary', sx={{
fontWeight: 700, color: 'text.secondary',
}} fontWeight: 700,
> }}
{action.label} >
</Typography> {action.label}{' '}
</TableCell> {formatMessage('permissionCatalogField.ownEntitiesLabel')}
))} </Typography>
</TableCell>
<TableCell component="th" /> <TableCell
component="th"
key={`${action.key}-isCreator-condition`}
>
<Typography
component="div"
variant="subtitle2"
align="center"
sx={{
color: 'text.secondary',
fontWeight: 700,
}}
>
{action.label}{' '}
{formatMessage('permissionCatalogField.allEntitiesLabel')}
</Typography>
</TableCell>
</React.Fragment>
))}
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{permissionCatalog?.subjects.map((subject) => ( {permissionCatalog?.subjects.map((subject) => (
<TableRow <TableRow
@@ -71,44 +89,43 @@ const PermissionCatalogField = ({
</TableCell> </TableCell>
{permissionCatalog?.actions.map((action) => ( {permissionCatalog?.actions.map((action) => (
<TableCell key={`${subject.key}.${action.key}`} align="center"> <React.Fragment key={`${subject.key}.${action.key}`}>
<Typography variant="subtitle2" component="div"> <TableCell
{action.subjects.includes(subject.key) && ( key={`${subject.key}.${action.key}-isCreator-condition`}
<ActionField align="center"
action={action}
subject={subject}
disabled={disabled}
name={name}
syncIsCreator={syncIsCreator}
/>
)}
{!action.subjects.includes(subject.key) && '-'}
</Typography>
</TableCell>
))}
<TableCell>
<Stack direction="row" gap={1} justifyContent="right">
<IconButton
color="info"
size="small"
onClick={() => setDialogName(subject.key)}
disabled={disabled}
data-test="permission-settings-button"
> >
<SettingsIcon /> <Typography variant="subtitle2" component="div">
</IconButton> {action.subjects.includes(subject.key) && (
<ConditionField
action={action}
subject={subject}
disabled={disabled}
name={name}
/>
)}
{!action.subjects.includes(subject.key) && '-'}
</Typography>
</TableCell>
<PermissionSettings <TableCell
open={dialogName === subject.key} key={`${subject.key}.${action.key}`}
onClose={() => setDialogName('')} align="center"
fieldPrefix={`${name}.${subject.key}`} >
subject={subject.key} <Typography variant="subtitle2" component="div">
actions={permissionCatalog?.actions} {action.subjects.includes(subject.key) && (
conditions={permissionCatalog?.conditions} <AllEntitiesPermissions
/> action={action}
</Stack> subject={subject}
</TableCell> disabled={disabled}
name={name}
syncIsCreator={syncIsCreator}
/>
)}
{!action.subjects.includes(subject.key) && '-'}
</Typography>
</TableCell>
</React.Fragment>
))}
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>
@@ -116,6 +133,7 @@ const PermissionCatalogField = ({
</TableContainer> </TableContainer>
); );
}; };
PermissionCatalogField.propTypes = { PermissionCatalogField.propTypes = {
name: PropTypes.string, name: PropTypes.string,
disabled: PropTypes.bool, disabled: PropTypes.bool,

View File

@@ -6,10 +6,8 @@ export function getRoleWithComputedPermissions(role) {
[permission.subject]: { [permission.subject]: {
...(computedPermissions[permission.subject] || {}), ...(computedPermissions[permission.subject] || {}),
[permission.action]: { [permission.action]: {
conditions: Object.fromEntries( allEntities: permission.conditions.includes('isCreator') === false,
permission.conditions.map((condition) => [condition, true]), ownEntities: true,
),
value: true,
}, },
}, },
}), }),
@@ -28,15 +26,19 @@ export function getPermissions(computedPermissions) {
(permissions, computedPermissionEntry) => { (permissions, computedPermissionEntry) => {
const [subject, actionsWithConditions] = computedPermissionEntry; const [subject, actionsWithConditions] = computedPermissionEntry;
for (const action in actionsWithConditions) { for (const action in actionsWithConditions) {
const { value: permitted, conditions = {} } = const { ownEntities, allEntities } = actionsWithConditions[action];
actionsWithConditions[action];
if (permitted) { if (ownEntities && !allEntities) {
permissions.push({ permissions.push({
action, action,
subject, subject,
conditions: Object.entries(conditions) conditions: ['isCreator'],
.filter(([, enabled]) => enabled) });
.map(([condition]) => condition), } else if (ownEntities && allEntities) {
permissions.push({
action,
subject,
conditions: [],
}); });
} }
} }
@@ -46,18 +48,9 @@ export function getPermissions(computedPermissions) {
); );
} }
export const getComputedPermissionsDefaultValues = ( export const getComputedPermissionsDefaultValues = (data) => {
data,
conditionsInitialValues,
) => {
if (!data) return {}; if (!data) return {};
const conditions = {};
data.conditions.forEach((condition) => {
conditions[condition.key] =
conditionsInitialValues?.[condition.key] || false;
});
const result = {}; const result = {};
data.subjects.forEach((subject) => { data.subjects.forEach((subject) => {
@@ -69,8 +62,8 @@ export const getComputedPermissionsDefaultValues = (
if (action.subjects.includes(subjectKey)) { if (action.subjects.includes(subjectKey)) {
result[subjectKey][actionKey] = { result[subjectKey][actionKey] = {
value: false, ownEntities: false,
conditions: { ...conditions }, allEntities: false,
}; };
} }
}); });

View File

@@ -403,5 +403,7 @@
"executionFilters.statusFilterSuccessfulOption": "Successful", "executionFilters.statusFilterSuccessfulOption": "Successful",
"executionFilters.statusFilterFailedOption": "Failed", "executionFilters.statusFilterFailedOption": "Failed",
"executionFilters.startDateLabel": "Start Date", "executionFilters.startDateLabel": "Start Date",
"executionFilters.endDateLabel": "End Date" "executionFilters.endDateLabel": "End Date",
"permissionCatalogField.ownEntitiesLabel": "(own entities)",
"permissionCatalogField.allEntitiesLabel": "(all entities)"
} }

View File

@@ -81,7 +81,7 @@ export default function Application() {
'data-test': 'add-connection-button', 'data-test': 'add-connection-button',
to: URLS.APP_ADD_CONNECTION(appKey, false), to: URLS.APP_ADD_CONNECTION(appKey, false),
disabled: disabled:
!currentUserAbility.can('create', 'Connection') || !currentUserAbility.can('manage', 'Connection') ||
appConfig?.data?.useOnlyPredefinedAuthClients === true || appConfig?.data?.useOnlyPredefinedAuthClients === true ||
appConfig?.data?.disabled === true, appConfig?.data?.disabled === true,
}; };
@@ -92,7 +92,7 @@ export default function Application() {
'data-test': 'add-connection-with-auth-client-button', 'data-test': 'add-connection-with-auth-client-button',
to: URLS.APP_ADD_CONNECTION(appKey, true), to: URLS.APP_ADD_CONNECTION(appKey, true),
disabled: disabled:
!currentUserAbility.can('create', 'Connection') || !currentUserAbility.can('manage', 'Connection') ||
appOAuthClients?.data?.length === 0 || appOAuthClients?.data?.length === 0 ||
appConfig?.data?.disabled === true, appConfig?.data?.disabled === true,
}; };
@@ -139,7 +139,7 @@ export default function Application() {
<Route <Route
path={`${URLS.FLOWS}/*`} path={`${URLS.FLOWS}/*`}
element={ element={
<Can I="create" a="Flow" passThrough> <Can I="manage" a="Flow" passThrough>
{(allowed) => ( {(allowed) => (
<ConditionalIconButton <ConditionalIconButton
type="submit" type="submit"
@@ -162,7 +162,7 @@ export default function Application() {
<Route <Route
path={`${URLS.CONNECTIONS}/*`} path={`${URLS.CONNECTIONS}/*`}
element={ element={
<Can I="create" a="Connection" passThrough> <Can I="manage" a="Connection" passThrough>
{(allowed) => ( {(allowed) => (
<SplitButton <SplitButton
disabled={!allowed} disabled={!allowed}
@@ -248,7 +248,7 @@ export default function Application() {
<Route <Route
path="/connections/add" path="/connections/add"
element={ element={
<Can I="create" a="Connection"> <Can I="manage" a="Connection">
<AddAppConnection <AddAppConnection
onClose={goToApplicationPage} onClose={goToApplicationPage}
application={app} application={app}
@@ -260,7 +260,7 @@ export default function Application() {
<Route <Route
path="/connections/:connectionId/reconnect" path="/connections/:connectionId/reconnect"
element={ element={
<Can I="create" a="Connection"> <Can I="manage" a="Connection">
<ReconnectConnection <ReconnectConnection
application={app} application={app}
onClose={goToApplicationPage} onClose={goToApplicationPage}

View File

@@ -53,7 +53,7 @@ export default function Applications() {
alignItems="center" alignItems="center"
order={{ xs: 1, sm: 2 }} order={{ xs: 1, sm: 2 }}
> >
<Can I="create" a="Connection" passThrough> <Can I="manage" a="Connection" passThrough>
{(allowed) => ( {(allowed) => (
<ConditionalIconButton <ConditionalIconButton
type="submit" type="submit"
@@ -84,7 +84,7 @@ export default function Applications() {
)} )}
{!isLoading && !hasApps && ( {!isLoading && !hasApps && (
<Can I="create" a="Connection" passThrough> <Can I="manage" a="Connection" passThrough>
{(allowed) => ( {(allowed) => (
<NoResultFound <NoResultFound
text={formatMessage('apps.noConnections')} text={formatMessage('apps.noConnections')}

View File

@@ -73,9 +73,6 @@ export default function CreateRole() {
description: '', description: '',
computedPermissions: getComputedPermissionsDefaultValues( computedPermissions: getComputedPermissionsDefaultValues(
permissionCatalogData?.data, permissionCatalogData?.data,
{
isCreator: true,
},
), ),
}), }),
[permissionCatalogData], [permissionCatalogData],

View File

@@ -67,7 +67,7 @@ export default function CreateUser() {
const roles = rolesData?.data; const roles = rolesData?.data;
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const currentUserAbility = useCurrentUserAbility(); const currentUserAbility = useCurrentUserAbility();
const canUpdateRole = currentUserAbility.can('update', 'Role'); const canUpdateRole = currentUserAbility.can('manage', 'Role');
const handleUserCreation = async (userData) => { const handleUserCreation = async (userData) => {
try { try {
@@ -125,7 +125,7 @@ export default function CreateUser() {
helperText={errors?.email?.message} helperText={errors?.email?.message}
/> />
<Can I="update" a="Role"> <Can I="manage" a="Role">
<ControlledAutocomplete <ControlledAutocomplete
name="roleId" name="roleId"
fullWidth fullWidth

View File

@@ -78,6 +78,7 @@ export default function EditRole() {
try { try {
setPermissionError(null); setPermissionError(null);
const newPermissions = getPermissions(roleData.computedPermissions); const newPermissions = getPermissions(roleData.computedPermissions);
await updateRole({ await updateRole({
name: roleData.name, name: roleData.name,
description: roleData.description, description: roleData.description,

View File

@@ -73,7 +73,7 @@ export default function EditUser() {
const enqueueSnackbar = useEnqueueSnackbar(); const enqueueSnackbar = useEnqueueSnackbar();
const navigate = useNavigate(); const navigate = useNavigate();
const currentUserAbility = useCurrentUserAbility(); const currentUserAbility = useCurrentUserAbility();
const canUpdateRole = currentUserAbility.can('update', 'Role'); const canUpdateRole = currentUserAbility.can('manage', 'Role');
const handleUserUpdate = async (userDataToUpdate) => { const handleUserUpdate = async (userDataToUpdate) => {
try { try {
@@ -169,7 +169,7 @@ export default function EditUser() {
helperText={errors?.email?.message} helperText={errors?.email?.message}
/> />
<Can I="update" a="Role"> <Can I="manage" a="Role">
<ControlledAutocomplete <ControlledAutocomplete
name="roleId" name="roleId"
fullWidth fullWidth

View File

@@ -168,7 +168,7 @@ export default function Flows() {
{!isLoading && !navigateToLastPage && !hasFlows && ( {!isLoading && !navigateToLastPage && !hasFlows && (
<NoResultFound <NoResultFound
text={formatMessage('flows.noFlows')} text={formatMessage('flows.noFlows')}
{...(currentUserAbility.can('create', 'Flow') && { {...(currentUserAbility.can('manage', 'Flow') && {
to: URLS.CREATE_FLOW, to: URLS.CREATE_FLOW,
})} })}
/> />