diff --git a/packages/strapi-admin/domain/action.js b/packages/strapi-admin/domain/action.js index 8e38c7346c..bb59b2f55b 100644 --- a/packages/strapi-admin/domain/action.js +++ b/packages/strapi-admin/domain/action.js @@ -10,8 +10,15 @@ const actionFields = [ 'pluginName', 'subjects', 'conditions', + 'options', ]; +const defaultAction = { + options: { + fieldsGranularity: true, + }, +}; + /** * Return a prefixed id that depends on the pluginName * @param {Object} params @@ -19,15 +26,13 @@ const actionFields = [ * @param {Object} params.uid - uid defined by the developer */ const getActionId = ({ pluginName, uid }) => { - let id = ''; if (pluginName === 'admin') { - id = `admin::${uid}`; + return `admin::${uid}`; } else if (pluginName) { - id = `plugins::${pluginName}.${uid}`; - } else { - id = `application::${uid}`; + return `plugins::${pluginName}.${uid}`; } - return id; + + return `application::${uid}`; }; /** @@ -42,7 +47,7 @@ function createAction(attributes) { action.subCategory = attributes.subCategory || 'general'; } - return action; + return _.merge(action, defaultAction); } module.exports = { diff --git a/packages/strapi-admin/services/__tests__/action-provider.test.js b/packages/strapi-admin/services/__tests__/action-provider.test.js index 45cd6a66af..69c84749a8 100644 --- a/packages/strapi-admin/services/__tests__/action-provider.test.js +++ b/packages/strapi-admin/services/__tests__/action-provider.test.js @@ -1,10 +1,9 @@ 'use strict'; const _ = require('lodash'); +const domain = require('../../domain/action'); const actionProviderService = require('../permission/action-provider'); describe('Action Provider Service', () => { - const createdActions = []; - beforeEach(() => { global.strapi = { plugins: { aPlugin: {} }, @@ -37,8 +36,6 @@ describe('Action Provider Service', () => { ..._.omit(readAction, ['uid']), actionId: 'admin::marketplace.read', }); - - createdActions.push(createdAction); }); test('Can register a settings action without subCategory', async () => { @@ -50,7 +47,6 @@ describe('Action Provider Service', () => { actionId: 'admin::marketplace.create', subCategory: 'general', }); - createdActions.push(createdAction); }); test('Can get all registered entries (array)', () => { @@ -61,6 +57,12 @@ describe('Action Provider Service', () => { expect(actionProviderService.getAllByMap().size).toBe(2); }); + test('Can get an action by its actionId', () => { + const actionId = 'admin::marketplace.create'; + const expected = domain.createAction(createAction); + expect(actionProviderService.getByActionId(actionId)).toStrictEqual(expected); + }); + test('Can register a settings action with a pluginName other than "admin"', async () => { const action = { uid: 'marketplace.update', diff --git a/packages/strapi-admin/services/content-type.js b/packages/strapi-admin/services/content-type.js index c202b72ccd..de71ad3284 100644 --- a/packages/strapi-admin/services/content-type.js +++ b/packages/strapi-admin/services/content-type.js @@ -110,24 +110,20 @@ const getNestedFieldsWithIntermediate = ( * @param {array} actions array of actions * @param {object} options * @param {number} options.nestingLevel level of nesting - * @param {array} options.fieldsNullFor actionIds where the fields should be null * @param {array} options.restrictedSubjects subjectsId to ignore * @returns {array} */ -const getPermissionsWithNestedFields = ( - actions, - { nestingLevel, fieldsNullFor = [], restrictedSubjects = [] } = {} -) => +const getPermissionsWithNestedFields = (actions, { nestingLevel, restrictedSubjects = [] } = {}) => actions.reduce((perms, action) => { action.subjects .filter(subject => !restrictedSubjects.includes(subject)) .forEach(contentTypeUid => { - const fields = fieldsNullFor.includes(action.actionId) - ? null - : getNestedFields(strapi.contentTypes[contentTypeUid], { + const fields = action.options.fieldsGranularity + ? getNestedFields(strapi.contentTypes[contentTypeUid], { components: strapi.components, nestingLevel, - }); + }) + : null; perms.push({ action: action.actionId, subject: contentTypeUid, @@ -143,28 +139,30 @@ const getPermissionsWithNestedFields = ( * @param {object} permissions array of existing permissions in db * @param {object} options * @param {number} options.nestingLevel level of nesting - * @param {array} options.fieldsNullFor actionIds where the fields should be null * @returns {array} */ -const cleanPermissionFields = (permissions, { nestingLevel, fieldsNullFor = [] }) => +const cleanPermissionFields = (permissions, { nestingLevel }) => permissions.map(perm => { - let newFields = perm.fields; - if (fieldsNullFor.includes(perm.action)) { + const { action: actionId, fields, subject } = perm; + const action = strapi.admin.services.permission.actionProvider.getByActionId(actionId); + let newFields = fields; + + if (!action.options.fieldsGranularity) { newFields = null; - } else if (perm.subject && strapi.contentTypes[perm.subject]) { - const possiblefields = getNestedFieldsWithIntermediate(strapi.contentTypes[perm.subject], { + } else if (subject && strapi.contentTypes[subject]) { + const possibleFields = getNestedFieldsWithIntermediate(strapi.contentTypes[subject], { components: strapi.components, nestingLevel, }); - const requiredFields = getNestedFields(strapi.contentTypes[perm.subject], { + const requiredFields = getNestedFields(strapi.contentTypes[subject], { components: strapi.components, requiredOnly: true, nestingLevel, - existingFields: perm.fields, + existingFields: fields, }); const badNestedFields = _.uniq([ - ..._.intersection(perm.fields, possiblefields), + ..._.intersection(fields, possibleFields), ...requiredFields, ]); newFields = badNestedFields.filter( diff --git a/packages/strapi-admin/services/permission.js b/packages/strapi-admin/services/permission.js index 11e54d827a..ae2875c4f2 100644 --- a/packages/strapi-admin/services/permission.js +++ b/packages/strapi-admin/services/permission.js @@ -28,7 +28,7 @@ const arePermissionsEqual = (perm1, perm2) => /** * Removes unwanted fields from a permission - * @param permission + * @param perm * @returns {*} */ const sanitizePermission = perm => ({ @@ -157,10 +157,7 @@ const cleanPermissionInDatabase = async () => { // Second, clean fields of permissions (add required ones, remove the non-existing anymore ones) const permissionsInDb = dbPermissions.filter(perm => !permissionsToRemoveIds.includes(perm.id)); const permissionsWithCleanFields = strapi.admin.services['content-type'].cleanPermissionFields( - permissionsInDb, - { - fieldsNullFor: ['plugins::content-manager.explorer.delete'], - } + permissionsInDb ); // Update only the ones that need to be updated @@ -197,10 +194,7 @@ const resetSuperAdminPermissions = async () => { const contentTypesActions = allActions.filter(a => a.section === 'contentTypes'); const permissions = strapi.admin.services['content-type'].getPermissionsWithNestedFields( - contentTypesActions, - { - fieldsNullFor: ['plugins::content-manager.explorer.delete'], - } + contentTypesActions ); const otherActions = allActions.filter(a => a.section !== 'contentTypes'); diff --git a/packages/strapi-admin/services/permission/action-provider.js b/packages/strapi-admin/services/permission/action-provider.js index 8ac8ece211..4edab4a314 100644 --- a/packages/strapi-admin/services/permission/action-provider.js +++ b/packages/strapi-admin/services/permission/action-provider.js @@ -16,8 +16,16 @@ const createActionProvider = () => { */ get(uid, pluginName) { const actionId = getActionId({ pluginName, uid }); - const action = actions.get(actionId); - return action; + return actions.get(actionId); + }, + + /** + * Get an action by its actionId + * @param {string} actionId + * @returns {Action} + */ + getByActionId(actionId) { + return actions.get(actionId); }, /** diff --git a/packages/strapi-admin/validation/action-provider.js b/packages/strapi-admin/validation/action-provider.js index a36906a7cb..cc0c016b13 100644 --- a/packages/strapi-admin/validation/action-provider.js +++ b/packages/strapi-admin/validation/action-provider.js @@ -32,7 +32,7 @@ const registerProviderActionSchema = yup then: yup .array() .of(yup.string()) - .required(), + .requiredAllowEmpty(), otherwise: yup .mixed() .oneOf([undefined], 'subjects should only be defined for the "contentTypes" section'), @@ -62,6 +62,9 @@ const registerProviderActionSchema = yup } ), }), + options: yup.object({ + fieldsGranularity: yup.boolean(), + }), }) .noUnknown() ); diff --git a/packages/strapi-plugin-content-manager/config/functions/bootstrap.js b/packages/strapi-plugin-content-manager/config/functions/bootstrap.js index cd01c6b37d..0d850fe419 100644 --- a/packages/strapi-plugin-content-manager/config/functions/bootstrap.js +++ b/packages/strapi-plugin-content-manager/config/functions/bootstrap.js @@ -94,6 +94,8 @@ const registerPermissions = () => { 'content-manager' ].services.contenttypes.getDisplayedContentTypesUids(); + const hasDraftAndPublish = uid => strapi.contentTypes[uid].options.draftAndPublish; + const actions = [ { section: 'contentTypes', @@ -122,6 +124,19 @@ const registerPermissions = () => { uid: 'explorer.delete', pluginName: 'content-manager', subjects: contentTypesUids, + options: { + fieldsGranularity: false, + }, + }, + { + section: 'contentTypes', + displayName: 'Publish', + uid: 'explorer.publish', + pluginName: 'content-manager', + subjects: contentTypesUids.filter(hasDraftAndPublish), + options: { + fieldsGranularity: false, + }, }, { section: 'plugins',