From ead7bbf9dd89630a887e0d56fed0b59ab4372b95 Mon Sep 17 00:00:00 2001 From: joker-x Date: Thu, 27 Aug 2020 19:48:15 +0200 Subject: [PATCH 01/30] Allow to hide menus with globals permissions. Fix: 940 --- client/src/root.js | 48 ++++++++++++++++++++++-------------- server/config/default.yaml | 4 +-- server/lib/client-helpers.js | 3 ++- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/client/src/root.js b/client/src/root.js index f6e3dfea..87944375 100644 --- a/client/src/root.js +++ b/client/src/root.js @@ -28,10 +28,10 @@ import {getUrl} from "./lib/urls"; import {withComponentMixins} from "./lib/decorator-helpers"; import Update from "./settings/Update"; -const topLevelMenuKeys = ['lists', 'channels', 'templates', 'campaigns']; +const topLevelMenuKeys = ['Lists', 'Channels', 'Templates', 'Campaigns']; if (mailtrainConfig.reportsEnabled) { - topLevelMenuKeys.push('reports'); + topLevelMenuKeys.push('Reports'); } @@ -67,29 +67,38 @@ class Root extends Component { const topLevelMenu = []; - for (const entryKey of topLevelMenuKeys) { - const entry = topLevelItems[entryKey]; - const link = entry.link || entry.externalLink; - - if (link && path.startsWith(link)) { - topLevelMenu.push({entry.title} {t('current')}); - } else { - topLevelMenu.push({entry.title}); - } - } - if (mailtrainConfig.isAuthenticated) { + + for (const entryKey of topLevelMenuKeys) { + const entry = topLevelItems[entryKey.toLowerCase()]; + const link = entry.link || entry.externalLink; + + if (mailtrainConfig.user.admin || mailtrainConfig.globalPermissions["manage"+entryKey]) { + if (link && path.startsWith(link)) { + topLevelMenu.push({entry.title} {t('current')}); + } else { + topLevelMenu.push({entry.title}); + } + } + } + return ( <> ); + } else { return ( <> diff --git a/server/config/default.yaml b/server/config/default.yaml index 14b07bf9..037bf9e3 100644 --- a/server/config/default.yaml +++ b/server/config/default.yaml @@ -277,12 +277,12 @@ defaultRoles: name: Global Master admin: true description: All permissions - permissions: [rebuildPermissions, createJavascriptWithROAccess, displayManageUsers, manageBlacklist, manageSettings, setupAutomation] + permissions: [rebuildPermissions, createJavascriptWithROAccess, manageUsers, manageBlacklist, manageSettings, setupAutomation] rootNamespaceRole: master campaignsAdmin: name: Campaigns Admin description: Under the namespace in which the user is located, the user has all permissions for managing lists, templates and campaigns and the permission to send to send configurations. - permissions: [setupAutomation] + permissions: [setupAutomation, manageLists, manageChannels, manageTemplates, manageCampaigns, manageReports, manageApi, manageSendConfigurations, manageNamespaces] ownNamespaceRole: campaignsAdmin campaignsAdminWithoutNamespace: name: Campaigns Admin (multiple namespaces) diff --git a/server/lib/client-helpers.js b/server/lib/client-helpers.js index 2e61b332..f0e8d57b 100644 --- a/server/lib/client-helpers.js +++ b/server/lib/client-helpers.js @@ -40,7 +40,8 @@ async function getAuthenticatedConfig(context) { user: { id: context.user.id, username: context.user.username, - namespace: context.user.namespace + namespace: context.user.namespace, + admin: (config.roles.global[context.user.role]["admin"] || false) }, globalPermissions, editors: config.editors, From 781f312467b13048900770ceb8cb539d6f889ff4 Mon Sep 17 00:00:00 2001 From: joker-x Date: Thu, 27 Aug 2020 21:08:01 +0200 Subject: [PATCH 02/30] Change permissions names from manageXXX to displayManageXXX, except manageSettings and manageBlacklist --- client/src/root.js | 10 +++++----- server/config/default.yaml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/src/root.js b/client/src/root.js index 87944375..80f8b019 100644 --- a/client/src/root.js +++ b/client/src/root.js @@ -73,7 +73,7 @@ class Root extends Component { const entry = topLevelItems[entryKey.toLowerCase()]; const link = entry.link || entry.externalLink; - if (mailtrainConfig.user.admin || mailtrainConfig.globalPermissions["manage"+entryKey]) { + if (mailtrainConfig.user.admin || mailtrainConfig.globalPermissions["displayManage"+entryKey]) { if (link && path.startsWith(link)) { topLevelMenu.push({entry.title} {t('current')}); } else { @@ -87,17 +87,17 @@ class Root extends Component { diff --git a/server/config/default.yaml b/server/config/default.yaml index 037bf9e3..f72d961c 100644 --- a/server/config/default.yaml +++ b/server/config/default.yaml @@ -277,12 +277,12 @@ defaultRoles: name: Global Master admin: true description: All permissions - permissions: [rebuildPermissions, createJavascriptWithROAccess, manageUsers, manageBlacklist, manageSettings, setupAutomation] + permissions: [rebuildPermissions, createJavascriptWithROAccess, manageBlacklist, manageSettings, setupAutomation] rootNamespaceRole: master campaignsAdmin: name: Campaigns Admin description: Under the namespace in which the user is located, the user has all permissions for managing lists, templates and campaigns and the permission to send to send configurations. - permissions: [setupAutomation, manageLists, manageChannels, manageTemplates, manageCampaigns, manageReports, manageApi, manageSendConfigurations, manageNamespaces] + permissions: [setupAutomation, displayManageLists, displayManageChannels, displayManageTemplates, displayManageCampaigns, displayManageReports, displayManageApi, displayManageSendConfigurations, displayManageNamespaces] ownNamespaceRole: campaignsAdmin campaignsAdminWithoutNamespace: name: Campaigns Admin (multiple namespaces) From 2e07e2442d1ead0c667fffb1bf572a7dac6bf44b Mon Sep 17 00:00:00 2001 From: joker-x Date: Fri, 28 Aug 2020 04:36:56 +0200 Subject: [PATCH 03/30] Hide Administration top level menu if the user do not have any global permission for submenus --- client/src/root.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/client/src/root.js b/client/src/root.js index 80f8b019..81909523 100644 --- a/client/src/root.js +++ b/client/src/root.js @@ -69,11 +69,14 @@ class Root extends Component { if (mailtrainConfig.isAuthenticated) { + const gP = mailtrainConfig.globalPermissions; + const superadmin = mailtrainConfig.user.admin; + for (const entryKey of topLevelMenuKeys) { const entry = topLevelItems[entryKey.toLowerCase()]; const link = entry.link || entry.externalLink; - if (mailtrainConfig.user.admin || mailtrainConfig.globalPermissions["displayManage"+entryKey]) { + if (superadmin || gP["displayManage"+entryKey]) { if (link && path.startsWith(link)) { topLevelMenu.push({entry.title} {t('current')}); } else { @@ -86,20 +89,22 @@ class Root extends Component { <>
    {topLevelMenu} - - {(mailtrainConfig.user.admin || mailtrainConfig.globalPermissions.displayManageUsers) && + {(superadmin || gP.displayManageUsers || gP.displayManageNamespaces || gP.manageSettings || + gP.displayManageSendConfigurations || gP.manageBlacklist || gP.displayManageApi) && + + {(superadmin || gP.displayManageUsers) && {t('users')}} - {(mailtrainConfig.user.admin || mailtrainConfig.globalPermissions.displayManageNamespaces) && + {(superadmin || gP.displayManageNamespaces) && {t('namespaces')}} - {(mailtrainConfig.user.admin || mailtrainConfig.globalPermissions.manageSettings) && + {(superadmin || gP.manageSettings) && {t('globalSettings')}} - {(mailtrainConfig.user.admin || mailtrainConfig.globalPermissions.displayManageSendConfigurations) && + {(superadmin || gP.displayManageSendConfigurations) && {t('sendConfigurations')}} - {(mailtrainConfig.user.admin || mailtrainConfig.globalPermissions.manageBlacklist) && + {(superadmin || gP.manageBlacklist) && {t('blacklist')}} - {(mailtrainConfig.user.admin || mailtrainConfig.globalPermissions.displayManageApi) && + {(superadmin || gP.displayManageApi) && {t('api')}} - + }
    {getLanguageChooser(t)} From c13d4df52137b9428b746ddf42556432e1a0b5c0 Mon Sep 17 00:00:00 2001 From: joker-x Date: Sat, 29 Aug 2020 23:04:42 +0200 Subject: [PATCH 04/30] Revert to mangeEntity --- client/src/root.js | 14 +++++++------- server/config/default.yaml | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/src/root.js b/client/src/root.js index 81909523..e2a5fcd6 100644 --- a/client/src/root.js +++ b/client/src/root.js @@ -76,7 +76,7 @@ class Root extends Component { const entry = topLevelItems[entryKey.toLowerCase()]; const link = entry.link || entry.externalLink; - if (superadmin || gP["displayManage"+entryKey]) { + if (superadmin || gP["manage"+entryKey]) { if (link && path.startsWith(link)) { topLevelMenu.push({entry.title} {t('current')}); } else { @@ -89,20 +89,20 @@ class Root extends Component { <>
      {topLevelMenu} - {(superadmin || gP.displayManageUsers || gP.displayManageNamespaces || gP.manageSettings || - gP.displayManageSendConfigurations || gP.manageBlacklist || gP.displayManageApi) && + {(superadmin || gP.manageUsers || gP.manageNamespaces || gP.manageSettings || + gP.manageSendConfigurations || gP.manageBlacklist || gP.manageApi) && - {(superadmin || gP.displayManageUsers) && + {(superadmin || gP.manageUsers) && {t('users')}} - {(superadmin || gP.displayManageNamespaces) && + {(superadmin || gP.manageNamespaces) && {t('namespaces')}} {(superadmin || gP.manageSettings) && {t('globalSettings')}} - {(superadmin || gP.displayManageSendConfigurations) && + {(superadmin || gP.manageSendConfigurations) && {t('sendConfigurations')}} {(superadmin || gP.manageBlacklist) && {t('blacklist')}} - {(superadmin || gP.displayManageApi) && + {(superadmin || gP.manageApi) && {t('api')}} }
    diff --git a/server/config/default.yaml b/server/config/default.yaml index f72d961c..46f7727d 100644 --- a/server/config/default.yaml +++ b/server/config/default.yaml @@ -277,12 +277,12 @@ defaultRoles: name: Global Master admin: true description: All permissions - permissions: [rebuildPermissions, createJavascriptWithROAccess, manageBlacklist, manageSettings, setupAutomation] + permissions: [rebuildPermissions, createJavascriptWithROAccess, manageBlacklist, manageSettings, manageUsers, manageLists, manageChannels, manageTemplates, manageCampaigns, manageReports, manageApi, manageSendConfigurations, manageNamespaces, setupAutomation] rootNamespaceRole: master campaignsAdmin: name: Campaigns Admin description: Under the namespace in which the user is located, the user has all permissions for managing lists, templates and campaigns and the permission to send to send configurations. - permissions: [setupAutomation, displayManageLists, displayManageChannels, displayManageTemplates, displayManageCampaigns, displayManageReports, displayManageApi, displayManageSendConfigurations, displayManageNamespaces] + permissions: [setupAutomation, manageLists, manageChannels, manageTemplates, manageCampaigns, manageReports, manageApi, manageSendConfigurations, manageNamespaces] ownNamespaceRole: campaignsAdmin campaignsAdminWithoutNamespace: name: Campaigns Admin (multiple namespaces) From f1b45530ed5ff8348a60eba182b66a4d015ba86f Mon Sep 17 00:00:00 2001 From: joker-x Date: Sat, 29 Aug 2020 23:25:01 +0200 Subject: [PATCH 05/30] Enforce manageNamespaces global permission in namespaces model --- server/models/namespaces.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/models/namespaces.js b/server/models/namespaces.js index eb1744d9..e30e7564 100644 --- a/server/models/namespaces.js +++ b/server/models/namespaces.js @@ -13,6 +13,7 @@ const dependencyHelpers = require('../lib/dependency-helpers'); const allowedKeys = new Set(['name', 'description', 'namespace']); async function listTree(context) { + shares.enforceGlobalPermission(context, 'manageNamespaces'); enforce(!context.user.admin, 'listTree is not supposed to be called by assumed admin'); const entityType = entitySettings.getEntityType('namespace'); @@ -110,6 +111,7 @@ function hash(entity) { } async function getById(context, id) { + shares.enforceGlobalPermission(context, 'manageNamespaces'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'namespace', id, 'view'); const entity = await tx('namespaces').where('id', id).first(); @@ -119,6 +121,7 @@ async function getById(context, id) { } async function getChildrenTx(tx, context, id) { + shares.enforceGlobalPermission(context, 'manageNamespaces'); await shares.enforceEntityPermissionTx(tx, context, 'namespace', id, 'view'); const entityType = entitySettings.getEntityType('namespace'); @@ -162,6 +165,7 @@ async function getChildrenTx(tx, context, id) { } async function createTx(tx, context, entity) { + shares.enforceGlobalPermission(context, 'manageNamespaces'); enforce(entity.namespace, 'Parent namespace must be set'); await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createNamespace'); @@ -183,6 +187,7 @@ async function create(context, entity) { async function updateWithConsistencyCheck(context, entity) { enforce(entity.id !== 1 || entity.namespace === null, 'Cannot assign a parent to the root namespace.'); + shares.enforceGlobalPermission(context, 'manageNamespaces'); await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.id, 'edit'); @@ -221,6 +226,7 @@ async function updateWithConsistencyCheck(context, entity) { async function remove(context, id) { enforce(id !== 1, 'Cannot delete the root namespace.'); + shares.enforceGlobalPermission(context, 'manageNamespaces'); await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'namespace', id, 'delete'); From e0edcda3dd69aa06b7aaaab0cf4b5643bbfe5e44 Mon Sep 17 00:00:00 2001 From: joker-x Date: Sat, 29 Aug 2020 23:30:57 +0200 Subject: [PATCH 06/30] Enforce manageReports global permission in reports model --- server/models/reports.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/models/reports.js b/server/models/reports.js index 90983580..be16c99a 100644 --- a/server/models/reports.js +++ b/server/models/reports.js @@ -25,6 +25,7 @@ function hash(entity) { } async function getByIdWithTemplate(context, id, withPermissions = true) { + shares.enforceGlobalPermission(context, 'manageReports'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'report', id, 'view'); @@ -46,6 +47,7 @@ async function getByIdWithTemplate(context, id, withPermissions = true) { } async function listDTAjax(context, params) { + shares.enforceGlobalPermission(context, 'manageReports'); return await dtHelpers.ajaxListWithPermissions( context, [ @@ -64,6 +66,7 @@ async function listDTAjax(context, params) { } async function create(context, entity) { + shares.enforceGlobalPermission(context, 'manageReports'); let id; await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createReport'); @@ -85,6 +88,7 @@ async function create(context, entity) { } async function updateWithConsistencyCheck(context, entity) { + shares.enforceGlobalPermission(context, 'manageReports'); await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'report', entity.id, 'edit'); await shares.enforceEntityPermissionTx(tx, context, 'reportTemplate', entity.report_template, 'execute'); @@ -120,6 +124,7 @@ async function updateWithConsistencyCheck(context, entity) { } async function removeTx(tx, context, id) { + shares.enforceGlobalPermission(context, 'manageReports'); await shares.enforceEntityPermissionTx(tx, context, 'report', id, 'delete'); const report = await tx('reports').where('id', id).first(); From 16665536c014765a19af03eb12d441d8aee2d901 Mon Sep 17 00:00:00 2001 From: joker-x Date: Sat, 29 Aug 2020 23:36:40 +0200 Subject: [PATCH 07/30] Enforce manageChannels global permission in channels model --- server/models/channels.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/models/channels.js b/server/models/channels.js index 75151090..65161782 100644 --- a/server/models/channels.js +++ b/server/models/channels.js @@ -30,6 +30,7 @@ function hash(entity) { } async function listDTAjax(context, params) { + shares.enforceGlobalPermission(context, 'manageChannels'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'channel', requiredOperations: ['view'] }], @@ -44,6 +45,7 @@ async function listDTAjax(context, params) { } async function listWithCreateCampaignPermissionDTAjax(context, params) { + shares.enforceGlobalPermission(context, 'manageChannels'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'channel', requiredOperations: ['createCampaign'] }], @@ -95,6 +97,7 @@ async function _getByTx(tx, context, key, id, withPermissions = true) { } async function getByIdTx(tx, context, id, withPermissions = true) { + shares.enforceGlobalPermission(context, 'manageChannels'); await shares.enforceEntityPermissionTx(tx, context, 'channel', id, 'view'); return await _getByTx(tx, context, 'id', id, withPermissions); @@ -140,6 +143,7 @@ async function _validateAndPreprocess(tx, context, entity, isCreate) { } async function _createTx(tx, context, entity, content) { + shares.enforceGlobalPermission(context, 'manageChannels'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createCampaign'); @@ -169,6 +173,7 @@ async function create(context, entity) { } async function updateWithConsistencyCheck(context, entity) { + shares.enforceGlobalPermission(context, 'manageChannels'); await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'channel', entity.id, 'edit'); @@ -198,6 +203,7 @@ async function updateWithConsistencyCheck(context, entity) { async function remove(context, id) { + shares.enforceGlobalPermission(context, 'manageChannels'); await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'channel', id, 'delete'); From 7aa7b1ac41c2b7c34773b1cd8e6f27eaaea475c4 Mon Sep 17 00:00:00 2001 From: joker-x Date: Sat, 29 Aug 2020 23:46:04 +0200 Subject: [PATCH 08/30] Enforce manageLists global permission in lists model --- server/models/lists.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/models/lists.js b/server/models/lists.js index ada16cf8..67ef8a71 100644 --- a/server/models/lists.js +++ b/server/models/lists.js @@ -27,6 +27,7 @@ function hash(entity) { async function _listDTAjax(context, namespaceId, params) { + shares.enforceGlobalPermission(context, 'manageLists'); const campaignEntityType = entitySettings.getEntityType('campaign'); return await dtHelpers.ajaxListWithPermissions( @@ -68,6 +69,7 @@ async function listByNamespaceDTAjax(context, namespaceId, params) { } async function listWithSegmentByCampaignDTAjax(context, campaignId, params) { + shares.enforceGlobalPermission(context, 'manageLists'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'list', requiredOperations: ['view'] }], @@ -84,6 +86,7 @@ async function listWithSegmentByCampaignDTAjax(context, campaignId, params) { } async function getByIdTx(tx, context, id) { + shares.enforceGlobalPermission(context, 'manageLists'); await shares.enforceEntityPermissionTx(tx, context, 'list', id, 'view'); const entity = await tx('lists').where('id', id).first(); return entity; @@ -97,6 +100,7 @@ async function getById(context, id) { } async function getByIdWithListFields(context, id) { + shares.enforceGlobalPermission(context, 'manageLists'); return await knex.transaction(async tx => { const entity = await getByIdTx(tx, context, id); entity.permissions = await shares.getPermissionsTx(tx, context, 'list', id); @@ -106,6 +110,7 @@ async function getByIdWithListFields(context, id) { } async function getByCidTx(tx, context, cid) { + shares.enforceGlobalPermission(context, 'manageLists'); const entity = await tx('lists').where('cid', cid).first(); if (!entity) { shares.throwPermissionDenied(); @@ -122,6 +127,7 @@ async function getByCid(context, cid) { } async function getByNamespaceIdTx(tx, context, namespaceId) { + shares.enforceGlobalPermission(context, 'manageLists'); // FIXME - this methods is rather suboptimal if there are many lists. It quite needs permission caching in shares.js const rows = await tx('lists').where('namespace', namespaceId); @@ -153,6 +159,7 @@ async function _validateAndPreprocess(tx, entity) { } async function create(context, entity) { + shares.enforceGlobalPermission(context, 'manageLists'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createList'); @@ -248,6 +255,7 @@ async function create(context, entity) { } async function updateWithConsistencyCheck(context, entity) { + shares.enforceGlobalPermission(context, 'manageLists'); await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'list', entity.id, 'edit'); @@ -274,6 +282,7 @@ async function updateWithConsistencyCheck(context, entity) { } async function remove(context, id) { + shares.enforceGlobalPermission(context, 'manageLists'); await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'list', id, 'delete'); From 630ae7290a318f99e7838b344b0868f22419f40c Mon Sep 17 00:00:00 2001 From: joker-x Date: Sat, 29 Aug 2020 23:50:38 +0200 Subject: [PATCH 09/30] Enforce manageTemplates global permission in templates model --- server/models/templates.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/models/templates.js b/server/models/templates.js index b6c53174..363c8811 100644 --- a/server/models/templates.js +++ b/server/models/templates.js @@ -20,6 +20,7 @@ function hash(entity) { } async function getByIdTx(tx, context, id, withPermissions = true) { + shares.enforceGlobalPermission(context, 'manageTemplates'); await shares.enforceEntityPermissionTx(tx, context, 'template', id, 'view'); const entity = await tx('templates').where('id', id).first(); entity.data = JSON.parse(entity.data); @@ -38,6 +39,7 @@ async function getById(context, id, withPermissions = true) { } async function _listDTAjax(context, namespaceId, params) { + shares.enforceGlobalPermission(context, 'manageTemplates'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'template', requiredOperations: ['view'] }], @@ -70,6 +72,7 @@ async function _validateAndPreprocess(tx, entity) { } async function create(context, entity) { + shares.enforceGlobalPermission(context, 'manageTemplates'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createTemplate'); @@ -114,6 +117,7 @@ async function create(context, entity) { } async function updateWithConsistencyCheck(context, entity) { + shares.enforceGlobalPermission(context, 'manageTemplates'); await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'template', entity.id, 'edit'); @@ -143,6 +147,7 @@ async function updateWithConsistencyCheck(context, entity) { } async function remove(context, id) { + shares.enforceGlobalPermission(context, 'manageTemplates'); await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'template', id, 'delete'); From 21976bd8f7df52e8d854bc0d52354a398b6dd2cf Mon Sep 17 00:00:00 2001 From: joker-x Date: Sat, 29 Aug 2020 23:56:40 +0200 Subject: [PATCH 10/30] Enforce manageUsers global permission in users model --- server/models/users.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/models/users.js b/server/models/users.js index 6ce7aee2..45bbfb6c 100644 --- a/server/models/users.js +++ b/server/models/users.js @@ -37,6 +37,7 @@ function hash(entity) { } async function _getByTx(tx, context, key, value, extraColumns = []) { + shares.enforceGlobalPermission(context, 'manageUsers'); const columns = ['id', 'username', 'name', 'email', 'namespace', 'role', ...extraColumns]; const user = await tx('users').select(columns).where(key, value).first(); @@ -109,6 +110,7 @@ async function serverValidate(context, data, isOwnAccount) { } async function listDTAjax(context, params) { + shares.enforceGlobalPermission(context, 'manageUsers'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'namespace', requiredOperations: ['manageUsers'] }], @@ -165,6 +167,7 @@ async function _validateAndPreprocess(tx, entity, isCreate, isOwnAccount) { } async function create(context, user) { + shares.enforceGlobalPermission(context, 'manageUsers'); let id; await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'namespace', user.namespace, 'manageUsers'); @@ -192,6 +195,7 @@ async function create(context, user) { } async function updateWithConsistencyCheck(context, user, isOwnAccount) { + shares.enforceGlobalPermission(context, 'manageUsers'); await knex.transaction(async tx => { const existing = await tx('users').where('id', user.id).first(); if (!existing) { @@ -240,6 +244,7 @@ async function updateWithConsistencyCheck(context, user, isOwnAccount) { async function remove(context, userId) { enforce(userId !== 1, 'Admin cannot be deleted'); enforce(context.user.id !== userId, 'User cannot delete himself/herself'); + shares.enforceGlobalPermission(context, 'manageUsers'); await knex.transaction(async tx => { const existing = await tx('users').where('id', userId).first(); From 96d5fc98c04fb0e48c010bcc64bfe86001c97c4c Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 00:04:31 +0200 Subject: [PATCH 11/30] Enforce manageCampaigns global permission in campaigns model --- server/models/campaigns.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/server/models/campaigns.js b/server/models/campaigns.js index d5e11ed0..86768419 100644 --- a/server/models/campaigns.js +++ b/server/models/campaigns.js @@ -68,6 +68,7 @@ function hash(entity, content) { } async function _listDTAjax(context, namespaceId, channelId, params) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'campaign', requiredOperations: ['view'] }], @@ -102,6 +103,7 @@ async function listByChannelDTAjax(context, channelId, params) { } async function listChildrenDTAjax(context, campaignId, params) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'campaign', requiredOperations: ['view'] }], @@ -115,6 +117,7 @@ async function listChildrenDTAjax(context, campaignId, params) { async function listWithContentDTAjax(context, params) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'campaign', requiredOperations: ['view'] }], @@ -127,6 +130,7 @@ async function listWithContentDTAjax(context, params) { } async function listOthersWhoseListsAreIncludedDTAjax(context, campaignId, listIds, params) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'campaign', requiredOperations: ['view'] }], @@ -140,6 +144,7 @@ async function listOthersWhoseListsAreIncludedDTAjax(context, campaignId, listId } async function listTestUsersDTAjax(context, campaignId, params) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'campaign', campaignId, 'view'); @@ -225,6 +230,7 @@ async function listTestUsersDTAjax(context, campaignId, params) { } async function _listSubscriberResultsDTAjax(context, campaignId, getSubsQrys, columns, params) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'campaign', campaignId, 'view'); @@ -319,6 +325,7 @@ async function listOpensDTAjax(context, campaignId, params) { } async function listLinkClicksDTAjax(context, campaignId, params) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); return await knex.transaction(async (tx) => { await shares.enforceEntityPermissionTx(tx, context, 'campaign', campaignId, 'viewStats'); @@ -353,6 +360,7 @@ async function lockByIdTx(tx, id) { } async function rawGetByTx(tx, key, id) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); const entity = await tx('campaigns').where('campaigns.' + key, id) .leftJoin('campaign_lists', 'campaigns.id', 'campaign_lists.campaign') .groupBy('campaigns.id') @@ -386,6 +394,7 @@ async function rawGetByTx(tx, key, id) { } async function getByIdTx(tx, context, id, withPermissions = true, content = Content.ALL) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'view'); let entity = await rawGetByTx(tx, 'id', id); @@ -445,6 +454,7 @@ async function getByCid(context, cid) { } async function _validateAndPreprocess(tx, context, entity, isCreate, content) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); if (content === Content.ALL || content === Content.WITHOUT_SOURCE_CUSTOM || content === Content.RSS_ENTRY) { await namespaceHelpers.validateEntity(tx, entity); @@ -481,6 +491,7 @@ async function _validateAndPreprocess(tx, context, entity, isCreate, content) { } async function _createTx(tx, context, entity, content) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createCampaign'); @@ -579,6 +590,7 @@ async function createRssTx(tx, context, entity) { } async function _validateChannelMoveTx(tx, context, entity, existing) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); if (existing.channel !== entity.channel) { await shares.enforceEntityPermission(context, 'channel', entity.channel, 'createCampaign'); await shares.enforceEntityPermission(context, 'campaign', entity.id, 'delete'); @@ -637,6 +649,7 @@ async function updateWithConsistencyCheck(context, entity, content) { } async function _removeTx(tx, context, id, existing = null, overrideTypeCheck = false) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'delete'); if (!existing) { @@ -861,6 +874,7 @@ async function prepareCampaignMessages(campaignId) { } async function _changeStatus(context, campaignId, permittedCurrentStates, newState, invalidStateMessage, extraData) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); await knex.transaction(async tx => { // This is quite inefficient because it selects the same row 3 times. However as status is changed // rather infrequently, we keep it this way for simplicity @@ -925,6 +939,7 @@ async function stop(context, campaignId) { } async function reset(context, campaignId) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); await knex.transaction(async tx => { // This is quite inefficient because it selects the same row 3 times. However as RESET is // going to be called rather infrequently, we keep it this way for simplicity @@ -965,6 +980,7 @@ async function disable(context, campaignId) { async function getStatisticsOpened(context, id) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'viewStats'); @@ -979,6 +995,7 @@ async function getStatisticsOpened(context, id) { } async function fetchRssCampaign(context, cid) { + shares.enforceGlobalPermission(context, 'manageCampaigns'); return await knex.transaction(async tx => { const campaign = await tx('campaigns').where('cid', cid).select(['id', 'type']).first(); From 7ed5243a3240d72cb0396708816ef3916d018fc0 Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 00:35:47 +0200 Subject: [PATCH 12/30] "Permission Denied" translated to es,fr,de and pt --- locales/de-DE/common.json | 3 ++- locales/en-US/common.json | 3 ++- locales/es-ES/common.json | 3 ++- locales/fr-FR/common.json | 3 ++- locales/pt-BR/common.json | 3 ++- server/models/shares.js | 6 ++++-- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/locales/de-DE/common.json b/locales/de-DE/common.json index 53d7aac5..f07a6119 100644 --- a/locales/de-DE/common.json +++ b/locales/de-DE/common.json @@ -1064,5 +1064,6 @@ "channelName": "Channel \"{{name}}\"", "cloneCampaign": "Clone Campaign", "next": "Next", - "selectCampaignToBeCloned": "Select campaign to be cloned." + "selectCampaignToBeCloned": "Select campaign to be cloned.", + "permissionDenied": "Zugang verweigert" } diff --git a/locales/en-US/common.json b/locales/en-US/common.json index eb9915ee..e071fe45 100644 --- a/locales/en-US/common.json +++ b/locales/en-US/common.json @@ -1070,5 +1070,6 @@ "channelName": "Channel \"{{name}}\"", "cloneCampaign": "Clone Campaign", "next": "Next", - "selectCampaignToBeCloned": "Select campaign to be cloned." + "selectCampaignToBeCloned": "Select campaign to be cloned.", + "permissionDenied": "Permission Denied" } diff --git a/locales/es-ES/common.json b/locales/es-ES/common.json index a0c072af..f43ba5db 100644 --- a/locales/es-ES/common.json +++ b/locales/es-ES/common.json @@ -1094,5 +1094,6 @@ "selectCampaignToBeCloned": "Elige la campaña que será clonada.", "tagLanguage": "Lenguaje de marcado", "tagLanguageMustBeSelected": "Debes seleccionar un lenguaje de marcado", - "helpText": "Texto de ayuda" + "helpText": "Texto de ayuda", + "permissionDenied": "Permission Denied" } diff --git a/locales/fr-FR/common.json b/locales/fr-FR/common.json index 8c7bff85..eea5ca5b 100644 --- a/locales/fr-FR/common.json +++ b/locales/fr-FR/common.json @@ -1065,5 +1065,6 @@ "channelName": "Channel \"{{name}}\"", "cloneCampaign": "Clone Campaign", "next": "Next", - "selectCampaignToBeCloned": "Select campaign to be cloned." + "selectCampaignToBeCloned": "Select campaign to be cloned.", + "permissionDenied": "Permission refusée" } diff --git a/locales/pt-BR/common.json b/locales/pt-BR/common.json index 19254c1d..42b02594 100644 --- a/locales/pt-BR/common.json +++ b/locales/pt-BR/common.json @@ -1143,5 +1143,6 @@ "channelName": "Channel \"{{name}}\"", "cloneCampaign": "Clone Campaign", "next": "Next", - "selectCampaignToBeCloned": "Select campaign to be cloned." + "selectCampaignToBeCloned": "Select campaign to be cloned.", + "permissionDenied": "Permissão negada" } diff --git a/server/models/shares.js b/server/models/shares.js index ce974cf8..ae248bb7 100644 --- a/server/models/shares.js +++ b/server/models/shares.js @@ -9,6 +9,8 @@ const interoperableErrors = require('../../shared/interoperable-errors'); const log = require('../lib/log'); const {getGlobalNamespaceId} = require('../../shared/namespaces'); const {getAdminId} = require('../../shared/users'); +const { tMark } = require('../lib/translate'); + // TODO: This would really benefit from some permission cache connected to rebuildPermissions // A bit of the problem is that the cache would have to expunged as the result of other processes modifying entites/permissions @@ -449,7 +451,7 @@ async function regenerateRoleNamesTable() { function throwPermissionDenied() { - throw new interoperableErrors.PermissionDeniedError('Permission denied'); + throw new interoperableErrors.PermissionDeniedError(tMark('permissionDenied')); } async function removeDefaultShares(tx, user) { @@ -726,4 +728,4 @@ module.exports.regenerateRoleNamesTable = regenerateRoleNamesTable; module.exports.getGlobalPermissions = getGlobalPermissions; module.exports.getPermissionsTx = getPermissionsTx; module.exports.filterPermissionsByRestrictedAccessHandler = filterPermissionsByRestrictedAccessHandler; -module.exports.isAccessibleByRestrictedAccessHandler = isAccessibleByRestrictedAccessHandler; \ No newline at end of file +module.exports.isAccessibleByRestrictedAccessHandler = isAccessibleByRestrictedAccessHandler; From a5ad6f8b5272b00d161263ea1e9865aeb4776aa9 Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 01:51:31 +0200 Subject: [PATCH 13/30] "Permission Denied" translated to es,fr,de and pt --- locales/es-ES/common.json | 2 +- server/lib/translate.js | 2 ++ server/models/shares.js | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/locales/es-ES/common.json b/locales/es-ES/common.json index f43ba5db..270cecd1 100644 --- a/locales/es-ES/common.json +++ b/locales/es-ES/common.json @@ -1095,5 +1095,5 @@ "tagLanguage": "Lenguaje de marcado", "tagLanguageMustBeSelected": "Debes seleccionar un lenguaje de marcado", "helpText": "Texto de ayuda", - "permissionDenied": "Permission Denied" + "permissionDenied": "Permiso denegado" } diff --git a/server/lib/translate.js b/server/lib/translate.js index ba272355..465cc6b4 100644 --- a/server/lib/translate.js +++ b/server/lib/translate.js @@ -15,6 +15,8 @@ function loadLanguage(longCode) { loadLanguage('en-US'); loadLanguage('es-ES'); loadLanguage('pt-BR'); +loadLanguage('de-DE'); +loadLanguage('fr-FR'); resourcesCommon['fk-FK'] = convertToFake(resourcesCommon['en-US']); const resources = {}; diff --git a/server/models/shares.js b/server/models/shares.js index ae248bb7..746e9c7e 100644 --- a/server/models/shares.js +++ b/server/models/shares.js @@ -9,7 +9,7 @@ const interoperableErrors = require('../../shared/interoperable-errors'); const log = require('../lib/log'); const {getGlobalNamespaceId} = require('../../shared/namespaces'); const {getAdminId} = require('../../shared/users'); -const { tMark } = require('../lib/translate'); +const { tUI } = require('../lib/translate'); // TODO: This would really benefit from some permission cache connected to rebuildPermissions @@ -451,7 +451,7 @@ async function regenerateRoleNamesTable() { function throwPermissionDenied() { - throw new interoperableErrors.PermissionDeniedError(tMark('permissionDenied')); + throw new interoperableErrors.PermissionDeniedError(tUI('permissionDenied', config.defaultLanguage)); } async function removeDefaultShares(tx, user) { From 27874027a927a1e44c71a44be5fb59ee9fdf290d Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 06:29:42 +0200 Subject: [PATCH 14/30] Allow superadmin access to all entities without global permissions --- server/models/shares.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/models/shares.js b/server/models/shares.js index 746e9c7e..8ec70f51 100644 --- a/server/models/shares.js +++ b/server/models/shares.js @@ -515,7 +515,9 @@ function checkGlobalPermission(context, requiredOperations) { } function enforceGlobalPermission(context, requiredOperations) { - if (!checkGlobalPermission(context, requiredOperations)) { + const superadmin = ((context.user && context.user.role && config.roles.global[context.user.role]["admin"]) || false); + + if (!superadmin && !checkGlobalPermission(context, requiredOperations)) { throwPermissionDenied(); } } From a42f629c7e7e3042b4c3c135de54880ea183512c Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 08:10:35 +0200 Subject: [PATCH 15/30] Fix --- server/models/channels.js | 1 - server/models/namespaces.js | 3 --- 2 files changed, 4 deletions(-) diff --git a/server/models/channels.js b/server/models/channels.js index 65161782..5c26f7de 100644 --- a/server/models/channels.js +++ b/server/models/channels.js @@ -45,7 +45,6 @@ async function listDTAjax(context, params) { } async function listWithCreateCampaignPermissionDTAjax(context, params) { - shares.enforceGlobalPermission(context, 'manageChannels'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'channel', requiredOperations: ['createCampaign'] }], diff --git a/server/models/namespaces.js b/server/models/namespaces.js index e30e7564..39ae70ed 100644 --- a/server/models/namespaces.js +++ b/server/models/namespaces.js @@ -13,7 +13,6 @@ const dependencyHelpers = require('../lib/dependency-helpers'); const allowedKeys = new Set(['name', 'description', 'namespace']); async function listTree(context) { - shares.enforceGlobalPermission(context, 'manageNamespaces'); enforce(!context.user.admin, 'listTree is not supposed to be called by assumed admin'); const entityType = entitySettings.getEntityType('namespace'); @@ -111,7 +110,6 @@ function hash(entity) { } async function getById(context, id) { - shares.enforceGlobalPermission(context, 'manageNamespaces'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'namespace', id, 'view'); const entity = await tx('namespaces').where('id', id).first(); @@ -121,7 +119,6 @@ async function getById(context, id) { } async function getChildrenTx(tx, context, id) { - shares.enforceGlobalPermission(context, 'manageNamespaces'); await shares.enforceEntityPermissionTx(tx, context, 'namespace', id, 'view'); const entityType = entitySettings.getEntityType('namespace'); From c601dc9709aeaf5bf71f1c479c03698ecbcbbd0d Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 10:34:50 +0200 Subject: [PATCH 16/30] Fix --- server/models/campaigns.js | 2 -- server/models/lists.js | 4 ---- 2 files changed, 6 deletions(-) diff --git a/server/models/campaigns.js b/server/models/campaigns.js index 86768419..adc43a25 100644 --- a/server/models/campaigns.js +++ b/server/models/campaigns.js @@ -360,7 +360,6 @@ async function lockByIdTx(tx, id) { } async function rawGetByTx(tx, key, id) { - shares.enforceGlobalPermission(context, 'manageCampaigns'); const entity = await tx('campaigns').where('campaigns.' + key, id) .leftJoin('campaign_lists', 'campaigns.id', 'campaign_lists.campaign') .groupBy('campaigns.id') @@ -394,7 +393,6 @@ async function rawGetByTx(tx, key, id) { } async function getByIdTx(tx, context, id, withPermissions = true, content = Content.ALL) { - shares.enforceGlobalPermission(context, 'manageCampaigns'); await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'view'); let entity = await rawGetByTx(tx, 'id', id); diff --git a/server/models/lists.js b/server/models/lists.js index 67ef8a71..851dce32 100644 --- a/server/models/lists.js +++ b/server/models/lists.js @@ -86,7 +86,6 @@ async function listWithSegmentByCampaignDTAjax(context, campaignId, params) { } async function getByIdTx(tx, context, id) { - shares.enforceGlobalPermission(context, 'manageLists'); await shares.enforceEntityPermissionTx(tx, context, 'list', id, 'view'); const entity = await tx('lists').where('id', id).first(); return entity; @@ -100,7 +99,6 @@ async function getById(context, id) { } async function getByIdWithListFields(context, id) { - shares.enforceGlobalPermission(context, 'manageLists'); return await knex.transaction(async tx => { const entity = await getByIdTx(tx, context, id); entity.permissions = await shares.getPermissionsTx(tx, context, 'list', id); @@ -110,7 +108,6 @@ async function getByIdWithListFields(context, id) { } async function getByCidTx(tx, context, cid) { - shares.enforceGlobalPermission(context, 'manageLists'); const entity = await tx('lists').where('cid', cid).first(); if (!entity) { shares.throwPermissionDenied(); @@ -127,7 +124,6 @@ async function getByCid(context, cid) { } async function getByNamespaceIdTx(tx, context, namespaceId) { - shares.enforceGlobalPermission(context, 'manageLists'); // FIXME - this methods is rather suboptimal if there are many lists. It quite needs permission caching in shares.js const rows = await tx('lists').where('namespace', namespaceId); From e2e00034bee4324cb90a4ab4fd54483bca530cb4 Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 10:38:50 +0200 Subject: [PATCH 17/30] Fix --- server/models/lists.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/models/lists.js b/server/models/lists.js index 851dce32..26f6e296 100644 --- a/server/models/lists.js +++ b/server/models/lists.js @@ -86,7 +86,6 @@ async function listWithSegmentByCampaignDTAjax(context, campaignId, params) { } async function getByIdTx(tx, context, id) { - await shares.enforceEntityPermissionTx(tx, context, 'list', id, 'view'); const entity = await tx('lists').where('id', id).first(); return entity; } From e244163bdbc8bac5ba91b561c7047be538f2bcd1 Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 10:42:38 +0200 Subject: [PATCH 18/30] Fix --- server/models/lists.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/models/lists.js b/server/models/lists.js index 26f6e296..8595d8c9 100644 --- a/server/models/lists.js +++ b/server/models/lists.js @@ -69,7 +69,6 @@ async function listByNamespaceDTAjax(context, namespaceId, params) { } async function listWithSegmentByCampaignDTAjax(context, campaignId, params) { - shares.enforceGlobalPermission(context, 'manageLists'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'list', requiredOperations: ['view'] }], From e8bad80e590d83b12bf1da5169ba13c626a04567 Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 12:16:30 +0200 Subject: [PATCH 19/30] Remove enforces for getters --- client/src/lists/List.js | 6 +++++- server/models/campaigns.js | 4 ---- server/models/channels.js | 2 -- server/models/lists.js | 1 - server/models/namespaces.js | 1 - server/models/reports.js | 4 ++-- server/models/users.js | 2 -- 7 files changed, 7 insertions(+), 13 deletions(-) diff --git a/client/src/lists/List.js b/client/src/lists/List.js index f7751ebd..1573a02a 100644 --- a/client/src/lists/List.js +++ b/client/src/lists/List.js @@ -10,6 +10,7 @@ import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRe import {withComponentMixins} from "../lib/decorator-helpers"; import {withForm} from "../lib/form"; import PropTypes from 'prop-types'; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -117,6 +118,7 @@ export default class List extends Component { ]; return ( + {(mailtrainConfig.user.admin || mailtrainConfig.globalPermissions.manageList) ?
    {tableRestActionDialogRender(this)} @@ -132,6 +134,8 @@ export default class List extends Component { this.table = node} withHeader dataUrl="rest/lists-table" columns={columns} /> + : +

    No tienes permisos para manejar listas

    } ); } -} \ No newline at end of file +} diff --git a/server/models/campaigns.js b/server/models/campaigns.js index adc43a25..1141bdbf 100644 --- a/server/models/campaigns.js +++ b/server/models/campaigns.js @@ -230,7 +230,6 @@ async function listTestUsersDTAjax(context, campaignId, params) { } async function _listSubscriberResultsDTAjax(context, campaignId, getSubsQrys, columns, params) { - shares.enforceGlobalPermission(context, 'manageCampaigns'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'campaign', campaignId, 'view'); @@ -325,7 +324,6 @@ async function listOpensDTAjax(context, campaignId, params) { } async function listLinkClicksDTAjax(context, campaignId, params) { - shares.enforceGlobalPermission(context, 'manageCampaigns'); return await knex.transaction(async (tx) => { await shares.enforceEntityPermissionTx(tx, context, 'campaign', campaignId, 'viewStats'); @@ -978,7 +976,6 @@ async function disable(context, campaignId) { async function getStatisticsOpened(context, id) { - shares.enforceGlobalPermission(context, 'manageCampaigns'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'viewStats'); @@ -993,7 +990,6 @@ async function getStatisticsOpened(context, id) { } async function fetchRssCampaign(context, cid) { - shares.enforceGlobalPermission(context, 'manageCampaigns'); return await knex.transaction(async tx => { const campaign = await tx('campaigns').where('cid', cid).select(['id', 'type']).first(); diff --git a/server/models/channels.js b/server/models/channels.js index 5c26f7de..a96ba111 100644 --- a/server/models/channels.js +++ b/server/models/channels.js @@ -30,7 +30,6 @@ function hash(entity) { } async function listDTAjax(context, params) { - shares.enforceGlobalPermission(context, 'manageChannels'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'channel', requiredOperations: ['view'] }], @@ -96,7 +95,6 @@ async function _getByTx(tx, context, key, id, withPermissions = true) { } async function getByIdTx(tx, context, id, withPermissions = true) { - shares.enforceGlobalPermission(context, 'manageChannels'); await shares.enforceEntityPermissionTx(tx, context, 'channel', id, 'view'); return await _getByTx(tx, context, 'id', id, withPermissions); diff --git a/server/models/lists.js b/server/models/lists.js index 8595d8c9..3c43d34b 100644 --- a/server/models/lists.js +++ b/server/models/lists.js @@ -27,7 +27,6 @@ function hash(entity) { async function _listDTAjax(context, namespaceId, params) { - shares.enforceGlobalPermission(context, 'manageLists'); const campaignEntityType = entitySettings.getEntityType('campaign'); return await dtHelpers.ajaxListWithPermissions( diff --git a/server/models/namespaces.js b/server/models/namespaces.js index 39ae70ed..6e961320 100644 --- a/server/models/namespaces.js +++ b/server/models/namespaces.js @@ -119,7 +119,6 @@ async function getById(context, id) { } async function getChildrenTx(tx, context, id) { - await shares.enforceEntityPermissionTx(tx, context, 'namespace', id, 'view'); const entityType = entitySettings.getEntityType('namespace'); diff --git a/server/models/reports.js b/server/models/reports.js index be16c99a..64292c15 100644 --- a/server/models/reports.js +++ b/server/models/reports.js @@ -25,7 +25,6 @@ function hash(entity) { } async function getByIdWithTemplate(context, id, withPermissions = true) { - shares.enforceGlobalPermission(context, 'manageReports'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'report', id, 'view'); @@ -47,7 +46,6 @@ async function getByIdWithTemplate(context, id, withPermissions = true) { } async function listDTAjax(context, params) { - shares.enforceGlobalPermission(context, 'manageReports'); return await dtHelpers.ajaxListWithPermissions( context, [ @@ -142,6 +140,7 @@ async function remove(context, id) { } async function updateFields(id, fields) { + shares.enforceGlobalPermission(context, 'manageReports'); return await knex('reports').where('id', id).update(fields); } @@ -150,6 +149,7 @@ async function listByState(state, limit) { } async function bulkChangeState(oldState, newState) { + shares.enforceGlobalPermission(context, 'manageReports'); return await knex('reports').where('state', oldState).update('state', newState); } diff --git a/server/models/users.js b/server/models/users.js index 45bbfb6c..f1c7cedc 100644 --- a/server/models/users.js +++ b/server/models/users.js @@ -37,7 +37,6 @@ function hash(entity) { } async function _getByTx(tx, context, key, value, extraColumns = []) { - shares.enforceGlobalPermission(context, 'manageUsers'); const columns = ['id', 'username', 'name', 'email', 'namespace', 'role', ...extraColumns]; const user = await tx('users').select(columns).where(key, value).first(); @@ -110,7 +109,6 @@ async function serverValidate(context, data, isOwnAccount) { } async function listDTAjax(context, params) { - shares.enforceGlobalPermission(context, 'manageUsers'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'namespace', requiredOperations: ['manageUsers'] }], From 564c0ce4f9fcdfd6fc77293b5f0b0c797cd20876 Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 16:07:15 +0200 Subject: [PATCH 20/30] Avoid listing without permissions from client side --- client/src/channels/CUD.js | 5 +++++ client/src/channels/List.js | 8 ++++++++ client/src/lists/CUD.js | 5 +++++ client/src/lists/List.js | 13 +++++++++---- client/src/lists/subscriptions/CUD.js | 6 ++++++ client/src/lists/subscriptions/List.js | 7 ++++++- 6 files changed, 39 insertions(+), 5 deletions(-) diff --git a/client/src/channels/CUD.js b/client/src/channels/CUD.js index 6ef6da1c..98eda0e0 100644 --- a/client/src/channels/CUD.js +++ b/client/src/channels/CUD.js @@ -37,6 +37,7 @@ import {getCampaignLabels, ListsSelectorHelper} from "../campaigns/helpers"; import {withComponentMixins} from "../lib/decorator-helpers"; import interoperableErrors from "../../../shared/interoperable-errors"; import {Trans} from "react-i18next"; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -254,6 +255,10 @@ export default class CUD extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.user.admin && !mailtrainConfig.globalPermissions.manageChannels) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageChannels'); + } if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity); diff --git a/client/src/channels/List.js b/client/src/channels/List.js index 1d03efe6..2b5a32be 100644 --- a/client/src/channels/List.js +++ b/client/src/channels/List.js @@ -10,6 +10,7 @@ import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRe import {withComponentMixins} from "../lib/decorator-helpers"; import styles from "./styles.scss"; import PropTypes from 'prop-types'; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -31,6 +32,13 @@ export default class List extends Component { permissions: PropTypes.object } + componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.user.admin && !mailtrainConfig.globalPermissions.manageChannels) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageChannels'); + } + } + render() { const t = this.props.t; diff --git a/client/src/lists/CUD.js b/client/src/lists/CUD.js index 588282fa..e187349f 100644 --- a/client/src/lists/CUD.js +++ b/client/src/lists/CUD.js @@ -27,6 +27,7 @@ import {FieldWizard, UnsubscriptionMode} from '../../../shared/lists'; import styles from "../lib/styles.scss"; import {getMailerTypes} from "../send-configurations/helpers"; import {withComponentMixins} from "../lib/decorator-helpers"; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -73,6 +74,10 @@ export default class CUD extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.user.admin && !mailtrainConfig.globalPermissions.manageLists) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); + } if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity); diff --git a/client/src/lists/List.js b/client/src/lists/List.js index 1573a02a..78fa55e1 100644 --- a/client/src/lists/List.js +++ b/client/src/lists/List.js @@ -27,6 +27,13 @@ export default class List extends Component { tableRestActionDialogInit(this); } + componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.user.admin && !mailtrainConfig.globalPermissions.manageLists) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); + } + } + static propTypes = { permissions: PropTypes.object } @@ -118,7 +125,6 @@ export default class List extends Component { ]; return ( - {(mailtrainConfig.user.admin || mailtrainConfig.globalPermissions.manageList) ?
    {tableRestActionDialogRender(this)} @@ -134,8 +140,7 @@ export default class List extends Component {
    this.table = node} withHeader dataUrl="rest/lists-table" columns={columns} /> - : -

    No tienes permisos para manejar listas

    } - ); + ) + } } diff --git a/client/src/lists/subscriptions/CUD.js b/client/src/lists/subscriptions/CUD.js index 22c2c6f2..56f20ead 100644 --- a/client/src/lists/subscriptions/CUD.js +++ b/client/src/lists/subscriptions/CUD.js @@ -25,6 +25,7 @@ import {getFieldColumn, SubscriptionStatus} from '../../../../shared/lists'; import {getFieldTypes, getSubscriptionStatusLabels} from './helpers'; import moment from 'moment-timezone'; import {withComponentMixins} from "../../lib/decorator-helpers"; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -89,6 +90,11 @@ export default class CUD extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.user.admin && !mailtrainConfig.globalPermissions.manageLists) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); + } + if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity); diff --git a/client/src/lists/subscriptions/List.js b/client/src/lists/subscriptions/List.js index 1938643a..db5d784f 100644 --- a/client/src/lists/subscriptions/List.js +++ b/client/src/lists/subscriptions/List.js @@ -21,6 +21,7 @@ import { } from "../../lib/modals"; import listStyles from "../styles.scss"; import {withComponentMixins} from "../../lib/decorator-helpers"; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -58,6 +59,10 @@ export default class List extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.user.admin && !mailtrainConfig.globalPermissions.manageLists) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); + } this.populateFormValues({ segment: this.props.segmentId || '' }); @@ -188,4 +193,4 @@ export default class List extends Component { ); } -} \ No newline at end of file +} From a5cb0b6dd9d3f7f6614c9644e9cdc155f8ea4159 Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 16:10:09 +0200 Subject: [PATCH 21/30] Fix --- client/src/channels/CUD.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/channels/CUD.js b/client/src/channels/CUD.js index 98eda0e0..3d5fae17 100644 --- a/client/src/channels/CUD.js +++ b/client/src/channels/CUD.js @@ -37,7 +37,6 @@ import {getCampaignLabels, ListsSelectorHelper} from "../campaigns/helpers"; import {withComponentMixins} from "../lib/decorator-helpers"; import interoperableErrors from "../../../shared/interoperable-errors"; import {Trans} from "react-i18next"; -import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, From 136e73e28d53cd649f02273367d929e6cc5591ee Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 17:13:41 +0200 Subject: [PATCH 22/30] Revert --- server/models/campaigns.js | 5 ----- server/models/lists.js | 1 + server/models/shares.js | 3 +-- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/server/models/campaigns.js b/server/models/campaigns.js index 1141bdbf..ffe4351e 100644 --- a/server/models/campaigns.js +++ b/server/models/campaigns.js @@ -68,7 +68,6 @@ function hash(entity, content) { } async function _listDTAjax(context, namespaceId, channelId, params) { - shares.enforceGlobalPermission(context, 'manageCampaigns'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'campaign', requiredOperations: ['view'] }], @@ -103,7 +102,6 @@ async function listByChannelDTAjax(context, channelId, params) { } async function listChildrenDTAjax(context, campaignId, params) { - shares.enforceGlobalPermission(context, 'manageCampaigns'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'campaign', requiredOperations: ['view'] }], @@ -117,7 +115,6 @@ async function listChildrenDTAjax(context, campaignId, params) { async function listWithContentDTAjax(context, params) { - shares.enforceGlobalPermission(context, 'manageCampaigns'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'campaign', requiredOperations: ['view'] }], @@ -130,7 +127,6 @@ async function listWithContentDTAjax(context, params) { } async function listOthersWhoseListsAreIncludedDTAjax(context, campaignId, listIds, params) { - shares.enforceGlobalPermission(context, 'manageCampaigns'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'campaign', requiredOperations: ['view'] }], @@ -144,7 +140,6 @@ async function listOthersWhoseListsAreIncludedDTAjax(context, campaignId, listId } async function listTestUsersDTAjax(context, campaignId, params) { - shares.enforceGlobalPermission(context, 'manageCampaigns'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'campaign', campaignId, 'view'); diff --git a/server/models/lists.js b/server/models/lists.js index 3c43d34b..22236652 100644 --- a/server/models/lists.js +++ b/server/models/lists.js @@ -68,6 +68,7 @@ async function listByNamespaceDTAjax(context, namespaceId, params) { } async function listWithSegmentByCampaignDTAjax(context, campaignId, params) { + await shares.enforceEntityPermissionTx(tx, context, 'list', id, 'view'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'list', requiredOperations: ['view'] }], diff --git a/server/models/shares.js b/server/models/shares.js index 8ec70f51..5be62d47 100644 --- a/server/models/shares.js +++ b/server/models/shares.js @@ -9,7 +9,6 @@ const interoperableErrors = require('../../shared/interoperable-errors'); const log = require('../lib/log'); const {getGlobalNamespaceId} = require('../../shared/namespaces'); const {getAdminId} = require('../../shared/users'); -const { tUI } = require('../lib/translate'); // TODO: This would really benefit from some permission cache connected to rebuildPermissions @@ -451,7 +450,7 @@ async function regenerateRoleNamesTable() { function throwPermissionDenied() { - throw new interoperableErrors.PermissionDeniedError(tUI('permissionDenied', config.defaultLanguage)); + throw new interoperableErrors.PermissionDeniedError('Permission denied'); } async function removeDefaultShares(tx, user) { From 6af4c0db9e1df21cf907fe7390ed10636ac33089 Mon Sep 17 00:00:00 2001 From: joker-x Date: Sun, 30 Aug 2020 17:23:24 +0200 Subject: [PATCH 23/30] Revert --- server/models/templates.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/models/templates.js b/server/models/templates.js index 363c8811..eaf49587 100644 --- a/server/models/templates.js +++ b/server/models/templates.js @@ -20,7 +20,6 @@ function hash(entity) { } async function getByIdTx(tx, context, id, withPermissions = true) { - shares.enforceGlobalPermission(context, 'manageTemplates'); await shares.enforceEntityPermissionTx(tx, context, 'template', id, 'view'); const entity = await tx('templates').where('id', id).first(); entity.data = JSON.parse(entity.data); @@ -39,7 +38,6 @@ async function getById(context, id, withPermissions = true) { } async function _listDTAjax(context, namespaceId, params) { - shares.enforceGlobalPermission(context, 'manageTemplates'); return await dtHelpers.ajaxListWithPermissions( context, [{ entityTypeId: 'template', requiredOperations: ['view'] }], From 14ec6d468aa23faffd919ae10bf745aeb621caee Mon Sep 17 00:00:00 2001 From: joker-x Date: Tue, 1 Sep 2020 14:25:35 +0200 Subject: [PATCH 24/30] Remove admin/superadmin tests --- client/src/channels/CUD.js | 2 +- client/src/channels/List.js | 2 +- client/src/lists/CUD.js | 2 +- client/src/lists/List.js | 2 +- client/src/lists/subscriptions/CUD.js | 2 +- client/src/lists/subscriptions/List.js | 2 +- client/src/root.js | 17 ++++++++--------- server/lib/client-helpers.js | 3 +-- 8 files changed, 15 insertions(+), 17 deletions(-) diff --git a/client/src/channels/CUD.js b/client/src/channels/CUD.js index 3d5fae17..21fcdf8e 100644 --- a/client/src/channels/CUD.js +++ b/client/src/channels/CUD.js @@ -255,7 +255,7 @@ export default class CUD extends Component { componentDidMount() { const t = this.props.t; - if (!mailtrainConfig.user.admin && !mailtrainConfig.globalPermissions.manageChannels) { + if (!mailtrainConfig.globalPermissions.manageChannels) { this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageChannels'); } if (this.props.entity) { diff --git a/client/src/channels/List.js b/client/src/channels/List.js index 2b5a32be..3d3a8ad6 100644 --- a/client/src/channels/List.js +++ b/client/src/channels/List.js @@ -34,7 +34,7 @@ export default class List extends Component { componentDidMount() { const t = this.props.t; - if (!mailtrainConfig.user.admin && !mailtrainConfig.globalPermissions.manageChannels) { + if (!mailtrainConfig.globalPermissions.manageChannels) { this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageChannels'); } } diff --git a/client/src/lists/CUD.js b/client/src/lists/CUD.js index e187349f..bc651622 100644 --- a/client/src/lists/CUD.js +++ b/client/src/lists/CUD.js @@ -75,7 +75,7 @@ export default class CUD extends Component { componentDidMount() { const t = this.props.t; - if (!mailtrainConfig.user.admin && !mailtrainConfig.globalPermissions.manageLists) { + if (!mailtrainConfig.globalPermissions.manageLists) { this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); } if (this.props.entity) { diff --git a/client/src/lists/List.js b/client/src/lists/List.js index 78fa55e1..511b6fe6 100644 --- a/client/src/lists/List.js +++ b/client/src/lists/List.js @@ -29,7 +29,7 @@ export default class List extends Component { componentDidMount() { const t = this.props.t; - if (!mailtrainConfig.user.admin && !mailtrainConfig.globalPermissions.manageLists) { + if (!mailtrainConfig.globalPermissions.manageLists) { this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); } } diff --git a/client/src/lists/subscriptions/CUD.js b/client/src/lists/subscriptions/CUD.js index 56f20ead..df20ed37 100644 --- a/client/src/lists/subscriptions/CUD.js +++ b/client/src/lists/subscriptions/CUD.js @@ -91,7 +91,7 @@ export default class CUD extends Component { componentDidMount() { const t = this.props.t; - if (!mailtrainConfig.user.admin && !mailtrainConfig.globalPermissions.manageLists) { + if (!mailtrainConfig.globalPermissions.manageLists) { this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); } diff --git a/client/src/lists/subscriptions/List.js b/client/src/lists/subscriptions/List.js index db5d784f..3c154173 100644 --- a/client/src/lists/subscriptions/List.js +++ b/client/src/lists/subscriptions/List.js @@ -60,7 +60,7 @@ export default class List extends Component { componentDidMount() { const t = this.props.t; - if (!mailtrainConfig.user.admin && !mailtrainConfig.globalPermissions.manageLists) { + if (!mailtrainConfig.globalPermissions.manageLists) { this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); } this.populateFormValues({ diff --git a/client/src/root.js b/client/src/root.js index e2a5fcd6..b51af63b 100644 --- a/client/src/root.js +++ b/client/src/root.js @@ -70,13 +70,12 @@ class Root extends Component { if (mailtrainConfig.isAuthenticated) { const gP = mailtrainConfig.globalPermissions; - const superadmin = mailtrainConfig.user.admin; for (const entryKey of topLevelMenuKeys) { const entry = topLevelItems[entryKey.toLowerCase()]; const link = entry.link || entry.externalLink; - if (superadmin || gP["manage"+entryKey]) { + if (gP["manage"+entryKey]) { if (link && path.startsWith(link)) { topLevelMenu.push({entry.title} {t('current')}); } else { @@ -89,20 +88,20 @@ class Root extends Component { <>
      {topLevelMenu} - {(superadmin || gP.manageUsers || gP.manageNamespaces || gP.manageSettings || + {(gP.manageUsers || gP.manageNamespaces || gP.manageSettings || gP.manageSendConfigurations || gP.manageBlacklist || gP.manageApi) && - {(superadmin || gP.manageUsers) && + {(gP.manageUsers) && {t('users')}} - {(superadmin || gP.manageNamespaces) && + {(gP.manageNamespaces) && {t('namespaces')}} - {(superadmin || gP.manageSettings) && + {(gP.manageSettings) && {t('globalSettings')}} - {(superadmin || gP.manageSendConfigurations) && + {(gP.manageSendConfigurations) && {t('sendConfigurations')}} - {(superadmin || gP.manageBlacklist) && + {(gP.manageBlacklist) && {t('blacklist')}} - {(superadmin || gP.manageApi) && + {(gP.manageApi) && {t('api')}} }
    diff --git a/server/lib/client-helpers.js b/server/lib/client-helpers.js index f0e8d57b..2e61b332 100644 --- a/server/lib/client-helpers.js +++ b/server/lib/client-helpers.js @@ -40,8 +40,7 @@ async function getAuthenticatedConfig(context) { user: { id: context.user.id, username: context.user.username, - namespace: context.user.namespace, - admin: (config.roles.global[context.user.role]["admin"] || false) + namespace: context.user.namespace }, globalPermissions, editors: config.editors, From 555f7a16f1bea1b41f199635d8172be59befa62a Mon Sep 17 00:00:00 2001 From: joker-x Date: Tue, 1 Sep 2020 14:34:38 +0200 Subject: [PATCH 25/30] Remove admin/superadmin tests --- server/models/shares.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/models/shares.js b/server/models/shares.js index 5be62d47..2398a96e 100644 --- a/server/models/shares.js +++ b/server/models/shares.js @@ -514,9 +514,7 @@ function checkGlobalPermission(context, requiredOperations) { } function enforceGlobalPermission(context, requiredOperations) { - const superadmin = ((context.user && context.user.role && config.roles.global[context.user.role]["admin"]) || false); - - if (!superadmin && !checkGlobalPermission(context, requiredOperations)) { + if (!checkGlobalPermission(context, requiredOperations)) { throwPermissionDenied(); } } From d6308e53d57fecb956ed513ab2f0e015c8e72996 Mon Sep 17 00:00:00 2001 From: joker-x Date: Tue, 1 Sep 2020 18:53:28 +0200 Subject: [PATCH 26/30] Complete client side --- client/src/account/API.js | 5 +++++ client/src/blacklist/List.js | 7 ++++++- client/src/campaigns/CUD.js | 5 +++++ client/src/campaigns/List.js | 8 ++++++++ client/src/lists/fields/CUD.js | 5 +++++ client/src/lists/fields/List.js | 7 ++++++- client/src/lists/imports/CUD.js | 7 ++++++- client/src/lists/imports/List.js | 7 ++++++- client/src/lists/segments/CUD.js | 7 ++++++- client/src/lists/segments/List.js | 7 ++++++- client/src/send-configurations/CUD.js | 4 ++++ client/src/send-configurations/List.js | 10 +++++++++- client/src/settings/Update.js | 7 ++++++- server/models/send-configurations.js | 3 +++ 14 files changed, 81 insertions(+), 8 deletions(-) diff --git a/client/src/account/API.js b/client/src/account/API.js index 42636879..bd97455c 100644 --- a/client/src/account/API.js +++ b/client/src/account/API.js @@ -10,6 +10,7 @@ import {Button} from '../lib/bootstrap-components'; import {getUrl} from "../lib/urls"; import {withComponentMixins} from "../lib/decorator-helpers"; import styles from "./styles.scss" +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -35,6 +36,10 @@ export default class API extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageApi) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageApi'); + } // noinspection JSIgnoredPromiseFromCall this.loadAccessToken(); } diff --git a/client/src/blacklist/List.js b/client/src/blacklist/List.js index 9429b446..24df22c2 100644 --- a/client/src/blacklist/List.js +++ b/client/src/blacklist/List.js @@ -10,6 +10,7 @@ import {Button} from "../lib/bootstrap-components"; import {HTTPMethod} from "../lib/axios"; import {tableAddRestActionButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals"; import {withComponentMixins} from "../lib/decorator-helpers"; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -87,6 +88,10 @@ export default class List extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageBlacklist) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageBlacklist'); + } this.clearFields(); } @@ -139,4 +144,4 @@ export default class List extends Component { ); } -} \ No newline at end of file +} diff --git a/client/src/campaigns/CUD.js b/client/src/campaigns/CUD.js index 6afaf2ca..e39c11c0 100644 --- a/client/src/campaigns/CUD.js +++ b/client/src/campaigns/CUD.js @@ -37,6 +37,7 @@ import {getCampaignLabels, ListsSelectorHelper} from "./helpers"; import {withComponentMixins} from "../lib/decorator-helpers"; import interoperableErrors from "../../../shared/interoperable-errors"; import {Trans} from "react-i18next"; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -254,6 +255,10 @@ export default class CUD extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageCampaigns) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageCampaigns'); + } if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity); diff --git a/client/src/campaigns/List.js b/client/src/campaigns/List.js index e8db5b36..b98dea0f 100644 --- a/client/src/campaigns/List.js +++ b/client/src/campaigns/List.js @@ -13,6 +13,7 @@ import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRe import {withComponentMixins} from "../lib/decorator-helpers"; import styles from "./styles.scss"; import PropTypes from 'prop-types'; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -34,6 +35,13 @@ export default class List extends Component { tableRestActionDialogInit(this); } + componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageCampaigns) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageCampaigns'); + } + } + static propTypes = { permissions: PropTypes.object, channel: PropTypes.object diff --git a/client/src/lists/fields/CUD.js b/client/src/lists/fields/CUD.js index 45864ee7..131aff9d 100644 --- a/client/src/lists/fields/CUD.js +++ b/client/src/lists/fields/CUD.js @@ -32,6 +32,7 @@ import styles from "../../lib/styles.scss"; import 'ace-builds/src-noconflict/mode-json'; import 'ace-builds/src-noconflict/mode-handlebars'; import {withComponentMixins} from "../../lib/decorator-helpers"; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -178,6 +179,10 @@ export default class CUD extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageLists) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); + } if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity); diff --git a/client/src/lists/fields/List.js b/client/src/lists/fields/List.js index 33461a41..0431b669 100644 --- a/client/src/lists/fields/List.js +++ b/client/src/lists/fields/List.js @@ -10,6 +10,7 @@ import {getFieldTypes} from './helpers'; import {Icon} from "../../lib/bootstrap-components"; import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../../lib/modals"; import {withComponentMixins} from "../../lib/decorator-helpers"; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -32,6 +33,10 @@ export default class List extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageLists) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); + } } render() { @@ -77,4 +82,4 @@ export default class List extends Component { ); } -} \ No newline at end of file +} diff --git a/client/src/lists/imports/CUD.js b/client/src/lists/imports/CUD.js index 887df141..96cf548e 100644 --- a/client/src/lists/imports/CUD.js +++ b/client/src/lists/imports/CUD.js @@ -30,6 +30,7 @@ import listStyles from "../styles.scss"; import styles from "../../lib/styles.scss"; import interoperableErrors from "../../../../shared/interoperable-errors"; import {withComponentMixins} from "../../lib/decorator-helpers"; +import mailtrainConfig from 'mailtrainConfig'; function truncate(str, len, ending = '...') { @@ -209,6 +210,10 @@ export default class CUD extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageLists) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); + } if (this.props.entity) { this.initFromEntity(this.props.entity); } else { @@ -469,4 +474,4 @@ export default class CUD extends Component { ); } -} \ No newline at end of file +} diff --git a/client/src/lists/imports/List.js b/client/src/lists/imports/List.js index 7e077748..defac298 100644 --- a/client/src/lists/imports/List.js +++ b/client/src/lists/imports/List.js @@ -13,6 +13,7 @@ import moment from "moment"; import {inProgress} from '../../../../shared/imports'; import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../../lib/modals"; import {withComponentMixins} from "../../lib/decorator-helpers"; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -37,6 +38,10 @@ export default class List extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageLists) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); + } } render() { @@ -95,4 +100,4 @@ export default class List extends Component { ); } -} \ No newline at end of file +} diff --git a/client/src/lists/segments/CUD.js b/client/src/lists/segments/CUD.js index af737d10..18e578f9 100644 --- a/client/src/lists/segments/CUD.js +++ b/client/src/lists/segments/CUD.js @@ -28,6 +28,7 @@ import {getRuleHelpers} from "./helpers"; import RuleSettingsPane from "./RuleSettingsPane"; import {withComponentMixins} from "../../lib/decorator-helpers"; import clone from "clone"; +import mailtrainConfig from 'mailtrainConfig'; // https://stackoverflow.com/a/4819886/1601953 const isTouchDevice = !!('ontouchstart' in window || navigator.maxTouchPoints); @@ -123,6 +124,10 @@ export default class CUD extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageLists) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); + } if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity); @@ -401,4 +406,4 @@ export default class CUD extends Component { ); } -} \ No newline at end of file +} diff --git a/client/src/lists/segments/List.js b/client/src/lists/segments/List.js index b95c134c..e71d7139 100644 --- a/client/src/lists/segments/List.js +++ b/client/src/lists/segments/List.js @@ -9,6 +9,7 @@ import {Table} from '../../lib/table'; import {Icon} from "../../lib/bootstrap-components"; import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../../lib/modals"; import {withComponentMixins} from "../../lib/decorator-helpers"; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -29,6 +30,10 @@ export default class List extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageLists) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists'); + } } render() { @@ -69,4 +74,4 @@ export default class List extends Component { ); } -} \ No newline at end of file +} diff --git a/client/src/send-configurations/CUD.js b/client/src/send-configurations/CUD.js index 4d972a28..3b72843e 100644 --- a/client/src/send-configurations/CUD.js +++ b/client/src/send-configurations/CUD.js @@ -91,6 +91,10 @@ export default class CUD extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageSendConfigurations) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageSendConfigurations'); + } if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity); } else { diff --git a/client/src/send-configurations/List.js b/client/src/send-configurations/List.js index 0238265d..5bd71398 100644 --- a/client/src/send-configurations/List.js +++ b/client/src/send-configurations/List.js @@ -11,6 +11,7 @@ import {getMailerTypes} from './helpers'; import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals"; import {withComponentMixins} from "../lib/decorator-helpers"; import PropTypes from 'prop-types'; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ @@ -33,6 +34,13 @@ export default class List extends Component { permissions: PropTypes.object } + componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageSendConfigurations) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageSendConfigurations'); + } + } + render() { const t = this.props.t; @@ -87,4 +95,4 @@ export default class List extends Component { ); } -} \ No newline at end of file +} diff --git a/client/src/settings/Update.js b/client/src/settings/Update.js index 27a26bd0..16dfbe54 100644 --- a/client/src/settings/Update.js +++ b/client/src/settings/Update.js @@ -19,6 +19,7 @@ import { } from '../lib/form'; import {withErrorHandling} from '../lib/error-handling'; import {withComponentMixins} from "../lib/decorator-helpers"; +import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, @@ -45,6 +46,10 @@ export default class Update extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageSettings) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageSettings'); + } this.getFormValuesFromEntity(this.props.entity); } @@ -102,4 +107,4 @@ export default class Update extends Component { ); } -} \ No newline at end of file +} diff --git a/server/models/send-configurations.js b/server/models/send-configurations.js index 890c0d38..631d605b 100644 --- a/server/models/send-configurations.js +++ b/server/models/send-configurations.js @@ -120,6 +120,7 @@ async function _validateAndPreprocess(tx, entity, isCreate) { async function create(context, entity) { + shares.enforceGlobalPermission(context, 'manageSendConfigurations'); return await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createSendConfiguration'); @@ -138,6 +139,7 @@ async function create(context, entity) { } async function updateWithConsistencyCheck(context, entity) { + shares.enforceGlobalPermission(context, 'manageSendConfigurations'); await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'sendConfiguration', entity.id, 'edit'); @@ -167,6 +169,7 @@ async function updateWithConsistencyCheck(context, entity) { } async function remove(context, id) { + shares.enforceGlobalPermission(context, 'manageSendConfigurations'); if (id === getSystemSendConfigurationId()) { shares.throwPermissionDenied(); } From cdcebaaa025c6222ce599dd12e3304177c67ccca Mon Sep 17 00:00:00 2001 From: joker-x Date: Tue, 1 Sep 2020 19:02:07 +0200 Subject: [PATCH 27/30] Fix double imported --- client/src/lists/imports/List.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/lists/imports/List.js b/client/src/lists/imports/List.js index defac298..1a960844 100644 --- a/client/src/lists/imports/List.js +++ b/client/src/lists/imports/List.js @@ -13,7 +13,6 @@ import moment from "moment"; import {inProgress} from '../../../../shared/imports'; import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../../lib/modals"; import {withComponentMixins} from "../../lib/decorator-helpers"; -import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, From 243daec7a649efb3dc5bde0cdefb0e61a6c90719 Mon Sep 17 00:00:00 2001 From: joker-x Date: Tue, 1 Sep 2020 19:03:29 +0200 Subject: [PATCH 28/30] Fix double imported --- client/src/campaigns/CUD.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/campaigns/CUD.js b/client/src/campaigns/CUD.js index e39c11c0..377772cc 100644 --- a/client/src/campaigns/CUD.js +++ b/client/src/campaigns/CUD.js @@ -37,7 +37,6 @@ import {getCampaignLabels, ListsSelectorHelper} from "./helpers"; import {withComponentMixins} from "../lib/decorator-helpers"; import interoperableErrors from "../../../shared/interoperable-errors"; import {Trans} from "react-i18next"; -import mailtrainConfig from 'mailtrainConfig'; @withComponentMixins([ withTranslation, From d36cc303fbcab1e3163bbfda9e2f87de9dadd47f Mon Sep 17 00:00:00 2001 From: joker-x Date: Tue, 1 Sep 2020 19:14:41 +0200 Subject: [PATCH 29/30] Fix --- server/models/namespaces.js | 2 +- server/models/reports.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/server/models/namespaces.js b/server/models/namespaces.js index 6e961320..d29282d2 100644 --- a/server/models/namespaces.js +++ b/server/models/namespaces.js @@ -119,7 +119,7 @@ async function getById(context, id) { } async function getChildrenTx(tx, context, id) { - + await shares.enforceEntityPermissionTx(tx, context, 'namespace', id, 'view'); const entityType = entitySettings.getEntityType('namespace'); const extraKeys = em.get('models.namespaces.extraKeys', []); diff --git a/server/models/reports.js b/server/models/reports.js index 64292c15..9d31ed74 100644 --- a/server/models/reports.js +++ b/server/models/reports.js @@ -140,7 +140,6 @@ async function remove(context, id) { } async function updateFields(id, fields) { - shares.enforceGlobalPermission(context, 'manageReports'); return await knex('reports').where('id', id).update(fields); } @@ -149,7 +148,6 @@ async function listByState(state, limit) { } async function bulkChangeState(oldState, newState) { - shares.enforceGlobalPermission(context, 'manageReports'); return await knex('reports').where('state', oldState).update('state', newState); } From a180d4961223f5450d5e33253b1f39a390d93b59 Mon Sep 17 00:00:00 2001 From: joker-x Date: Tue, 1 Sep 2020 19:23:48 +0200 Subject: [PATCH 30/30] Add manageNamespaces redirect in client side --- client/src/namespaces/CUD.js | 4 ++++ client/src/namespaces/List.js | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/client/src/namespaces/CUD.js b/client/src/namespaces/CUD.js index b073266e..e9b21216 100644 --- a/client/src/namespaces/CUD.js +++ b/client/src/namespaces/CUD.js @@ -93,6 +93,10 @@ export default class CUD extends Component { } componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageNamespaces) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageNamespaces'); + } if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity); } else { diff --git a/client/src/namespaces/List.js b/client/src/namespaces/List.js index dcb684c8..f791d54f 100644 --- a/client/src/namespaces/List.js +++ b/client/src/namespaces/List.js @@ -29,7 +29,12 @@ export default class List extends Component { static propTypes = { permissions: PropTypes.object } - + componentDidMount() { + const t = this.props.t; + if (!mailtrainConfig.globalPermissions.manageNamespaces) { + this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageNamespaces'); + } + } render() { const t = this.props.t;