Skip to content

Commit

Permalink
Do not display options for Site Administrator to create, modify, or
Browse files Browse the repository at this point in the history
delete Manager users
  • Loading branch information
wesleybl committed Feb 20, 2024
1 parent e7788b3 commit dec7146
Show file tree
Hide file tree
Showing 14 changed files with 238 additions and 49 deletions.
1 change: 1 addition & 0 deletions packages/volto/news/5244.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Do not display options for Site Administrator to create, modify, or delete Manager users. @wesleybl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
listRoles,
updateGroup,
authenticatedRole,
getUser,
} from '@plone/volto/actions';
import jwtDecode from 'jwt-decode';
import {
Icon,
ModalForm,
Expand All @@ -21,7 +23,12 @@ import {
Error,
} from '@plone/volto/components';
import { Link } from 'react-router-dom';
import { Helmet, messages } from '@plone/volto/helpers';
import {
Helmet,
messages,
isManager,
canAssignRole,
} from '@plone/volto/helpers';
import clearSVG from '@plone/volto/icons/clear.svg';
import addUserSvg from '@plone/volto/icons/add-user.svg';
import saveSVG from '@plone/volto/icons/save.svg';
Expand Down Expand Up @@ -75,6 +82,19 @@ class GroupsControlpanel extends Component {
groupname: PropTypes.string,
}),
).isRequired,
user: PropTypes.shape({
'@id': PropTypes.string,
id: PropTypes.string,
description: PropTypes.string,
email: PropTypes.string,
fullname: PropTypes.string,
groups: PropTypes.object,
location: PropTypes.string,
portrait: PropTypes.string,
home_page: PropTypes.string,
roles: PropTypes.arrayOf(PropTypes.string),
username: PropTypes.string,
}).isRequired,
};

/**
Expand Down Expand Up @@ -118,6 +138,7 @@ class GroupsControlpanel extends Component {
groupEntries: this.props.groups,
});
}
await this.props.getUser(this.props.userId);
};
/**
* Component did mount
Expand Down Expand Up @@ -375,6 +396,8 @@ class GroupsControlpanel extends Component {
? this.state.groupToDelete.id
: '';

const isUserManager = isManager(this.props.user);

return (
<Container className="users-control-panel">
<Helmet title={this.props.intl.formatMessage(messages.groups)} />
Expand Down Expand Up @@ -460,10 +483,9 @@ class GroupsControlpanel extends Component {
messages.addGroupsFormRolesTitle,
),
type: 'array',
choices: this.props.roles.map((role) => [
role.id,
role.title,
]),
choices: this.props.roles
.filter((role) => canAssignRole(isUserManager, role))
.map((role) => [role.id, role.title]),
noValueOption: false,
description: '',
},
Expand Down Expand Up @@ -553,6 +575,7 @@ class GroupsControlpanel extends Component {
group={group}
updateGroups={this.updateGroupRole}
inheritedRole={this.state.authenticatedRole}
isUserManager={isUserManager}
/>
))}
</Table.Body>
Expand Down Expand Up @@ -640,6 +663,10 @@ export default compose(
injectIntl,
connect(
(state, props) => ({
user: state.users.user,
userId: state.userSession.token
? jwtDecode(state.userSession.token).sub
: '',
roles: state.roles.roles,
groups: state.groups.groups,
description: state.description,
Expand All @@ -661,6 +688,7 @@ export default compose(
createGroup,
updateGroup,
authenticatedRole,
getUser,
},
dispatch,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import jwt from 'jsonwebtoken';

import GroupsControlpanel from './GroupsControlpanel';

Expand All @@ -12,6 +13,17 @@ jest.mock('react-portal', () => ({
describe('UsersControlpanel', () => {
it('renders a user control component', () => {
const store = mockStore({
userSession: {
token: jwt.sign({ sub: 'john' }, 'secret'),
},
users: {
users: [],
create: { loading: false },
user: {
roles: ['Manager'],
'@id': 'admin',
},
},
roles: { roles: [] },
groups: {
groups: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Dropdown, Table, Checkbox } from 'semantic-ui-react';
import trashSVG from '@plone/volto/icons/delete.svg';
import ploneSVG from '@plone/volto/icons/plone.svg';
import { Icon } from '@plone/volto/components';
import { canAssignRole } from '@plone/volto/helpers';

/**
* UsersControlpanelGroups class.
Expand Down Expand Up @@ -38,6 +39,7 @@ class RenderGroups extends Component {
).isRequired,
inheritedRole: PropTypes.array,
onDelete: PropTypes.func.isRequired,
isUserManager: PropTypes.bool.isRequired,
};

/**
Expand Down Expand Up @@ -69,6 +71,12 @@ class RenderGroups extends Component {
isAuthGroup = (roleId) => {
return this.props.inheritedRole.includes(roleId);
};

canDeleteGroup() {
if (this.props.isUserManager) return true;
return !this.props.group.roles.includes('Manager');
}

/**
* Render method.
* @method render
Expand Down Expand Up @@ -98,22 +106,25 @@ class RenderGroups extends Component {
}
onChange={this.onChange}
value={`${this.props.group.id}&role=${role.id}`}
disabled={!canAssignRole(this.props.isUserManager, role)}
/>
)}
</Table.Cell>
))}
<Table.Cell textAlign="right">
<Dropdown icon="ellipsis horizontal">
<Dropdown.Menu className="left">
<Dropdown.Item
onClick={this.props.onDelete}
value={this.props.group['@id']}
>
<Icon name={trashSVG} size="15px" />
<FormattedMessage id="Delete" defaultMessage="Delete" />
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
{this.canDeleteGroup() && (
<Dropdown icon="ellipsis horizontal">
<Dropdown.Menu className="left">
<Dropdown.Item
onClick={this.props.onDelete}
value={this.props.group['@id']}
>
<Icon name={trashSVG} size="15px" />
<FormattedMessage id="Delete" defaultMessage="Delete" />
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
)}
</Table.Cell>
</Table.Row>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,27 @@ const testGroups = {
id: 'Administrators',
title: 'Administrators',
roles: ['Manager'],
can_delete: true,
};

const testRoles = [
{
'@id': 'http://localhost:8080/Plone/@roles/Member',
'@type': 'role',
id: 'Member',
can_assign: true,
},
{
'@id': 'http://localhost:8080/Plone/@roles/Reader',
'@type': 'role',
id: 'Reader',
can_assign: true,
},
{
'@id': 'http://localhost:8080/Plone/@roles/Manager',
'@type': 'role',
id: 'Manager',
can_assign: true,
},
];

Expand All @@ -49,6 +53,7 @@ describe('UsersControlpanelGroups', () => {
group={testGroups}
roles={testRoles}
onDelete={() => {}}
isUserManager={true}
/>
</Provider>,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ exports[`UsersControlpanelGroups renders a UsersControlpanelGroups component 1`]
<input
checked={false}
className="hidden"
disabled={false}
readOnly={true}
tabIndex={0}
type="checkbox"
Expand All @@ -43,6 +44,7 @@ exports[`UsersControlpanelGroups renders a UsersControlpanelGroups component 1`]
<input
checked={false}
className="hidden"
disabled={false}
readOnly={true}
tabIndex={0}
type="checkbox"
Expand All @@ -64,6 +66,7 @@ exports[`UsersControlpanelGroups renders a UsersControlpanelGroups component 1`]
<input
checked={true}
className="hidden"
disabled={false}
readOnly={true}
tabIndex={0}
type="checkbox"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { updateUser } from '@plone/volto/actions';
import ploneSVG from '@plone/volto/icons/plone.svg';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { messages } from '@plone/volto/helpers';
import { messages, canAssignRole } from '@plone/volto/helpers';
import { toast } from 'react-toastify';

/**
Expand All @@ -39,6 +39,7 @@ class RenderUsers extends Component {
}),
).isRequired,
onDelete: PropTypes.func.isRequired,
isUserManager: PropTypes.bool.isRequired,
};

/**
Expand Down Expand Up @@ -110,6 +111,11 @@ class RenderUsers extends Component {
this.setState({ user: { ...formData } });
}

canDeleteUser() {
if (this.props.isUserManager) return true;
return !this.props.user.roles.includes('Manager');
}

/**
* Render method.
* @method render
Expand Down Expand Up @@ -139,35 +145,38 @@ class RenderUsers extends Component {
checked={this.props.user.roles.includes(role.id)}
onChange={this.onChange}
value={`${this.props.user.id}&role=${role.id}`}
disabled={!canAssignRole(this.props.isUserManager, role)}
/>
)}
</Table.Cell>
))}
<Table.Cell textAlign="right">
<Dropdown icon="ellipsis horizontal">
<Dropdown.Menu className="left">
{this.props.userschema && (
{this.canDeleteUser() && (
<Dropdown icon="ellipsis horizontal">
<Dropdown.Menu className="left">
{this.props.userschema && (
<Dropdown.Item
id="edit-user-button"
onClick={() => {
this.onClickEdit({ formData: this.props.user });
}}
value={this.props.user['@id']}
>
<Icon name={editSVG} size="15px" />
<FormattedMessage id="Edit" defaultMessage="Edit" />
</Dropdown.Item>
)}
<Dropdown.Item
id="edit-user-button"
onClick={() => {
this.onClickEdit({ formData: this.props.user });
}}
id="delete-user-button"
onClick={this.props.onDelete}
value={this.props.user['@id']}
>
<Icon name={editSVG} size="15px" />
<FormattedMessage id="Edit" defaultMessage="Edit" />
<Icon name={trashSVG} size="15px" />
<FormattedMessage id="Delete" defaultMessage="Delete" />
</Dropdown.Item>
)}
<Dropdown.Item
id="delete-user-button"
onClick={this.props.onDelete}
value={this.props.user['@id']}
>
<Icon name={trashSVG} size="15px" />
<FormattedMessage id="Delete" defaultMessage="Delete" />
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</Dropdown.Menu>
</Dropdown>
)}
</Table.Cell>
{Object.keys(this.state.user).length > 0 &&
this.props.userschema.loaded && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ describe('UsersControlpanelUser', () => {
});
const component = renderer.create(
<Provider store={store}>
<RenderUsers user={testUser} roles={testRoles} onDelete={() => {}} />
<RenderUsers
user={testUser}
roles={testRoles}
onDelete={() => {}}
isUserManager={true}
/>
</Provider>,
);
const json = component.toJSON();
Expand Down
Loading

0 comments on commit dec7146

Please sign in to comment.