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 666025ac..6e78b855 100644
--- a/client/src/campaigns/CUD.js
+++ b/client/src/campaigns/CUD.js
@@ -254,6 +254,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/channels/CUD.js b/client/src/channels/CUD.js
index 6ef6da1c..21fcdf8e 100644
--- a/client/src/channels/CUD.js
+++ b/client/src/channels/CUD.js
@@ -254,6 +254,10 @@ export default class CUD extends Component {
}
componentDidMount() {
+ const t = this.props.t;
+ if (!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..3d3a8ad6 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.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..bc651622 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.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 f7751ebd..511b6fe6 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,
@@ -26,6 +27,13 @@ export default class List extends Component {
tableRestActionDialogInit(this);
}
+ componentDidMount() {
+ const t = this.props.t;
+ if (!mailtrainConfig.globalPermissions.manageLists) {
+ this.navigateToWithFlashMessage('/', 'danger', t('permissionDenied')+': manageLists');
+ }
+ }
+
static propTypes = {
permissions: PropTypes.object
}
@@ -132,6 +140,7 @@ export default class List extends Component {
this.table = node} withHeader dataUrl="rest/lists-table" columns={columns} />
- );
+ )
+
}
-}
\ No newline at end of file
+}
diff --git a/client/src/lists/fields/CUD.js b/client/src/lists/fields/CUD.js
index f99bb0ce..61c5d792 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..1a960844 100644
--- a/client/src/lists/imports/List.js
+++ b/client/src/lists/imports/List.js
@@ -37,6 +37,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 +99,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/lists/subscriptions/CUD.js b/client/src/lists/subscriptions/CUD.js
index 22c2c6f2..df20ed37 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.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..3c154173 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.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
+}
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;
diff --git a/client/src/root.js b/client/src/root.js
index f6e3dfea..b51af63b 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,30 +67,43 @@ 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) {
+
+ const gP = mailtrainConfig.globalPermissions;
+
+ for (const entryKey of topLevelMenuKeys) {
+ const entry = topLevelItems[entryKey.toLowerCase()];
+ const link = entry.link || entry.externalLink;
+
+ if (gP["manage"+entryKey]) {
+ if (link && path.startsWith(link)) {
+ topLevelMenu.push({entry.title} {t('current')});
+ } else {
+ topLevelMenu.push({entry.title});
+ }
+ }
+ }
+
return (
<>
{topLevelMenu}
-
- {mailtrainConfig.globalPermissions.displayManageUsers && {t('users')}}
- {t('namespaces')}
- {mailtrainConfig.globalPermissions.manageSettings && {t('globalSettings')}}
- {t('sendConfigurations')}
- {mailtrainConfig.globalPermissions.manageBlacklist && {t('blacklist')}}
- {t('api')}
-
+ {(gP.manageUsers || gP.manageNamespaces || gP.manageSettings ||
+ gP.manageSendConfigurations || gP.manageBlacklist || gP.manageApi) &&
+
+ {(gP.manageUsers) &&
+ {t('users')}}
+ {(gP.manageNamespaces) &&
+ {t('namespaces')}}
+ {(gP.manageSettings) &&
+ {t('globalSettings')}}
+ {(gP.manageSendConfigurations) &&
+ {t('sendConfigurations')}}
+ {(gP.manageBlacklist) &&
+ {t('blacklist')}}
+ {(gP.manageApi) &&
+ {t('api')}}
+ }
{getLanguageChooser(t)}
@@ -101,6 +114,7 @@ class Root extends Component {
>
);
+
} else {
return (
<>
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/locales/de-DE/common.json b/locales/de-DE/common.json
index e1f0faf2..434cb6c9 100644
--- a/locales/de-DE/common.json
+++ b/locales/de-DE/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": "Zugang verweigert"
}
diff --git a/locales/en-US/common.json b/locales/en-US/common.json
index 25510369..a3ca6924 100644
--- a/locales/en-US/common.json
+++ b/locales/en-US/common.json
@@ -1071,5 +1071,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 341d60e6..e155fdcc 100644
--- a/locales/es-ES/common.json
+++ b/locales/es-ES/common.json
@@ -1095,5 +1095,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": "Permiso denegado"
}
diff --git a/locales/fr-FR/common.json b/locales/fr-FR/common.json
index e25d07b1..3efacd8c 100644
--- a/locales/fr-FR/common.json
+++ b/locales/fr-FR/common.json
@@ -1066,5 +1066,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 d60be0ad..c1ee6bbf 100644
--- a/locales/pt-BR/common.json
+++ b/locales/pt-BR/common.json
@@ -1144,5 +1144,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/config/default.yaml b/server/config/default.yaml
index 14b07bf9..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, displayManageUsers, 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]
+ permissions: [setupAutomation, manageLists, manageChannels, manageTemplates, manageCampaigns, manageReports, manageApi, manageSendConfigurations, manageNamespaces]
ownNamespaceRole: campaignsAdmin
campaignsAdminWithoutNamespace:
name: Campaigns Admin (multiple namespaces)
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/campaigns.js b/server/models/campaigns.js
index d5e11ed0..ffe4351e 100644
--- a/server/models/campaigns.js
+++ b/server/models/campaigns.js
@@ -445,6 +445,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 +482,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 +581,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 +640,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 +865,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 +930,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
diff --git a/server/models/channels.js b/server/models/channels.js
index 75151090..a96ba111 100644
--- a/server/models/channels.js
+++ b/server/models/channels.js
@@ -140,6 +140,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 +170,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 +200,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');
diff --git a/server/models/lists.js b/server/models/lists.js
index ada16cf8..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'] }],
@@ -84,7 +85,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;
}
@@ -153,6 +153,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 +249,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 +276,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');
diff --git a/server/models/namespaces.js b/server/models/namespaces.js
index eb1744d9..d29282d2 100644
--- a/server/models/namespaces.js
+++ b/server/models/namespaces.js
@@ -120,7 +120,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');
const extraKeys = em.get('models.namespaces.extraKeys', []);
@@ -162,6 +161,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 +183,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 +222,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');
diff --git a/server/models/reports.js b/server/models/reports.js
index 90983580..9d31ed74 100644
--- a/server/models/reports.js
+++ b/server/models/reports.js
@@ -64,6 +64,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 +86,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 +122,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();
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();
}
diff --git a/server/models/shares.js b/server/models/shares.js
index ce974cf8..2398a96e 100644
--- a/server/models/shares.js
+++ b/server/models/shares.js
@@ -10,6 +10,7 @@ const log = require('../lib/log');
const {getGlobalNamespaceId} = require('../../shared/namespaces');
const {getAdminId} = require('../../shared/users');
+
// 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
@@ -726,4 +727,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;
diff --git a/server/models/templates.js b/server/models/templates.js
index b6c53174..eaf49587 100644
--- a/server/models/templates.js
+++ b/server/models/templates.js
@@ -70,6 +70,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 +115,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 +145,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');
diff --git a/server/models/users.js b/server/models/users.js
index 6ce7aee2..f1c7cedc 100644
--- a/server/models/users.js
+++ b/server/models/users.js
@@ -165,6 +165,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 +193,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 +242,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();