Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#9179: Include editing support to allowed user groups #9210

Merged
merged 4 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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
})
),
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