Add permissions to roles

Signed-off-by: soupette <cyril.lpz@gmail.com>
This commit is contained in:
soupette 2020-06-18 11:01:44 +02:00 committed by Alexandre Bodin
parent a83c34e72e
commit cc20facc5e
17 changed files with 319 additions and 78 deletions

View File

@ -0,0 +1,52 @@
{
"routes": [
{
"method": "GET",
"path": "/vegetables",
"handler": "vegetable.find",
"config": {
"policies": []
}
},
{
"method": "GET",
"path": "/vegetables/count",
"handler": "vegetable.count",
"config": {
"policies": []
}
},
{
"method": "GET",
"path": "/vegetables/:id",
"handler": "vegetable.findOne",
"config": {
"policies": []
}
},
{
"method": "POST",
"path": "/vegetables",
"handler": "vegetable.create",
"config": {
"policies": []
}
},
{
"method": "PUT",
"path": "/vegetables/:id",
"handler": "vegetable.update",
"config": {
"policies": []
}
},
{
"method": "DELETE",
"path": "/vegetables/:id",
"handler": "vegetable.delete",
"config": {
"policies": []
}
}
]
}

View File

@ -0,0 +1,8 @@
'use strict';
/**
* Read the documentation (https://strapi.io/documentation/v3.x/concepts/controllers.html#core-controllers)
* to customize this controller
*/
module.exports = {};

View File

@ -0,0 +1,8 @@
'use strict';
/**
* Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#lifecycle-hooks)
* to customize this model
*/
module.exports = {};

View File

@ -0,0 +1,16 @@
{
"kind": "collectionType",
"collectionName": "vegetables",
"info": {
"name": "vegetable"
},
"options": {
"increments": true,
"timestamps": true
},
"attributes": {
"name": {
"type": "string"
}
}
}

View File

@ -0,0 +1,8 @@
'use strict';
/**
* Read the documentation (https://strapi.io/documentation/v3.x/concepts/services.html#core-services)
* to customize this service
*/
module.exports = {};

View File

@ -3,8 +3,9 @@ import { Header } from '@buffetjs/custom';
import { Padded } from '@buffetjs/core';
import { Formik } from 'formik';
import { useIntl } from 'react-intl';
import { request } from 'strapi-helper-plugin';
import { CheckPagePermissions, request } from 'strapi-helper-plugin';
import { useHistory } from 'react-router-dom';
import adminPermissions from '../../../../src/permissions';
import { useFetchPermissionsLayout } from '../../../../src/hooks';
import BaselineAlignement from '../../../../src/components/BaselineAlignement';
import ContainerFluid from '../../../../src/components/ContainerFluid';
@ -144,4 +145,10 @@ const CreatePage = () => {
);
};
export default CreatePage;
export default () => (
<CheckPagePermissions permissions={adminPermissions.settings.roles.create}>
<CreatePage />
</CheckPagePermissions>
);
export { CreatePage };

View File

@ -7,7 +7,16 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { RoleRow as RoleRowBase } from '../../../../src/components/Roles';
import Checkbox from './CustomCheckbox';
const RoleRow = ({ role, onRoleToggle, onRoleDuplicate, onRoleRemove, selectedRoles }) => {
const RoleRow = ({
canCreate,
canDelete,
canUpdate,
role,
onRoleToggle,
onRoleDuplicate,
onRoleRemove,
selectedRoles,
}) => {
const { push } = useHistory();
const { settingsBaseURL } = useGlobalContext();
@ -25,13 +34,13 @@ const RoleRow = ({ role, onRoleToggle, onRoleDuplicate, onRoleRemove, selectedRo
}
};
const prefix = (
const prefix = canDelete ? (
<Checkbox
value={selectedRoles.findIndex(selectedRoleId => selectedRoleId === role.id) !== -1}
onClick={handleRoleSelection}
name="role-checkbox"
/>
);
) : null;
return (
<RoleRowBase
@ -40,15 +49,15 @@ const RoleRow = ({ role, onRoleToggle, onRoleDuplicate, onRoleRemove, selectedRo
role={role}
links={[
{
icon: <Duplicate fill="#0e1622" />,
icon: canCreate ? <Duplicate fill="#0e1622" /> : null,
onClick: () => onRoleDuplicate(role.id),
},
{
icon: <Pencil fill="#0e1622" />,
icon: canUpdate ? <Pencil fill="#0e1622" /> : null,
onClick: () => push(`${settingsBaseURL}/roles/${role.id}`),
},
{
icon: <FontAwesomeIcon icon="trash-alt" />,
icon: canDelete ? <FontAwesomeIcon icon="trash-alt" /> : null,
onClick: handleClickDelete,
},
]}
@ -61,6 +70,9 @@ RoleRow.defaultProps = {
};
RoleRow.propTypes = {
canCreate: PropTypes.bool.isRequired,
canDelete: PropTypes.bool.isRequired,
canUpdate: PropTypes.bool.isRequired,
onRoleToggle: PropTypes.func.isRequired,
onRoleDuplicate: PropTypes.func.isRequired,
onRoleRemove: PropTypes.func.isRequired,

View File

@ -1,4 +1,4 @@
import React, { useEffect, useReducer, useState } from 'react';
import React, { useEffect, useReducer, useRef, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { Button } from '@buffetjs/core';
import { List, Header } from '@buffetjs/custom';
@ -10,9 +10,11 @@ import {
ListButton,
PopUpWarning,
request,
useUserPermissions,
LoadingIndicatorPage,
} from 'strapi-helper-plugin';
import { useIntl } from 'react-intl';
import adminPermissions from '../../../../src/permissions';
import useSettingsHeaderSearchContext from '../../../../src/hooks/useSettingsHeaderSearchContext';
import { EmptyRole, RoleListWrapper } from '../../../../src/components/Roles';
import { useRolesList } from '../../../../src/hooks';
@ -29,20 +31,37 @@ const RoleListPage = () => {
reducer,
initialState
);
const { getData, roles, isLoading } = useRolesList();
const {
isLoading: isLoadingForPermissions,
allowedActions: { canCreate, canDelete, canRead, canUpdate },
} = useUserPermissions(adminPermissions.settings.roles);
const { getData, roles, isLoading } = useRolesList(false);
const getDataRef = useRef(getData);
// console.log({ roles });
const { toggleHeaderSearch } = useSettingsHeaderSearchContext();
const query = useQuery();
const _q = decodeURIComponent(query.get('_q') || '');
const results = matchSorter(roles, _q, { keys: ['name', 'description'] });
useEffect(() => {
toggleHeaderSearch({ id: 'Settings.permissions.menu.link.roles.label' });
// Show the search bar only if the user is allowed to read
if (canRead) {
toggleHeaderSearch({ id: 'Settings.permissions.menu.link.roles.label' });
}
return () => {
toggleHeaderSearch();
if (canRead) {
toggleHeaderSearch();
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [canRead]);
useEffect(() => {
if (!isLoadingForPermissions && canRead) {
getDataRef.current();
}
}, [isLoadingForPermissions, canRead]);
const handleClosedModal = () => {
if (shouldRefetchData) {
@ -115,21 +134,29 @@ const RoleListPage = () => {
const handleToggleModal = () => setIsWarningDeleteAllOpenend(prev => !prev);
const headerActions = [
{
label: formatMessage({
id: 'Settings.roles.list.button.add',
defaultMessage: 'Add new role',
}),
onClick: handleNewRoleClick,
color: 'primary',
type: 'button',
icon: true,
},
];
/* eslint-disable indent */
const headerActions = canCreate
? [
{
label: formatMessage({
id: 'Settings.roles.list.button.add',
defaultMessage: 'Add new role',
}),
onClick: handleNewRoleClick,
color: 'primary',
type: 'button',
icon: true,
},
]
: [];
/* eslint-enable indent */
const resultsCount = results.length;
if (isLoadingForPermissions) {
return <LoadingIndicatorPage />;
}
return (
<>
<Header
@ -147,46 +174,59 @@ const RoleListPage = () => {
isLoading={isLoading}
/>
<BaselineAlignment />
<RoleListWrapper>
<List
title={formatMessage(
{
id: `Settings.roles.list.title${resultsCount > 1 ? '.plural' : '.singular'}`,
defaultMessage: `{number} ${resultsCount > 1 ? 'roles' : 'role'}`,
},
{ number: resultsCount }
)}
isLoading={isLoading}
button={{
color: 'delete',
disabled: selectedRoles.length === 0,
label: formatMessage({ id: 'app.utils.delete', defaultMessage: 'Delete' }),
onClick: handleToggleModal,
type: 'button',
}}
items={results}
customRowComponent={role => (
<RoleRow
selectedRoles={selectedRoles}
onRoleDuplicate={handleDuplicateRole}
onRoleRemove={handleRemoveRole}
onRoleToggle={handleRoleToggle}
role={role}
/>
)}
/>
{!resultsCount && !isLoading && <EmptyRole />}
<ListButton>
<Button
onClick={handleNewRoleClick}
icon={<Plus fill="#007eff" width="11px" height="11px" />}
label={formatMessage({
id: 'Settings.roles.list.button.add',
defaultMessage: 'Add new role',
})}
{canRead && (
<RoleListWrapper>
<List
title={formatMessage(
{
id: `Settings.roles.list.title${resultsCount > 1 ? '.plural' : '.singular'}`,
defaultMessage: `{number} ${resultsCount > 1 ? 'roles' : 'role'}`,
},
{ number: resultsCount }
)}
isLoading={isLoading}
/* eslint-disable indent */
button={
canDelete
? {
color: 'delete',
disabled: selectedRoles.length === 0,
label: formatMessage({ id: 'app.utils.delete', defaultMessage: 'Delete' }),
onClick: handleToggleModal,
type: 'button',
}
: null
}
/* eslint-enable indent */
items={results}
customRowComponent={role => (
<RoleRow
canCreate={canCreate}
canDelete={canDelete}
canUpdate={canUpdate}
selectedRoles={selectedRoles}
onRoleDuplicate={handleDuplicateRole}
onRoleRemove={handleRemoveRole}
onRoleToggle={handleRoleToggle}
role={role}
/>
)}
/>
</ListButton>
</RoleListWrapper>
{!resultsCount && !isLoading && <EmptyRole />}
{canCreate && (
<ListButton>
<Button
onClick={handleNewRoleClick}
icon={<Plus fill="#007eff" width="11px" height="11px" />}
label={formatMessage({
id: 'Settings.roles.list.button.add',
defaultMessage: 'Add new role',
})}
/>
</ListButton>
)}
</RoleListWrapper>
)}
<PopUpWarning
isOpen={isWarningDeleteAllOpened}
onClosed={handleClosedModal}

View File

@ -0,0 +1,12 @@
import React from 'react';
import { CheckPagePermissions } from 'strapi-helper-plugin';
import adminPermissions from '../../../../src/permissions';
import ListPage from '../ListPage';
const ProtectedListPage = () => (
<CheckPagePermissions permissions={adminPermissions.settings.roles.main}>
<ListPage />
</CheckPagePermissions>
);
export default ProtectedListPage;

View File

@ -4,8 +4,8 @@ import { Pencil } from '@buffetjs/icons';
import matchSorter from 'match-sorter';
import { useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { useGlobalContext, useQuery } from 'strapi-helper-plugin';
import { useGlobalContext, useQuery, useUserPermissions } from 'strapi-helper-plugin';
import adminPermissions from '../../../permissions';
import { EmptyRole, RoleListWrapper, RoleRow } from '../../../components/Roles';
import { useRolesList, useSettingsHeaderSearchContext } from '../../../hooks';
import BaselineAlignment from './BaselineAlignment';
@ -16,6 +16,9 @@ const RoleListPage = () => {
const { settingsBaseURL } = useGlobalContext();
const { roles, isLoading } = useRolesList();
const { toggleHeaderSearch } = useSettingsHeaderSearchContext();
const {
allowedActions: { canUpdate },
} = useUserPermissions(adminPermissions.settings.roles);
const query = useQuery();
const _q = decodeURIComponent(query.get('_q') || '');
const results = matchSorter(roles, _q, { keys: ['name', 'description'] });
@ -45,6 +48,8 @@ const RoleListPage = () => {
id: 'Settings.roles.list.description',
defaultMessage: 'List of roles',
})}
// Show a loader in the header while requesting data
isLoading={isLoading}
/>
<BaselineAlignment />
<RoleListWrapper>
@ -59,9 +64,10 @@ const RoleListPage = () => {
isLoading={isLoading}
customRowComponent={role => (
<RoleRow
canUpdate={canUpdate}
links={[
{
icon: <Pencil fill="#0e1622" />,
icon: canUpdate ? <Pencil fill="#0e1622" /> : null,
onClick: () => push(`${settingsBaseURL}/roles/${role.id}`),
},
]}

View File

@ -0,0 +1,12 @@
import React from 'react';
import { CheckPagePermissions } from 'strapi-helper-plugin';
import adminPermissions from '../../../permissions';
import EditPage from '../EditPage';
const ProtectedListPage = () => (
<CheckPagePermissions permissions={adminPermissions.settings.roles.update}>
<EditPage />
</CheckPagePermissions>
);
export default ProtectedListPage;

View File

@ -0,0 +1,12 @@
import React from 'react';
import { CheckPagePermissions } from 'strapi-helper-plugin';
import adminPermissions from '../../../permissions';
import ListPage from '../ListPage';
const ProtectedListPage = () => (
<CheckPagePermissions permissions={adminPermissions.settings.roles.main}>
<ListPage />
</CheckPagePermissions>
);
export default ProtectedListPage;

View File

@ -18,17 +18,17 @@ import {
} from 'strapi-helper-plugin';
import { Switch, Redirect, Route, useParams, useHistory } from 'react-router-dom';
import RolesListPage from 'ee_else_ce/containers/Roles/ListPage';
import RolesCreatePage from 'ee_else_ce/containers/Roles/CreatePage';
import ProtectedRolesListPage from 'ee_else_ce/containers/Roles/ProtectedListPage';
// TODO remove this line when feature finished
// import ProtectedRolesListPage from '../Roles/ProtectedListPage';
import HeaderSearch from '../../components/HeaderSearch';
import { useSettingsMenu } from '../../hooks';
import { retrieveGlobalLinks } from '../../utils';
import SettingsSearchHeaderProvider from '../SettingsHeaderSearchContextProvider';
import UsersEditPage from '../Users/ProtectedEditPage';
import UsersListPage from '../Users/ProtectedListPage';
import RolesEditPage from '../Roles/EditPage';
// TODO remove this line when feature finished
// import RolesListPage from '../Roles/ListPage';
import RolesEditPage from '../Roles/ProtectedEditPage';
import {
createRoute,
findFirstAllowedEndpoint,
@ -114,7 +114,7 @@ function SettingsPage() {
</div>
<div className="col-md-9">
<Switch>
<Route exact path={`${settingsBaseURL}/roles`} component={RolesListPage} />
<Route exact path={`${settingsBaseURL}/roles`} component={ProtectedRolesListPage} />
<Route exact path={`${settingsBaseURL}/roles/new`} component={RolesCreatePage} />
<Route exact path={`${settingsBaseURL}/roles/:id`} component={RolesEditPage} />
<Route exact path={`${settingsBaseURL}/users`} component={UsersListPage} />

View File

@ -1,15 +1,19 @@
import { useEffect, useReducer } from 'react';
import { request } from 'strapi-helper-plugin';
import { get } from 'lodash';
import init from './init';
import reducer, { initialState } from './reducer';
const useRolesList = () => {
const [{ roles, isLoading }, dispatch] = useReducer(reducer, initialState);
const useRolesList = (shouldFetchData = true) => {
const [{ roles, isLoading }, dispatch] = useReducer(reducer, initialState, () =>
init(initialState, shouldFetchData)
);
useEffect(() => {
fetchRolesList();
}, []);
if (shouldFetchData) {
fetchRolesList();
}
}, [shouldFetchData]);
const fetchRolesList = async () => {
try {

View File

@ -0,0 +1,5 @@
const init = (initialState, shouldFetchData) => {
return { ...initialState, isLoading: shouldFetchData };
};
export default init;

View File

@ -0,0 +1,31 @@
import init from '../init';
describe('ADMIN | HOOKS | useRolesList | init', () => {
it('should return the initial state and set the isLoading key to true', () => {
const initialState = {
roles: [],
isLoading: null,
};
const expected = {
roles: [],
isLoading: true,
};
expect(init(initialState, true)).toEqual(expected);
});
it('should return the initial state and set the isLoading key to false', () => {
const initialState = {
roles: [],
isLoading: null,
};
const expected = {
roles: [],
isLoading: false,
};
expect(init(initialState, false)).toEqual(expected);
});
});

View File

@ -20,6 +20,14 @@ const permissions = {
{ action: 'admin::roles.read', subject: null },
{ action: 'admin::roles.delete', subject: null },
],
create: [{ action: 'admin::roles.create', subject: null }],
delete: [{ action: 'admin::roles.delete', subject: null }],
read: [
{ action: 'admin::roles.read', subject: null },
{ action: 'admin::roles.delete', subject: null },
{ action: 'admin::roles.update', subject: null },
],
update: [{ action: 'admin::roles.update', subject: null }],
},
users: {
main: [