From f4c7ca3e7f8da1b79d5c46163012196334b96a12 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Mon, 31 Mar 2025 15:30:08 +0000 Subject: [PATCH 1/9] feat(migrations): introduce manage permissions instead of create, update, delete, publish --- ...0250331115016_simplify_role_permissions.js | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 packages/backend/src/db/migrations/20250331115016_simplify_role_permissions.js diff --git a/packages/backend/src/db/migrations/20250331115016_simplify_role_permissions.js b/packages/backend/src/db/migrations/20250331115016_simplify_role_permissions.js new file mode 100644 index 00000000..a14ba052 --- /dev/null +++ b/packages/backend/src/db/migrations/20250331115016_simplify_role_permissions.js @@ -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; +} From 8b1ed54d5455aca3d7f8ffd99ad6c41c9a99ee54 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Tue, 1 Apr 2025 13:56:28 +0000 Subject: [PATCH 2/9] feat(web): use new manage permission in checks --- .../src/components/AdminSettingsLayout/index.jsx | 14 +++++++------- packages/web/src/components/FlowsButtons/index.jsx | 2 +- packages/web/src/pages/Application/index.jsx | 4 ++-- packages/web/src/pages/CreateUser/index.jsx | 2 +- packages/web/src/pages/EditUser/index.jsx | 2 +- packages/web/src/pages/Flows/index.jsx | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/web/src/components/AdminSettingsLayout/index.jsx b/packages/web/src/components/AdminSettingsLayout/index.jsx index 29351314..e8aad6e1 100644 --- a/packages/web/src/components/AdminSettingsLayout/index.jsx +++ b/packages/web/src/components/AdminSettingsLayout/index.jsx @@ -93,15 +93,15 @@ function SettingsLayout() { const closeDrawer = () => setDrawerOpen(false); const drawerLinks = createDrawerLinks({ - canCreateFlows: currentUserAbility.can('create', 'Flow'), + canCreateFlows: currentUserAbility.can('manage', 'Flow'), canReadUser: currentUserAbility.can('read', 'User'), canReadRole: currentUserAbility.can('read', 'Role'), - canUpdateConfig: currentUserAbility.can('update', 'Config'), - canManageSamlAuthProvider: - currentUserAbility.can('read', 'SamlAuthProvider') && - currentUserAbility.can('update', 'SamlAuthProvider') && - currentUserAbility.can('create', 'SamlAuthProvider'), - canUpdateApp: currentUserAbility.can('update', 'App'), + canUpdateConfig: currentUserAbility.can('manage', 'Config'), + canManageSamlAuthProvider: currentUserAbility.can( + 'manage', + 'SamlAuthProvider', + ), + canUpdateApp: currentUserAbility.can('manage', 'App'), }); const drawerBottomLinks = [ diff --git a/packages/web/src/components/FlowsButtons/index.jsx b/packages/web/src/components/FlowsButtons/index.jsx index 44abf690..b3a6c4f8 100644 --- a/packages/web/src/components/FlowsButtons/index.jsx +++ b/packages/web/src/components/FlowsButtons/index.jsx @@ -19,7 +19,7 @@ export default function FlowsButtons() { const theme = useTheme(); const { data: config } = useAutomatischConfig(); 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 createFlowButtonData = { diff --git a/packages/web/src/pages/Application/index.jsx b/packages/web/src/pages/Application/index.jsx index 0ba6ab62..7ef8683a 100644 --- a/packages/web/src/pages/Application/index.jsx +++ b/packages/web/src/pages/Application/index.jsx @@ -81,7 +81,7 @@ export default function Application() { 'data-test': 'add-connection-button', to: URLS.APP_ADD_CONNECTION(appKey, false), disabled: - !currentUserAbility.can('create', 'Connection') || + !currentUserAbility.can('manage', 'Connection') || appConfig?.data?.useOnlyPredefinedAuthClients === true || appConfig?.data?.disabled === true, }; @@ -92,7 +92,7 @@ export default function Application() { 'data-test': 'add-connection-with-auth-client-button', to: URLS.APP_ADD_CONNECTION(appKey, true), disabled: - !currentUserAbility.can('create', 'Connection') || + !currentUserAbility.can('manage', 'Connection') || appOAuthClients?.data?.length === 0 || appConfig?.data?.disabled === true, }; diff --git a/packages/web/src/pages/CreateUser/index.jsx b/packages/web/src/pages/CreateUser/index.jsx index 5eb04376..b0afe719 100644 --- a/packages/web/src/pages/CreateUser/index.jsx +++ b/packages/web/src/pages/CreateUser/index.jsx @@ -67,7 +67,7 @@ export default function CreateUser() { const roles = rolesData?.data; const queryClient = useQueryClient(); const currentUserAbility = useCurrentUserAbility(); - const canUpdateRole = currentUserAbility.can('update', 'Role'); + const canUpdateRole = currentUserAbility.can('manage', 'Role'); const handleUserCreation = async (userData) => { try { diff --git a/packages/web/src/pages/EditUser/index.jsx b/packages/web/src/pages/EditUser/index.jsx index 28713ece..e2e06db1 100644 --- a/packages/web/src/pages/EditUser/index.jsx +++ b/packages/web/src/pages/EditUser/index.jsx @@ -73,7 +73,7 @@ export default function EditUser() { const enqueueSnackbar = useEnqueueSnackbar(); const navigate = useNavigate(); const currentUserAbility = useCurrentUserAbility(); - const canUpdateRole = currentUserAbility.can('update', 'Role'); + const canUpdateRole = currentUserAbility.can('manage', 'Role'); const handleUserUpdate = async (userDataToUpdate) => { try { diff --git a/packages/web/src/pages/Flows/index.jsx b/packages/web/src/pages/Flows/index.jsx index 2fc5da75..21e03a35 100644 --- a/packages/web/src/pages/Flows/index.jsx +++ b/packages/web/src/pages/Flows/index.jsx @@ -168,7 +168,7 @@ export default function Flows() { {!isLoading && !navigateToLastPage && !hasFlows && ( From ee1b910f3c4aacdfb80de5d9411b6746b187b8dc Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 2 Apr 2025 10:26:28 +0000 Subject: [PATCH 3/9] feat(backend): use manage permissions in checks --- packages/backend/src/helpers/authorization.js | 52 +++++++++---------- .../src/helpers/permission-catalog.ee.js | 48 +++-------------- 2 files changed, 33 insertions(+), 67 deletions(-) diff --git a/packages/backend/src/helpers/authorization.js b/packages/backend/src/helpers/authorization.js index e921cee8..2f616b92 100644 --- a/packages/backend/src/helpers/authorization.js +++ b/packages/backend/src/helpers/authorization.js @@ -22,19 +22,19 @@ const authorizationList = { subject: 'Flow', }, 'POST /api/v1/flows/': { - action: 'create', + action: 'manage', subject: 'Flow', }, 'PATCH /api/v1/flows/:flowId': { - action: 'update', + action: 'manage', subject: 'Flow', }, 'DELETE /api/v1/flows/:flowId': { - action: 'delete', + action: 'manage', subject: 'Flow', }, 'GET /api/v1/templates/': { - action: 'create', + action: 'manage', subject: 'Flow', }, 'GET /api/v1/steps/:stepId/connection': { @@ -42,23 +42,23 @@ const authorizationList = { subject: 'Flow', }, 'PATCH /api/v1/steps/:stepId': { - action: 'update', + action: 'manage', subject: 'Flow', }, 'POST /api/v1/steps/:stepId/test': { - action: 'update', + action: 'manage', subject: 'Flow', }, 'GET /api/v1/steps/:stepId/previous-steps': { - action: 'update', + action: 'manage', subject: 'Flow', }, 'POST /api/v1/steps/:stepId/dynamic-fields': { - action: 'update', + action: 'manage', subject: 'Flow', }, 'POST /api/v1/steps/:stepId/dynamic-data': { - action: 'update', + action: 'manage', subject: 'Flow', }, 'GET /api/v1/connections/:connectionId/flows': { @@ -66,11 +66,11 @@ const authorizationList = { subject: 'Flow', }, 'POST /api/v1/connections/:connectionId/test': { - action: 'update', + action: 'manage', subject: 'Connection', }, 'POST /api/v1/connections/:connectionId/verify': { - action: 'create', + action: 'manage', subject: 'Connection', }, 'GET /api/v1/apps/:appKey/flows': { @@ -94,59 +94,59 @@ const authorizationList = { subject: 'Execution', }, 'DELETE /api/v1/steps/:stepId': { - action: 'update', + action: 'manage', subject: 'Flow', }, 'PATCH /api/v1/connections/:connectionId': { - action: 'update', + action: 'manage', subject: 'Connection', }, 'DELETE /api/v1/connections/:connectionId': { - action: 'delete', + action: 'manage', subject: 'Connection', }, 'POST /api/v1/connections/:connectionId/reset': { - action: 'create', + action: 'manage', subject: 'Connection', }, 'PATCH /api/v1/flows/:flowId/status': { - action: 'publish', + action: 'manage', subject: 'Flow', }, 'POST /api/v1/flows/:flowId/duplicate': { - action: 'create', + action: 'manage', subject: 'Flow', }, 'POST /api/v1/flows/:flowId/export': { - action: 'update', + action: 'manage', subject: 'Flow', }, 'POST /api/v1/flows/import': { - action: 'create', + action: 'manage', subject: 'Flow', }, 'POST /api/v1/flows/:flowId/steps': { - action: 'update', + action: 'manage', subject: 'Flow', }, 'POST /api/v1/apps/:appKey/connections': { - action: 'create', + action: 'manage', subject: 'Connection', }, 'POST /api/v1/connections/:connectionId/auth-url': { - action: 'create', + action: 'manage', subject: 'Connection', }, 'POST /api/v1/folders/': { - action: 'create', + action: 'manage', subject: 'Flow', }, 'PATCH /api/v1/folders/:folderId': { - action: 'create', + action: 'manage', subject: 'Flow', }, 'DELETE /api/v1/folders/:folderId': { - action: 'create', + action: 'manage', subject: 'Flow', }, 'GET /api/v1/folders/': { @@ -154,7 +154,7 @@ const authorizationList = { subject: 'Flow', }, 'PATCH /api/v1/flows/:flowId/folder': { - action: 'update', + action: 'manage', subject: 'Flow', }, 'GET /api/v1/flows/:flowId/folder': { diff --git a/packages/backend/src/helpers/permission-catalog.ee.js b/packages/backend/src/helpers/permission-catalog.ee.js index 1f527d9d..b14ec6c1 100644 --- a/packages/backend/src/helpers/permission-catalog.ee.js +++ b/packages/backend/src/helpers/permission-catalog.ee.js @@ -17,56 +17,22 @@ const permissionCatalog = { conditions: [ { key: 'isCreator', - label: 'Is creator' - } + label: 'Is creator', + }, ], actions: [ { - label: 'Create', - key: 'create', - subjects: [ - Connection.key, - Flow.key, - ] + label: 'Manage', + key: 'manage', + subjects: [Connection.key, Flow.key], }, { label: 'Read', key: 'read', - subjects: [ - Connection.key, - Execution.key, - Flow.key, - ] + subjects: [Connection.key, Execution.key, Flow.key], }, - { - label: 'Update', - key: 'update', - subjects: [ - Connection.key, - Flow.key, - ] - }, - { - label: 'Delete', - key: 'delete', - subjects: [ - Connection.key, - Flow.key, - ] - }, - { - label: 'Publish', - key: 'publish', - subjects: [ - Flow.key, - ] - } ], - subjects: [ - Connection, - Flow, - Execution - ] + subjects: [Connection, Flow, Execution], }; export default permissionCatalog; From 6c0a5aad76ba676415225bd1cfa9605cf07037d2 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 2 Apr 2025 10:27:57 +0000 Subject: [PATCH 4/9] feat(Can): use new manage permission in checks --- packages/web/src/adminSettingsRoutes.jsx | 24 +++++++++---------- .../AdminTemplateContextMenu/index.jsx | 2 +- .../AppConnectionContextMenu/index.jsx | 6 ++--- .../src/components/AppConnections/index.jsx | 2 +- .../web/src/components/AppFlows/index.jsx | 2 +- .../components/DeleteRoleButton/index.ee.jsx | 2 +- .../web/src/components/EditorLayout/index.jsx | 2 +- .../src/components/FlowContextMenu/index.jsx | 8 +++---- packages/web/src/pages/Application/index.jsx | 8 +++---- packages/web/src/pages/Applications/index.jsx | 4 ++-- packages/web/src/pages/CreateUser/index.jsx | 2 +- packages/web/src/pages/EditUser/index.jsx | 2 +- 12 files changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/web/src/adminSettingsRoutes.jsx b/packages/web/src/adminSettingsRoutes.jsx index 560d4d23..408c40bf 100644 --- a/packages/web/src/adminSettingsRoutes.jsx +++ b/packages/web/src/adminSettingsRoutes.jsx @@ -31,7 +31,7 @@ export default ( + } @@ -40,7 +40,7 @@ export default ( + } @@ -58,7 +58,7 @@ export default ( + } @@ -67,7 +67,7 @@ export default ( + } @@ -76,7 +76,7 @@ export default ( + } @@ -86,8 +86,8 @@ export default ( path={URLS.AUTHENTICATION} element={ - - + + @@ -98,7 +98,7 @@ export default ( + } @@ -107,7 +107,7 @@ export default ( + } @@ -116,7 +116,7 @@ export default ( + } @@ -125,7 +125,7 @@ export default ( + } @@ -134,7 +134,7 @@ export default ( + } diff --git a/packages/web/src/components/AdminTemplateContextMenu/index.jsx b/packages/web/src/components/AdminTemplateContextMenu/index.jsx index df63ff7e..9ac9debc 100644 --- a/packages/web/src/components/AdminTemplateContextMenu/index.jsx +++ b/packages/web/src/components/AdminTemplateContextMenu/index.jsx @@ -41,7 +41,7 @@ function AdminTemplateContextMenu(props) { hideBackdrop={false} anchorEl={anchorEl} > - + {(allowed) => ( {formatMessage('adminTemplateContextMenu.delete')} diff --git a/packages/web/src/components/AppConnectionContextMenu/index.jsx b/packages/web/src/components/AppConnectionContextMenu/index.jsx index 8e7eb318..d5050e02 100644 --- a/packages/web/src/components/AppConnectionContextMenu/index.jsx +++ b/packages/web/src/components/AppConnectionContextMenu/index.jsx @@ -51,7 +51,7 @@ function ContextMenu(props) { )} - + {(allowed) => ( - + {(allowed) => ( - + {(allowed) => ( + {(allowed) => ( + {(allowed) => ( - + {(allowed) => ( - + {(allowed) => (