mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 15:13:21 +00:00
Add permissions to roles
Signed-off-by: soupette <cyril.lpz@gmail.com>
This commit is contained in:
parent
a83c34e72e
commit
cc20facc5e
52
examples/getstarted/api/vegetable/config/routes.json
Normal file
52
examples/getstarted/api/vegetable/config/routes.json
Normal 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": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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 = {};
|
||||
8
examples/getstarted/api/vegetable/models/vegetable.js
Normal file
8
examples/getstarted/api/vegetable/models/vegetable.js
Normal 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 = {};
|
||||
@ -0,0 +1,16 @@
|
||||
{
|
||||
"kind": "collectionType",
|
||||
"collectionName": "vegetables",
|
||||
"info": {
|
||||
"name": "vegetable"
|
||||
},
|
||||
"options": {
|
||||
"increments": true,
|
||||
"timestamps": true
|
||||
},
|
||||
"attributes": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
8
examples/getstarted/api/vegetable/services/vegetable.js
Normal file
8
examples/getstarted/api/vegetable/services/vegetable.js
Normal 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 = {};
|
||||
@ -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 };
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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;
|
||||
@ -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}`),
|
||||
},
|
||||
]}
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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} />
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
const init = (initialState, shouldFetchData) => {
|
||||
return { ...initialState, isLoading: shouldFetchData };
|
||||
};
|
||||
|
||||
export default init;
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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: [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user