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 628a252
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ describe('Groups Control Panel Test', () => {

// select first group with name, delete it and search if its exists or not!
cy.get('div[role="listbox"]').first().click();
cy.get('div[role="option"]').should('be.visible');
cy.get('div[role="option"]').first().click();
cy.contains('Delete Group');
cy.get('button.ui.primary.button').should('have.text', 'OK').click();
Expand Down
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 @@ -49,6 +49,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 628a252

Please sign in to comment.