diff --git a/web/client/actions/__tests__/featuregrid-test.js b/web/client/actions/__tests__/featuregrid-test.js
index 110f8ed0d1..3a5b834206 100644
--- a/web/client/actions/__tests__/featuregrid-test.js
+++ b/web/client/actions/__tests__/featuregrid-test.js
@@ -90,7 +90,9 @@ import {
toggleShowAgain,
TOGGLE_SHOW_AGAIN_FLAG,
setSyncTool,
- SET_SYNC_TOOL, setViewportFilter, SET_VIEWPORT_FILTER
+ SET_SYNC_TOOL,
+ setViewportFilter,
+ SET_VIEWPORT_FILTER
} from '../featuregrid';
const idFeature = "2135";
diff --git a/web/client/actions/__tests__/styleeditor-test.js b/web/client/actions/__tests__/styleeditor-test.js
index 599cbb0ac9..2f97dcf750 100644
--- a/web/client/actions/__tests__/styleeditor-test.js
+++ b/web/client/actions/__tests__/styleeditor-test.js
@@ -163,12 +163,15 @@ describe('Test the styleeditor actions', () => {
});
it('initStyleService', () => {
const service = { baseUrl: '/geoserver/' };
- const canEdit = true;
- const retval = initStyleService(service, canEdit);
+ const permissions = {
+ editingAllowedRoles: ["USER"],
+ editingAllowedGroups: ["testGroup"]
+ };
+ const retval = initStyleService(service, permissions);
expect(retval).toExist();
expect(retval.type).toBe(INIT_STYLE_SERVICE);
expect(retval.service).toBe(service);
- expect(retval.canEdit).toBe(canEdit);
+ expect(retval.permissions).toEqual(permissions);
});
it('setEditPermissionStyleEditor', () => {
const canEdit = true;
diff --git a/web/client/actions/styleeditor.js b/web/client/actions/styleeditor.js
index 7332aaac87..d806bd12af 100644
--- a/web/client/actions/styleeditor.js
+++ b/web/client/actions/styleeditor.js
@@ -186,14 +186,14 @@ export function deleteStyle(styleName) {
* Setup the style editor service
* @memberof actions.styleeditor
* @param {object} service style editor service
-* @param {bool} canEdit flag to enable/disable style editor in current session
+* @param {object} permissions editing allowed roles and groups permission object
* @return {object} of type `INIT_STYLE_SERVICE`
*/
-export function initStyleService(service, canEdit) {
+export function initStyleService(service, permissions) {
return {
type: INIT_STYLE_SERVICE,
service,
- canEdit
+ permissions
};
}
/**
diff --git a/web/client/components/data/featuregrid/enhancers/editor.js b/web/client/components/data/featuregrid/enhancers/editor.js
index 8a000023a3..5bb9124bcc 100644
--- a/web/client/components/data/featuregrid/enhancers/editor.js
+++ b/web/client/components/data/featuregrid/enhancers/editor.js
@@ -84,9 +84,10 @@ const featuresToGrid = compose(
props => ({displayFilters: props.enableColumnFilters})
),
withPropsOnChange(
- ["editingAllowedRoles", "virtualScroll"],
+ ["editingAllowedRoles", "editingAllowedGroups", "virtualScroll"],
props => ({
editingAllowedRoles: props.editingAllowedRoles,
+ editingAllowedGroups: props.editingAllowedGroups,
initPlugin: props.initPlugin
})
),
diff --git a/web/client/plugins/FeatureEditor.jsx b/web/client/plugins/FeatureEditor.jsx
index 80b6cf2051..a6eade5f14 100644
--- a/web/client/plugins/FeatureEditor.jsx
+++ b/web/client/plugins/FeatureEditor.jsx
@@ -49,7 +49,11 @@ import {isViewportFilterActive} from "../selectors/featuregrid";
* }]
*}
* ```
- * @prop {object} cfg.editingAllowedRoles array of user roles allowed to enter in edit mode
+ * @prop {string[]} cfg.editingAllowedRoles array of user roles allowed to enter in edit mode.
+ * Support predefined ('ADMIN', 'USER', 'ALL') and custom roles. Default value is ['ADMIN'].
+ * Configuring with ["ALL"] allows all users to have access regardless of user's permission.
+ * @prop {string[]} cfg.editingAllowedGroups array of user groups allowed to enter in edit mode.
+ * When configured, gives the editing permissions to users members of one of the groups listed.
* @prop {boolean} cfg.virtualScroll default true. Activates virtualScroll. When false the grid uses normal pagination
* @prop {number} cfg.maxStoredPages default 5. In virtual Scroll mode determines the size of the loaded pages cache
* @prop {number} cfg.vsOverScan default 20. Number of rows to load above/below the visible slice of the grid
diff --git a/web/client/plugins/StyleEditor.jsx b/web/client/plugins/StyleEditor.jsx
index 97d0f7624b..9fca0c85f0 100644
--- a/web/client/plugins/StyleEditor.jsx
+++ b/web/client/plugins/StyleEditor.jsx
@@ -6,89 +6,104 @@
* LICENSE file in the root directory of this source tree.
*/
-import { isArray, isString } from 'lodash';
+import React, {useEffect} from 'react';
import assign from 'object-assign';
import PropTypes from 'prop-types';
-import React from 'react';
import { connect } from 'react-redux';
import { branch, compose, lifecycle, toClass } from 'recompose';
import { createSelector } from 'reselect';
import { updateSettingsParams } from '../actions/layers';
-import { initStyleService, toggleStyleEditor } from '../actions/styleeditor';
+import { initStyleService, setEditPermissionStyleEditor, toggleStyleEditor } from '../actions/styleeditor';
import HTML from '../components/I18N/HTML';
import BorderLayout from '../components/layout/BorderLayout';
import emptyState from '../components/misc/enhancers/emptyState';
import loadingState from '../components/misc/enhancers/loadingState';
import Loader from '../components/misc/Loader';
-import { userRoleSelector } from '../selectors/security';
import {
canEditStyleSelector,
errorStyleSelector,
- getUpdatedLayer,
loadingStyleSelector,
statusStyleSelector,
styleServiceSelector
} from '../selectors/styleeditor';
-import { isSameOrigin } from '../utils/StyleEditorUtils';
import {
StyleCodeEditor,
StyleSelector,
StyleToolbar
} from './styleeditor/index';
-class StyleEditorPanel extends React.Component {
- static propTypes = {
- layer: PropTypes.object,
- header: PropTypes.node,
- isEditing: PropTypes.bool,
- showToolbar: PropTypes.node.bool,
- onInit: PropTypes.func,
- styleService: PropTypes.object,
- userRole: PropTypes.string,
- editingAllowedRoles: PropTypes.array,
- enableSetDefaultStyle: PropTypes.bool,
- canEdit: PropTypes.bool,
- editorConfig: PropTypes.object
- };
+const StyleEditorPanel = ({
+ header,
+ isEditing,
+ showToolbar,
+ onInit,
+ styleService,
+ editingAllowedRoles,
+ editingAllowedGroups,
+ enableSetDefaultStyle,
+ canEdit,
+ editorConfig,
+ onSetPermission
+}) => {
- static defaultProps = {
- layer: {},
- onInit: () => {},
- editingAllowedRoles: [
- 'ADMIN'
- ],
- editorConfig: {}
- };
+ useEffect(() => {
+ onInit(
+ styleService,
+ {
+ editingAllowedRoles,
+ editingAllowedGroups
+ }
+ );
+ }, []);
- UNSAFE_componentWillMount() {
- const canEdit = !this.props.editingAllowedRoles || (isArray(this.props.editingAllowedRoles) && isString(this.props.userRole)
- && this.props.editingAllowedRoles.indexOf(this.props.userRole) !== -1);
- this.props.onInit(this.props.styleService, canEdit && isSameOrigin(this.props.layer, this.props.styleService));
- }
+ useEffect(() => {
+ onSetPermission(canEdit);
+ }, [canEdit]);
+
+ return (
+
+ {header}
+
+
+
+ : null
+ }
+ footer={}>
+ {isEditing
+ ?
+ : }
+
+ );
+};
+StyleEditorPanel.propTypes = {
+ header: PropTypes.node,
+ isEditing: PropTypes.bool,
+ showToolbar: PropTypes.bool,
+ onInit: PropTypes.func,
+ styleService: PropTypes.object,
+ editingAllowedRoles: PropTypes.array,
+ editingAllowedGroups: PropTypes.array,
+ enableSetDefaultStyle: PropTypes.bool,
+ canEdit: PropTypes.bool,
+ editorConfig: PropTypes.object,
+ onSetPermission: PropTypes.func
+};
+StyleEditorPanel.defaultProps = {
+ layer: {},
+ onInit: () => {},
+ editingAllowedRoles: [
+ 'ADMIN'
+ ],
+ editingAllowedGroups: [],
+ editorConfig: {}
+};
- render() {
- return (
-
- {this.props.header}
-
-
-
- : null
- }
- footer={}>
- {this.props.isEditing
- ?
- : }
-
- );
- }
-}
/**
* StyleEditor plugin.
* - Select styles from available styles of the layer
@@ -101,7 +116,12 @@ class StyleEditorPanel extends React.Component {
* @prop {string} cfg.styleService.baseUrl base url of service eg: '/geoserver/'
* @prop {array} cfg.styleService.availableUrls a list of urls that can access directly to the style service
* @prop {array} cfg.styleService.formats supported formats, could be one of [ 'sld' ] or [ 'sld', 'css' ]
- * @prop {array} cfg.editingAllowedRoles all roles with edit permission eg: [ 'ADMIN' ], if null all roles have edit permission
+ * @prop {string[]} cfg.editingAllowedRoles array of user roles allowed to enter in edit mode.
+ * Support predefined ('ADMIN', 'USER', 'ALL') and custom roles. Default value is ['ADMIN'].
+ * Configuring with ["ALL"] allows all users to have access regardless of user's permission.
+ * However, the outcome can be influenced by the user's permission to access the requested style service.
+ * @prop {string[]} cfg.editingAllowedGroups array of user groups allowed to enter in edit mode.
+ * When configured, gives the editing permissions to users members of one of the groups listed.
* @prop {array} cfg.enableSetDefaultStyle enable set default style functionality
* @prop {object} cfg.editorConfig contains editor configurations
* @prop {object} cfg.editorConfig.classification configuration of the classification symbolizer
@@ -123,7 +143,7 @@ const StyleEditorPlugin = compose(
// in this case 'branch' return always a functional component and PluginUtils expects a class
toClass,
// No rendering if not active
- // eg: now only TOCItemsSettings can active following plugin
+ // eg: now only TOCItemsSettings can activate the following plugin
branch(
({ active } = {}) => !active,
() => () => null
@@ -134,25 +154,22 @@ const StyleEditorPlugin = compose(
[
statusStyleSelector,
loadingStyleSelector,
- getUpdatedLayer,
errorStyleSelector,
- userRoleSelector,
canEditStyleSelector,
styleServiceSelector
],
- (status, loading, layer, error, userRole, canEdit, styleService) => ({
+ (status, loading, error, canEdit, styleService) => ({
isEditing: status === 'edit',
loading,
- layer,
error,
- userRole,
canEdit,
styleService
})
),
{
onInit: initStyleService,
- onUpdateParams: updateSettingsParams
+ onUpdateParams: updateSettingsParams,
+ onSetPermission: setEditPermissionStyleEditor
},
(stateProps, dispatchProps, ownProps) => {
// detect if the static service has been updated with new information in the global state
diff --git a/web/client/plugins/__tests__/StyleEditor-test.jsx b/web/client/plugins/__tests__/StyleEditor-test.jsx
index a6b6ebc542..2353e50f96 100644
--- a/web/client/plugins/__tests__/StyleEditor-test.jsx
+++ b/web/client/plugins/__tests__/StyleEditor-test.jsx
@@ -12,7 +12,11 @@ import ReactDOM from 'react-dom';
import StyleEditorPlugin from '../StyleEditor';
import { getPluginForTest } from './pluginsTestUtils';
import { act } from 'react-dom/test-utils';
-import { INIT_STYLE_SERVICE, TOGGLE_STYLE_EDITOR } from '../../actions/styleeditor';
+import {
+ INIT_STYLE_SERVICE,
+ TOGGLE_STYLE_EDITOR,
+ SET_EDIT_PERMISSION
+} from '../../actions/styleeditor';
describe('StyleEditor Plugin', () => {
beforeEach((done) => {
@@ -41,11 +45,12 @@ describe('StyleEditor Plugin', () => {
styleService={cfgStyleService}
/>, document.getElementById("container"));
});
- expect(actions.length).toBe(2);
+ expect(actions.length).toBe(3);
expect(actions.map(action => action.type))
- .toEqual([ INIT_STYLE_SERVICE, TOGGLE_STYLE_EDITOR ]);
- expect(actions[0].service).toBeTruthy();
- expect(actions[0].service).toEqual({ ...cfgStyleService, isStatic: true });
+ .toEqual([ TOGGLE_STYLE_EDITOR, INIT_STYLE_SERVICE, SET_EDIT_PERMISSION ]);
+ expect(actions[1].service).toBeTruthy();
+ expect(actions[1].service).toEqual({ ...cfgStyleService, isStatic: true });
+ expect(actions[1].permissions.editingAllowedRoles).toEqual(['ADMIN']);
});
it('should use the static service from the state', () => {
const cfgStyleService = {
@@ -70,17 +75,23 @@ describe('StyleEditor Plugin', () => {
service: stateStyleService
}
});
+ const permissions = {
+ "editingAllowedRoles": ["USER"],
+ "editingAllowedGroups": ["temp"]
+ };
act(() => {
ReactDOM.render(, document.getElementById("container"));
});
- expect(actions.length).toBe(2);
+ expect(actions.length).toBe(3);
expect(actions.map(action => action.type))
- .toEqual([ INIT_STYLE_SERVICE, TOGGLE_STYLE_EDITOR ]);
- expect(actions[0].service).toBeTruthy();
- expect(actions[0].service).toEqual(stateStyleService);
+ .toEqual([ TOGGLE_STYLE_EDITOR, INIT_STYLE_SERVICE, SET_EDIT_PERMISSION ]);
+ expect(actions[1].service).toBeTruthy();
+ expect(actions[1].service).toEqual(stateStyleService);
+ expect(actions[1].permissions).toEqual(permissions);
});
it('should use the service from the state', () => {
const styleService = {
@@ -99,10 +110,10 @@ describe('StyleEditor Plugin', () => {
active
/>, document.getElementById("container"));
});
- expect(actions.length).toBe(2);
+ expect(actions.length).toBe(3);
expect(actions.map(action => action.type))
- .toEqual([ INIT_STYLE_SERVICE, TOGGLE_STYLE_EDITOR ]);
- expect(actions[0].service).toBeTruthy();
- expect(actions[0].service).toEqual(styleService);
+ .toEqual([ TOGGLE_STYLE_EDITOR, INIT_STYLE_SERVICE, SET_EDIT_PERMISSION ]);
+ expect(actions[1].service).toBeTruthy();
+ expect(actions[1].service).toEqual(styleService);
});
});
diff --git a/web/client/plugins/featuregrid/FeatureEditor.jsx b/web/client/plugins/featuregrid/FeatureEditor.jsx
index 7e062e9fc7..059c70ed36 100644
--- a/web/client/plugins/featuregrid/FeatureEditor.jsx
+++ b/web/client/plugins/featuregrid/FeatureEditor.jsx
@@ -70,7 +70,11 @@ const Dock = connect(createSelector(
* }]
*}
* ```
- * @prop {object} cfg.editingAllowedRoles array of user roles allowed to enter in edit mode
+ * @prop {string[]} cfg.editingAllowedRoles array of user roles allowed to enter in edit mode.
+ * Support predefined ('ADMIN', 'USER', 'ALL') and custom roles. Default value is ['ADMIN'].
+ * Configuring with ["ALL"] allows all users to have access regardless of user's permission.
+ * @prop {string[]} cfg.editingAllowedGroups array of user groups allowed to enter in edit mode.
+ * When configured, gives the editing permissions to users members of one of the groups listed.
* @prop {boolean} cfg.virtualScroll default true. Activates virtualScroll. When false the grid uses normal pagination
* @prop {number} cfg.maxStoredPages default 5. In virtual Scroll mode determines the size of the loaded pages cache
* @prop {number} cfg.vsOverScan default 20. Number of rows to load above/below the visible slice of the grid
@@ -94,7 +98,7 @@ const Dock = connect(createSelector(
*
* @classdesc
* `FeatureEditor` Plugin, also called *FeatureGrid*, provides functionalities to browse/edit data via WFS. The grid can be configured to use paging or
- *
virtual scroll mechanisms. By default virtual scroll is enabled. When on virtual scroll mode, the maxStoredPages param
+ *
virtual scroll mechanisms. By default, virtual scroll is enabled. When on virtual scroll mode, the maxStoredPages param
* sets the size of loaded pages cache, while vsOverscan and scrollDebounce params determine the behavior of grid scrolling
* and of row loading.
*
Furthermore it can be configured to use custom editor cells for certain layers/columns, specifying the rules to recognize them. If no rule matches, then it will be used the default editor based on the dataType of that column.
@@ -187,10 +191,16 @@ const FeatureDock = (props = {
// const editors = items.filter(({target}) => target === 'editors');
useEffect(() => {
- props.initPlugin({virtualScroll, editingAllowedRoles: props.editingAllowedRoles, maxStoredPages: props.maxStoredPages});
+ props.initPlugin({
+ virtualScroll,
+ editingAllowedRoles: props.editingAllowedRoles,
+ editingAllowedGroups: props.editingAllowedGroups,
+ maxStoredPages: props.maxStoredPages
+ });
}, [
virtualScroll,
(props.editingAllowedRoles ?? []).join(","), // this avoids multiple calls when the array remains the equal
+ (props.editingAllowedGroups ?? []).join(","),
props.maxStoredPages
]);
diff --git a/web/client/reducers/__tests__/featuregrid-test.js b/web/client/reducers/__tests__/featuregrid-test.js
index bd1af232ad..44bc0ba25b 100644
--- a/web/client/reducers/__tests__/featuregrid-test.js
+++ b/web/client/reducers/__tests__/featuregrid-test.js
@@ -114,13 +114,22 @@ describe('Test the featuregrid reducer', () => {
let state2 = featuregrid({showAgain: true}, toggleShowAgain());
expect(state2.showAgain).toBe(false);
});
- it('initPlugin', () => {
+ it('initPlugin with default roles and groups', () => {
+ let state = featuregrid({}, initPlugin({}));
+ expect(state).toExist();
+ expect(state.editingAllowedRoles.length).toBe(1);
+ expect(state.editingAllowedRoles).toEqual(["ADMIN"]);
+ expect(state.editingAllowedGroups).toEqual([]);
+ });
+ it('initPlugin with roles and groups allowed', () => {
const someValue = "someValue";
const editingAllowedRoles = [someValue];
- let state = featuregrid({}, initPlugin({editingAllowedRoles}));
+ const editingAllowedGroups = [someValue];
+ let state = featuregrid({}, initPlugin({editingAllowedRoles, editingAllowedGroups}));
expect(state).toExist();
expect(state.editingAllowedRoles.length).toBe(1);
expect(state.editingAllowedRoles[0]).toBe(someValue);
+ expect(state.editingAllowedGroups[0]).toBe(someValue);
});
it('openFeatureGrid', () => {
let state = featuregrid(undefined, openFeatureGrid());
diff --git a/web/client/reducers/__tests__/styleeditor-test.js b/web/client/reducers/__tests__/styleeditor-test.js
index 7ef85d5003..5809e7952a 100644
--- a/web/client/reducers/__tests__/styleeditor-test.js
+++ b/web/client/reducers/__tests__/styleeditor-test.js
@@ -28,11 +28,14 @@ describe('Test styleeditor reducer', () => {
const service = {
baseUrl: '/geoserver'
};
- const canEdit = true;
- const state = styleeditor({}, initStyleService(service, canEdit));
+ const permissions = {
+ editingAllowedRoles: ['ADMIN'],
+ editingAllowedGroups: ['test']
+ };
+ const state = styleeditor({}, initStyleService(service, permissions));
expect(state).toEqual({
service,
- canEdit: true
+ ...permissions
});
});
it('test setEditPermissionStyleEditor', () => {
diff --git a/web/client/reducers/featuregrid.js b/web/client/reducers/featuregrid.js
index 3fcd17417f..515f9a3ea2 100644
--- a/web/client/reducers/featuregrid.js
+++ b/web/client/reducers/featuregrid.js
@@ -60,6 +60,7 @@ const emptyResultsState = {
advancedFilters: {},
filters: {},
editingAllowedRoles: ["ADMIN"],
+ editingAllowedGroups: [],
enableColumnFilters: true,
showFilteredObject: false,
timeSync: false,
@@ -111,6 +112,7 @@ const applyNewChanges = (features, changedFeatures, updates, updatesGeom) =>
* Manages the state of the featuregrid
* The properties represent the shape of the state
* @prop {string[]} editingAllowedRoles array of user roles allowed to enter in edit mode
+ * @prop {string[]} editingAllowedGroups array of user roles allowed to enter in edit mode, when logged-in user role is not ADMIN
* @prop {boolean} canEdit flag used to enable editing on the feature grid
* @prop {object} filters filters for quick search. `{attribute: "name", value: "filter_value", opeartor: "=", rawValue: "the fitler raw value"}`
* @prop {boolean} enableColumnFilters enables column filter. [configurable]
@@ -155,6 +157,7 @@ function featuregrid(state = emptyResultsState, action) {
return assign({}, state, {
showPopoverSync: getApi().getItem("showPopoverSync") !== null ? getApi().getItem("showPopoverSync") === "true" : true,
editingAllowedRoles: action.options.editingAllowedRoles || state.editingAllowedRoles || ["ADMIN"],
+ editingAllowedGroups: action.options.editingAllowedGroups || state.editingAllowedGroups || [],
virtualScroll: !!action.options.virtualScroll,
maxStoredPages: action.options.maxStoredPages || 5
});
diff --git a/web/client/reducers/styleeditor.js b/web/client/reducers/styleeditor.js
index ff951a52d9..da5b244ef4 100644
--- a/web/client/reducers/styleeditor.js
+++ b/web/client/reducers/styleeditor.js
@@ -27,7 +27,8 @@ function styleeditor(state = {}, action) {
return {
...state,
service: action.service,
- canEdit: action.canEdit
+ editingAllowedRoles: action?.permissions?.editingAllowedRoles || state.editingAllowedRoles,
+ editingAllowedGroups: action?.permissions?.editingAllowedGroups || state.editingAllowedGroups
};
}
case SET_EDIT_PERMISSION: {
diff --git a/web/client/selectors/__tests__/featuregrid-test.js b/web/client/selectors/__tests__/featuregrid-test.js
index 81db96898a..471e6e25cb 100644
--- a/web/client/selectors/__tests__/featuregrid-test.js
+++ b/web/client/selectors/__tests__/featuregrid-test.js
@@ -37,8 +37,13 @@ import {
queryOptionsSelector,
showTimeSync,
timeSyncActive,
- multiSelect, isViewportFilterActive, viewportFilter, isFilterByViewportSupported,
- selectedLayerFieldsSelector
+ multiSelect,
+ isViewportFilterActive,
+ viewportFilter,
+ isFilterByViewportSupported,
+ selectedLayerFieldsSelector,
+ editingAllowedGroupsSelector,
+ isEditingAllowedSelector
} from '../featuregrid';
const idFt1 = "idFt1";
@@ -667,4 +672,124 @@ describe('Test featuregrid selectors', () => {
};
expect(selectedLayerFieldsSelector(state)).toEqual([FIELD]);
});
+ it('editingAllowedGroupsSelector', () => {
+ const editingAllowedGroups = ['test'];
+ expect(editingAllowedGroupsSelector({
+ featuregrid: {
+ editingAllowedGroups
+ }
+ })).toEqual(editingAllowedGroups);
+ });
+ describe('isEditingAllowedSelector', () => {
+ const state = {
+ featuregrid: {
+ canEdit: false,
+ editingAllowedRoles: ['USER'],
+ editingAllowedGroups: ['test']
+ },
+ security: {
+ user: {
+ role: 'USER',
+ groups: {
+ group: {
+ enabled: true,
+ groupName: 'test'
+ }
+ }
+ }
+ }
+ };
+ it('test isEditingAllowedSelector with canEdit', () => {
+ expect(isEditingAllowedSelector({
+ ...state,
+ featuregrid: {
+ canEdit: true
+ }
+ })).toBeTruthy();
+ });
+ it('test isEditingAllowedSelector with ALL role', () => {
+ expect(isEditingAllowedSelector({
+ ...state,
+ featuregrid: {
+ editingAllowedRoles: ["ALL"]
+ }
+ })).toBeTruthy();
+ });
+ it('test isEditingAllowedSelector with defaults', () => {
+ expect(isEditingAllowedSelector({
+ featuregrid: {
+ editingAllowedRoles: ["ADMIN"],
+ editingAllowedGroups: []
+ },
+ security: {
+ user: {
+ role: 'ADMIN',
+ groups: {
+ group: {
+ enabled: true,
+ groupName: 'test'
+ }
+ }
+ }
+ }
+ })).toBeTruthy();
+ });
+ it('test isEditingAllowedSelector with ADMIN user matching allowedGroups', () => {
+ expect(isEditingAllowedSelector({
+ featuregrid: {
+ editingAllowedGroups: ['test']
+ },
+ security: {
+ user: {
+ role: 'ADMIN',
+ groups: {
+ group: {
+ enabled: true,
+ groupName: 'test'
+ }
+ }
+ }
+ }
+ })).toBeTruthy();
+ });
+ it('test isEditingAllowedSelector with non-admin user matching allowed roles', () => {
+ expect(isEditingAllowedSelector({
+ ...state,
+ featuregrid: {
+ editingAllowedRoles: ['USER']
+ }
+ })).toBeTruthy();
+ });
+ it('test isEditingAllowedSelector with non-admin user with non-allowed groups', () => {
+ expect(isEditingAllowedSelector({
+ ...state,
+ featuregrid: {
+ editingAllowedRoles: ['USER1'],
+ editingAllowedGroups: ['some']
+ }
+ })).toBeFalsy();
+ });
+ it('test isEditingAllowedSelector with non-admin user and with default editingAllowedRoles', () => {
+ expect(isEditingAllowedSelector({
+ ...state,
+ featuregrid: {}
+ })).toBeFalsy();
+ });
+ it('test isEditingAllowedSelector with ADMIN user and with default editingAllowedRoles', () => {
+ expect(isEditingAllowedSelector({
+ featuregrid: {},
+ security: {
+ user: {
+ role: 'ADMIN',
+ groups: {
+ group: {
+ enabled: true,
+ groupName: 'test'
+ }
+ }
+ }
+ }
+ })).toBeTruthy();
+ });
+ });
});
diff --git a/web/client/selectors/__tests__/security-test.js b/web/client/selectors/__tests__/security-test.js
index 07919b910e..ec3809ad79 100644
--- a/web/client/selectors/__tests__/security-test.js
+++ b/web/client/selectors/__tests__/security-test.js
@@ -15,7 +15,9 @@ import {
rulesSelector,
securityTokenSelector,
userGroupSecuritySelector,
- userParamsSelector
+ userParamsSelector,
+ userGroupsEnabledSelector,
+ isUserAllowedSelectorCreator
} from '../security';
const id = 1833;
@@ -95,4 +97,85 @@ describe('Test security selectors', () => {
expect(userParams.id).toBe(id);
expect(userParams.name).toBe(name);
});
+ it('test userGroupsEnabledSelector ', () => {
+ const userGroups = userGroupsEnabledSelector(initialState);
+ expect(userGroups).toBeTruthy();
+ expect(userGroups).toEqual(['everyone']);
+ });
+ describe('isUserAllowedForEditingSelector', () => {
+ const state = {
+ security: {
+ user: {
+ role: 'USER',
+ groups: {
+ group: {
+ enabled: true,
+ groupName: 'test'
+ }
+ }
+ }
+ }
+ };
+ it('test with allowedRole ALL', () => {
+ expect(isUserAllowedSelectorCreator({
+ allowedRoles: ["ALL"]
+ })(state)).toBeTruthy();
+ });
+ it('test with both role and group matching both allowedRoles and allowedGroups', () => {
+ expect(isUserAllowedSelectorCreator({
+ allowedRoles: ["USER"],
+ allowedGroups: ["test"]
+ })(state)).toBeTruthy();
+ });
+ it('test with role ADMIN and allowedRoles', () => {
+ expect(isUserAllowedSelectorCreator({
+ allowedRoles: ['ADMIN']
+ })({
+ security: {
+ user: {
+ role: 'ADMIN',
+ groups: {
+ group: {
+ enabled: true,
+ groupName: 'test'
+ }
+ }
+ }
+ }
+ })).toBeTruthy();
+ });
+ it('test with role ADMIN and allowedGroups', () => {
+ expect(isUserAllowedSelectorCreator({
+ allowedGroups: ['test']
+ })({
+ security: {
+ user: {
+ role: 'ADMIN',
+ groups: {
+ group: {
+ enabled: true,
+ groupName: 'test'
+ }
+ }
+ }
+ }
+ })).toBeTruthy();
+ });
+ it('test with role non-admin and allowedgroups', () => {
+ expect(isUserAllowedSelectorCreator({
+ allowedGroups: ['test']
+ })(state)).toBeTruthy();
+ });
+ it('test with role non-admin and allowedroles', () => {
+ expect(isUserAllowedSelectorCreator({
+ allowedRoles: ['USER']
+ })(state)).toBeTruthy();
+ });
+ it('test not allowed for edit', () => {
+ expect(isUserAllowedSelectorCreator({
+ allowedRoles: ['USER1'],
+ allowedGroups: ['some']
+ })(state)).toBeFalsy();
+ });
+ });
});
diff --git a/web/client/selectors/__tests__/styleeditor-test.js b/web/client/selectors/__tests__/styleeditor-test.js
index c07b62ca07..842b7d4b82 100644
--- a/web/client/selectors/__tests__/styleeditor-test.js
+++ b/web/client/selectors/__tests__/styleeditor-test.js
@@ -29,8 +29,14 @@ import {
getAllStyles,
selectedStyleFormatSelector,
editorMetadataSelector,
- selectedStyleMetadataSelector
+ selectedStyleMetadataSelector,
+ editingAllowedRolesSelector,
+ editingAllowedGroupsSelector
} from '../styleeditor';
+import {
+ setCustomUtils,
+ StyleEditorCustomUtils
+} from "../../utils/StyleEditorUtils";
describe('Test styleeditor selector', () => {
it('test temporaryIdSelector', () => {
@@ -390,17 +396,6 @@ describe('Test styleeditor selector', () => {
}
);
});
- it('test canEditStyleSelector', () => {
- const state = {
- styleeditor: {
- canEdit: true
- }
- };
- const retval = canEditStyleSelector(state);
-
- expect(retval).toExist();
- expect(retval).toBe(true);
- });
it('test getUpdatedLayer', () => {
const state = {
layers: {
@@ -683,4 +678,119 @@ describe('Test styleeditor selector', () => {
styleJSON: 'null'
});
});
+ it('test editingAllowedRolesSelector', () => {
+ expect(editingAllowedRolesSelector({
+ styleeditor: {
+ editingAllowedRoles: ['ADMIN']
+ }
+ })).toEqual(['ADMIN']);
+ });
+ it('test editingAllowedGroupsSelector', () => {
+ expect(editingAllowedGroupsSelector({
+ styleeditor: {
+ editingAllowedGroups: ['test']
+ }
+ })).toEqual(['test']);
+ });
+ describe('canEditStyleSelector', () => {
+ const isSameOrigin = StyleEditorCustomUtils.isSameOrigin;
+ before(() => {
+ setCustomUtils('isSameOrigin', () => true);
+ });
+ after(()=> {
+ setCustomUtils('isSameOrigin', isSameOrigin);
+ });
+ it('test with role ADMIN', () => {
+ expect(canEditStyleSelector({
+ styleeditor: {
+ editingAllowedRoles: ['ADMIN']
+ },
+ security: {
+ user: {
+ role: 'ADMIN',
+ groups: {
+ group: {
+ enabled: true,
+ groupName: 'test'
+ }
+ }
+ }
+ }
+ })).toBeTruthy();
+ });
+ it('test with user matching allowedRoles', () => {
+ expect(canEditStyleSelector({
+ styleeditor: {
+ editingAllowedRoles: ['USER']
+ },
+ security: {
+ user: {
+ role: 'USER',
+ groups: {
+ group: {
+ enabled: true,
+ groupName: 'test'
+ }
+ }
+ }
+ }
+ })).toBeTruthy();
+ });
+ it('test with user matching allowedGroups', () => {
+ expect(canEditStyleSelector({
+ styleeditor: {
+ editingAllowedGroups: ['test']
+ },
+ security: {
+ user: {
+ role: 'USER',
+ groups: {
+ group: {
+ enabled: true,
+ groupName: 'test'
+ }
+ }
+ }
+ }
+ })).toBeTruthy();
+ });
+ it('test with user matching both allowedRoles and allowedGroups', () => {
+ expect(canEditStyleSelector({
+ styleeditor: {
+ editingAllowedRoles: ['USER'],
+ editingAllowedGroups: ['test']
+ },
+ security: {
+ user: {
+ role: 'USER',
+ groups: {
+ group: {
+ enabled: true,
+ groupName: 'test'
+ }
+ }
+ }
+ }
+ })).toBeTruthy();
+ });
+ it('test not allowed for editing', () => {
+ expect(canEditStyleSelector({
+ styleeditor: {
+ editingAllowedRoles: ['USER1'],
+ editingAllowedGroups: ['some']
+ },
+ security: {
+ user: {
+ role: 'USER',
+ groups: {
+ group: {
+ enabled: true,
+ groupName: 'test'
+ }
+ }
+ }
+ }
+ })).toBeFalsy();
+ });
+ });
});
diff --git a/web/client/selectors/featuregrid.js b/web/client/selectors/featuregrid.js
index 7a4125028a..b9856e6de8 100644
--- a/web/client/selectors/featuregrid.js
+++ b/web/client/selectors/featuregrid.js
@@ -14,13 +14,13 @@ import { currentLocaleSelector } from './locale';
import { isSimpleGeomType } from '../utils/MapUtils';
import { toChangesMap } from '../utils/FeatureGridUtils';
import { layerDimensionSelectorCreator } from './dimension';
-import { userRoleSelector } from './security';
+import { isUserAllowedSelectorCreator } from './security';
import {isCesium, mapTypeSelector} from './maptype';
import { attributesSelector, describeSelector } from './query';
-import {createShallowSelectorCreator} from "../utils/ReselectUtils";
+import { createShallowSelectorCreator } from "../utils/ReselectUtils";
import isEqual from "lodash/isEqual";
-import {mapBboxSelector, projectionSelector} from "./map";
-import {bboxToFeatureGeometry} from "../utils/CoordinatesUtils";
+import { mapBboxSelector, projectionSelector } from "./map";
+import { bboxToFeatureGeometry } from "../utils/CoordinatesUtils";
import { MapLibraries } from '../utils/MapTypeUtils';
export const getLayerById = getLayerFromId;
@@ -76,6 +76,7 @@ export const getAttributeFilters = state => state && state.featuregrid && state.
export const selectedLayerParamsSelector = state => get(getLayerById(state, selectedLayerIdSelector(state)), "params");
export const selectedLayerSelector = state => getLayerById(state, selectedLayerIdSelector(state));
export const editingAllowedRolesSelector = state => get(state, "featuregrid.editingAllowedRoles", ["ADMIN"]);
+export const editingAllowedGroupsSelector = state => get(state, "featuregrid.editingAllowedGroups", []);
export const canEditSelector = state => state && state.featuregrid && state.featuregrid.canEdit;
/**
* selects featuregrid state
@@ -192,12 +193,15 @@ export const queryOptionsSelector = state => {
cqlFilter
};
};
-export const isEditingAllowedSelector = state => {
- const role = userRoleSelector(state);
- const editingAllowedRoles = editingAllowedRolesSelector(state) || ['ADMIN'];
+export const isEditingAllowedSelector = (state) => {
+ const allowedRoles = editingAllowedRolesSelector(state);
+ const allowedGroups = editingAllowedGroupsSelector(state);
const canEdit = canEditSelector(state);
-
- return (editingAllowedRoles.indexOf(role) !== -1 || canEdit) && !isCesium(state);
+ const isAllowed = isUserAllowedSelectorCreator({
+ allowedRoles,
+ allowedGroups
+ })(state);
+ return (canEdit || isAllowed) && !isCesium(state);
};
export const paginationSelector = state => get(state, "featuregrid.pagination");
export const useLayerFilterSelector = state => get(state, "featuregrid.useLayerFilter", true);
diff --git a/web/client/selectors/security.js b/web/client/selectors/security.js
index 786286a970..0f268a8e50 100644
--- a/web/client/selectors/security.js
+++ b/web/client/selectors/security.js
@@ -8,7 +8,8 @@
import assign from 'object-assign';
-import { get } from 'lodash';
+import get from 'lodash/get';
+import castArray from "lodash/castArray";
export const rulesSelector = (state) => {
if (!state.security || !state.security.rules) {
@@ -32,6 +33,14 @@ export const rulesSelector = (state) => {
export const userSelector = (state) => state && state.security && state.security.user;
export const userGroupSecuritySelector = (state) => get(state, "security.user.groups.group");
+export const userGroupsEnabledSelector = (state) => {
+ const securityGroup = userGroupSecuritySelector(state);
+ return securityGroup
+ ? castArray(securityGroup)
+ ?.filter(group => group.enabled)
+ ?.map(group => group.groupName)
+ : [];
+};
export const userRoleSelector = (state) => userSelector(state) && userSelector(state).role;
export const userParamsSelector = (state) => {
const user = userSelector(state);
@@ -46,3 +55,25 @@ export const securityTokenSelector = state => state.security && state.security.t
export const isAdminUserSelector = (state) => userRoleSelector(state) === "ADMIN";
export const isUserSelector = (state) => userRoleSelector(state) === "USER";
export const authProviderSelector = state => state.security && state.security.authProvider;
+
+/**
+ * Creates a selector that checks if user is allowed to edit
+ * something based on the user's role and groups
+ * by passing the authorized roles and groups as parameter for selector creation.
+ * @param {string[]} allowedRoles array of roles allowed. Supports predefined ("ADMIN", "USER", "ALL") and custom roles
+ * @param {string[]} allowedGroups array of user group names allowed
+ * @returns {function(*): boolean}
+ */
+export const isUserAllowedSelectorCreator = ({
+ allowedRoles,
+ allowedGroups
+})=> (state) => {
+ const role = userRoleSelector(state);
+ const groups = userGroupsEnabledSelector(state);
+ return (
+ castArray(allowedRoles).includes('ALL')
+ || castArray(allowedRoles).includes(role)
+ || castArray(allowedGroups)
+ .some((group) => groups.includes(group))
+ );
+};
diff --git a/web/client/selectors/styleeditor.js b/web/client/selectors/styleeditor.js
index 04d8422a6e..3031ce1d08 100644
--- a/web/client/selectors/styleeditor.js
+++ b/web/client/selectors/styleeditor.js
@@ -9,7 +9,8 @@
import { get, head, uniqBy, find, isString } from 'lodash';
import { layerSettingSelector, getSelectedLayer } from './layers';
-import { STYLE_ID_SEPARATOR, extractFeatureProperties } from '../utils/StyleEditorUtils';
+import { STYLE_ID_SEPARATOR, extractFeatureProperties, isSameOrigin } from '../utils/StyleEditorUtils';
+import { isUserAllowedSelectorCreator } from "./security";
/**
* selects styleeditor state
@@ -102,13 +103,6 @@ export const enabledStyleEditorSelector = state => get(state, 'styleeditor.enabl
* @return {object} eg: styleService: {baseUrl: '/geoserver/', formats: ['css', 'sld'], availableUrls: ['http://localhost:8081/geoserver/']}
*/
export const styleServiceSelector = state => get(state, 'styleeditor.service') || {};
-/**
- * selects canEdit status of styleeditor service from state
- * @memberof selectors.styleeditor
- * @param {object} state the state
- * @return {bool}
- */
-export const canEditStyleSelector = state => get(state, 'styleeditor.canEdit');
/**
* selects layer with current changes applied in settings session from state
* @memberof selectors.styleeditor
@@ -120,6 +114,36 @@ export const getUpdatedLayer = state => {
const selectedLayer = getSelectedLayer(state) || {};
return {...selectedLayer, ...(settings && settings.options || {})};
};
+/**
+ * Selects configured editing roles allowed
+ * @memberof selectors.styleeditor
+ * @param {object} state the state
+ * @returns {object}
+ */
+export const editingAllowedRolesSelector = (state) => get(state, 'styleeditor.editingAllowedRoles', []);
+/**
+ * Selects configured editing groups allowed
+ * @memberof selectors.styleeditor
+ * @param {object} state the state
+ * @returns {object}
+ */
+export const editingAllowedGroupsSelector = (state) => get(state, 'styleeditor.editingAllowedGroups', []);
+/**
+ * selects canEdit status of styleeditor service from state
+ * @memberof selectors.styleeditor
+ * @param {object} state the state
+ * @return {bool}
+ */
+export const canEditStyleSelector = (state) => {
+ const allowedRoles = editingAllowedRolesSelector(state);
+ const allowedGroups = editingAllowedGroupsSelector(state);
+ const _isSameOrigin = isSameOrigin(getUpdatedLayer(state), styleServiceSelector(state));
+ const isAllowed = isUserAllowedSelectorCreator({
+ allowedRoles,
+ allowedGroups
+ })(state);
+ return isAllowed && _isSameOrigin;
+};
/**
* selects geometry type of selected layer from state
* @memberof selectors.styleeditor
diff --git a/web/client/utils/StyleEditorUtils.js b/web/client/utils/StyleEditorUtils.js
index 97804382a7..749a3e4190 100644
--- a/web/client/utils/StyleEditorUtils.js
+++ b/web/client/utils/StyleEditorUtils.js
@@ -36,7 +36,7 @@ const xmlBuilder = new xml2js.Builder();
export const STYLE_ID_SEPARATOR = '___';
export const STYLE_OWNER_NAME = 'styleeditor';
-const StyleEditorCustomUtils = {};
+export const StyleEditorCustomUtils = {};
const EDITOR_MODES = {
'css': 'geocss',