Skip to content

Commit

Permalink
* #9179: Include editing support to allowed user groups
Browse files Browse the repository at this point in the history
* Code refactor

* Unit tests corrected

* Permission hierarchy updated
  • Loading branch information
dsuren1 authored and offtherailz committed Jun 21, 2023
1 parent 9dcafe5 commit 3bbffec
Show file tree
Hide file tree
Showing 19 changed files with 469 additions and 128 deletions.
4 changes: 3 additions & 1 deletion web/client/actions/__tests__/featuregrid-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
9 changes: 6 additions & 3 deletions web/client/actions/__tests__/styleeditor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions web/client/actions/styleeditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
}
/**
Expand Down
3 changes: 2 additions & 1 deletion web/client/components/data/featuregrid/enhancers/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ const featuresToGrid = compose(
props => ({displayFilters: props.enableColumnFilters})
),
withPropsOnChange(
["editingAllowedRoles", "virtualScroll"],
["editingAllowedRoles", "editingAllowedGroups", "virtualScroll"],
props => ({
editingAllowedRoles: props.editingAllowedRoles,
editingAllowedGroups: props.editingAllowedGroups,
initPlugin: props.initPlugin
})
),
Expand Down
6 changes: 5 additions & 1 deletion web/client/plugins/FeatureEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
143 changes: 80 additions & 63 deletions web/client/plugins/StyleEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<BorderLayout
className="ms-style-editor-container"
header={
showToolbar ? <div className="ms-style-editor-container-header">
{header}
<div className="text-center">
<StyleToolbar
enableSetDefaultStyle={enableSetDefaultStyle}/>
</div>
</div> : null
}
footer={<div style={{ height: 25 }} />}>
{isEditing
? <StyleCodeEditor config={editorConfig}/>
: <StyleSelector
showDefaultStyleIcon={canEdit && enableSetDefaultStyle}/>}
</BorderLayout>
);
};
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 (
<BorderLayout
className="ms-style-editor-container"
header={
this.props.showToolbar ? <div className="ms-style-editor-container-header">
{this.props.header}
<div className="text-center">
<StyleToolbar
enableSetDefaultStyle={this.props.enableSetDefaultStyle}/>
</div>
</div> : null
}
footer={<div style={{ height: 25 }} />}>
{this.props.isEditing
? <StyleCodeEditor config={this.props.editorConfig}/>
: <StyleSelector
showDefaultStyleIcon={this.props.canEdit && this.props.enableSetDefaultStyle}/>}
</BorderLayout>
);
}
}
/**
* StyleEditor plugin.
* - Select styles from available styles of the layer
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
37 changes: 24 additions & 13 deletions web/client/plugins/__tests__/StyleEditor-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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 = {
Expand All @@ -70,17 +75,23 @@ describe('StyleEditor Plugin', () => {
service: stateStyleService
}
});
const permissions = {
"editingAllowedRoles": ["USER"],
"editingAllowedGroups": ["temp"]
};
act(() => {
ReactDOM.render(<Plugin
active
styleService={cfgStyleService}
{...permissions}
/>, 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 = {
Expand All @@ -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);
});
});
16 changes: 13 additions & 3 deletions web/client/plugins/featuregrid/FeatureEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
* <br/>virtual scroll mechanisms. By default virtual scroll is enabled. When on virtual scroll mode, the maxStoredPages param
* <br/>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.
* <br/>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.
Expand Down Expand Up @@ -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
]);

Expand Down
Loading

0 comments on commit 3bbffec

Please sign in to comment.