105 lines
4.6 KiB
JavaScript
105 lines
4.6 KiB
JavaScript
import { jsx } from 'react/jsx-runtime';
|
|
import * as React from 'react';
|
|
import { createContext, useQueryParams, useAuth, useRBAC, Page } from '@strapi/admin/strapi-admin';
|
|
import { useParams } from 'react-router-dom';
|
|
|
|
const [DocumentRBACProvider, useDocumentRBAC] = createContext('DocumentRBAC', {
|
|
canCreate: false,
|
|
canCreateFields: [],
|
|
canDelete: false,
|
|
canPublish: false,
|
|
canRead: false,
|
|
canReadFields: [],
|
|
canUpdate: false,
|
|
canUpdateFields: [],
|
|
canUserAction: ()=>false,
|
|
isLoading: false
|
|
});
|
|
/**
|
|
* @internal This component is not meant to be used outside of the Content Manager plugin.
|
|
* It depends on knowing the slug/model of the content-type using the params of the URL or the model if it is passed as arg.
|
|
* If you do use the hook outside of the context, we default to `false` for all actions.
|
|
*
|
|
* It then creates an list of `can{Action}` that are passed to the context for consumption
|
|
* within the app to enforce RBAC.
|
|
*/ const DocumentRBAC = ({ children, permissions, model })=>{
|
|
const { slug } = useParams();
|
|
if (!slug && !model) {
|
|
throw new Error('Cannot find the slug param in the URL or the model prop is not provided.');
|
|
}
|
|
const contentTypeUid = model ?? slug;
|
|
const [{ rawQuery }] = useQueryParams();
|
|
const userPermissions = useAuth('DocumentRBAC', (state)=>state.permissions);
|
|
const contentTypePermissions = React.useMemo(()=>{
|
|
const contentTypePermissions = userPermissions.filter((permission)=>permission.subject === contentTypeUid);
|
|
return contentTypePermissions.reduce((acc, permission)=>{
|
|
const [action] = permission.action.split('.').slice(-1);
|
|
return {
|
|
...acc,
|
|
[action]: [
|
|
permission
|
|
]
|
|
};
|
|
}, {});
|
|
}, [
|
|
contentTypeUid,
|
|
userPermissions
|
|
]);
|
|
const { isLoading, allowedActions } = useRBAC(contentTypePermissions, permissions ?? undefined, // TODO: useRBAC context should be typed and built differently
|
|
// We are passing raw query as context to the hook so that it can
|
|
// rely on the locale provided from DocumentRBAC for its permission calculations.
|
|
rawQuery);
|
|
const canCreateFields = !isLoading && allowedActions.canCreate ? extractAndDedupeFields(contentTypePermissions.create) : [];
|
|
const canReadFields = !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];
|
|
const canUpdateFields = !isLoading && allowedActions.canUpdate ? extractAndDedupeFields(contentTypePermissions.update) : [];
|
|
/**
|
|
* @description Checks if the user can perform an action on a field based on the field names
|
|
* provided as the second argument.
|
|
*/ const canUserAction = React.useCallback((fieldName, fieldsUserCanAction, fieldType)=>{
|
|
const name = removeNumericalStrings(fieldName.split('.'));
|
|
const componentFieldNames = fieldsUserCanAction// filter out fields that aren't components (components are dot separated)
|
|
.filter((field)=>field.split('.').length > 1);
|
|
if (fieldType === 'component') {
|
|
// check if the field name is within any of those arrays
|
|
return componentFieldNames.some((field)=>{
|
|
return field.includes(name.join('.'));
|
|
});
|
|
}
|
|
/**
|
|
* The field is within a component.
|
|
*/ if (name.length > 1) {
|
|
return componentFieldNames.includes(name.join('.'));
|
|
}
|
|
/**
|
|
* just a regular field
|
|
*/ return fieldsUserCanAction.includes(fieldName);
|
|
}, []);
|
|
if (isLoading) {
|
|
return /*#__PURE__*/ jsx(Page.Loading, {});
|
|
}
|
|
return /*#__PURE__*/ jsx(DocumentRBACProvider, {
|
|
isLoading: isLoading,
|
|
canCreateFields: canCreateFields,
|
|
canReadFields: canReadFields,
|
|
canUpdateFields: canUpdateFields,
|
|
canUserAction: canUserAction,
|
|
...allowedActions,
|
|
children: children
|
|
});
|
|
};
|
|
/**
|
|
* @internal it's really small, but it's used three times in a row and DRY for something this straight forward.
|
|
*/ const extractAndDedupeFields = (permissions = [])=>permissions.flatMap((permission)=>permission.properties?.fields).filter((field, index, arr)=>arr.indexOf(field) === index && typeof field === 'string');
|
|
/**
|
|
* @internal removes numerical strings from arrays.
|
|
* @example
|
|
* ```ts
|
|
* const name = 'a.0.b';
|
|
* const res = removeNumericalStrings(name.split('.'));
|
|
* console.log(res); // ['a', 'b']
|
|
* ```
|
|
*/ const removeNumericalStrings = (arr)=>arr.filter((item)=>isNaN(Number(item)));
|
|
|
|
export { DocumentRBAC, useDocumentRBAC };
|
|
//# sourceMappingURL=DocumentRBAC.mjs.map
|