Ensure bound permissions in database (#8180)

* Ensure bound permissions in database

Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu>

* Refactor, handle missing fields & fix e2e tests

Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu>
This commit is contained in:
Jean-Sébastien Herbaux 2020-10-05 16:26:55 +02:00 committed by GitHub
parent ded8bc37bb
commit eb5a94501e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 115 additions and 32 deletions

View File

@ -17,6 +17,7 @@ module.exports = async () => {
registerAdminConditions();
registerPermissionActions();
await strapi.admin.services.permission.cleanPermissionInDatabase();
await strapi.admin.services.permission.ensureBoundPermissionsInDatabase();
await strapi.admin.services.user.migrateUsers();
await strapi.admin.services.role.createRolesIfNoneExist();
await strapi.admin.services.permission.resetSuperAdminPermissions();

View File

@ -0,0 +1,29 @@
'use strict';
const {
contentTypes: { hasDraftAndPublish },
} = require('strapi-utils');
const {
AUTHOR_CODE,
PUBLISH_ACTION,
DELETE_ACTION,
UPDATE_ACTION,
CREATE_ACTION,
READ_ACTION,
} = require('../services/constants');
const BOUND_ACTIONS = [READ_ACTION, CREATE_ACTION, UPDATE_ACTION, DELETE_ACTION, PUBLISH_ACTION];
const BOUND_ACTIONS_FOR_FIELDS = [READ_ACTION, CREATE_ACTION, UPDATE_ACTION];
const getBoundActionsBySubject = (role, subject) => {
const model = strapi.getModel(subject);
if (role.code === AUTHOR_CODE || !hasDraftAndPublish(model)) {
return [READ_ACTION, UPDATE_ACTION, CREATE_ACTION, DELETE_ACTION];
}
return BOUND_ACTIONS;
};
module.exports = { getBoundActionsBySubject, BOUND_ACTIONS, BOUND_ACTIONS_FOR_FIELDS };

View File

@ -2,4 +2,9 @@ module.exports = {
SUPER_ADMIN_CODE: 'strapi-super-admin',
EDITOR_CODE: 'strapi-editor',
AUTHOR_CODE: 'strapi-author',
READ_ACTION: 'plugins::content-manager.explorer.read',
CREATE_ACTION: 'plugins::content-manager.explorer.create',
UPDATE_ACTION: 'plugins::content-manager.explorer.update',
DELETE_ACTION: 'plugins::content-manager.explorer.delete',
PUBLISH_ACTION: 'plugins::content-manager.explorer.publish',
};

View File

@ -1,13 +1,16 @@
'use strict';
const _ = require('lodash');
const { flatMap, filter } = require('lodash/fp');
const pmap = require('p-map');
const { createPermission } = require('../domain/permission');
const { validatePermissionsExist } = require('../validation/permission');
const createPermissionsManager = require('./permission/permissions-manager');
const createConditionProvider = require('./permission/condition-provider');
const createPermissionEngine = require('./permission/engine');
const actionProvider = require('./permission/action-provider');
const { EDITOR_CODE } = require('./constants');
const { getBoundActionsBySubject, BOUND_ACTIONS_FOR_FIELDS } = require('../domain/role');
const { createPermission } = require('../domain/permission');
const conditionProvider = createConditionProvider();
const engine = createPermissionEngine(conditionProvider);
@ -54,6 +57,25 @@ const deleteByIds = ids => {
return strapi.query('permission', 'admin').delete({ id_in: ids });
};
/**
* Create many permissions
* @param permissions
* @returns {Promise<*[]|*>}
*/
const createMany = async permissions => {
return strapi.query('permission', 'admin').createMany(permissions);
};
/**
* Update a permission
* @returns {Promise<*[]|*>}
* @param params
* @param attributes
*/
const update = async (params, attributes) => {
return strapi.query('permission', 'admin').update(params, attributes);
};
/**
* Find assigned permissions in the database
* @param params query params to find the permissions
@ -100,9 +122,7 @@ const assign = async (roleId, permissions = []) => {
await deleteByIds(permissionsToDelete.map(p => p.id));
}
if (permissionsToAdd.length > 0) {
const createdPermissions = await strapi
.query('permission', 'admin')
.createMany(permissionsToAdd);
const createdPermissions = await createMany(permissionsToAdd);
permissionsToReturn.push(...createdPermissions.map(p => ({ ...p, role: p.role.id })));
}
@ -166,8 +186,7 @@ const cleanPermissionInDatabase = async () => {
permissionsInDb,
(a, b) => a.id === b.id && _.xor(a.fields, b.fields).length === 0
);
const promiseProvider = perm =>
strapi.query('permission', 'admin').update({ id: perm.id }, perm);
const promiseProvider = perm => update({ id: perm.id }, perm);
//Update the database
await Promise.all([
@ -180,6 +199,53 @@ const cleanPermissionInDatabase = async () => {
}
};
const ensureBoundPermissionsInDatabase = async () => {
if (strapi.EE) {
return;
}
const contentTypes = Object.values(strapi.contentTypes);
const editorRole = await strapi.query('role', 'admin').findOne({ code: EDITOR_CODE }, []);
if (_.isNil(editorRole)) {
return;
}
for (const contentType of contentTypes) {
const boundActions = getBoundActionsBySubject(editorRole, contentType.uid);
const permissions = await strapi.query('permission', 'admin').find(
{
subject: contentType.uid,
action_in: boundActions,
role: editorRole.id,
},
[]
);
if (permissions.length === 0) {
return;
}
const fields = _.flow(flatMap('fields'), filter(_.negate(_.isNil)), _.uniq)(permissions);
// Handle the scenario where permissions are missing
const missingActions = _.difference(boundActions, _.map(permissions, 'action'));
if (missingActions.length > 0) {
const permissions = missingActions.map(action =>
createPermission({
action,
subject: contentType.uid,
role: editorRole.id,
fields: BOUND_ACTIONS_FOR_FIELDS.includes(action) ? fields : null,
})
);
await createMany(permissions);
}
}
};
/**
* Reset super admin permissions (giving it all permissions)
* @returns {Promise<>}
@ -225,4 +291,5 @@ module.exports = {
conditionProvider,
cleanPermissionInDatabase,
resetSuperAdminPermissions,
ensureBoundPermissionsInDatabase,
};

View File

@ -1,38 +1,19 @@
'use strict';
const _ = require('lodash');
const {
yup,
formatYupErrors,
contentTypes: { hasDraftAndPublish },
} = require('strapi-utils');
const { yup, formatYupErrors } = require('strapi-utils');
const validators = require('./common-validators');
const { AUTHOR_CODE } = require('../services/constants');
const { AUTHOR_CODE, PUBLISH_ACTION } = require('../services/constants');
const {
BOUND_ACTIONS_FOR_FIELDS,
BOUND_ACTIONS,
getBoundActionsBySubject,
} = require('../domain/role');
const handleReject = error => Promise.reject(formatYupErrors(error));
// validatedUpdatePermissionsInput
const READ_ACTION = 'plugins::content-manager.explorer.read';
const CREATE_ACTION = 'plugins::content-manager.explorer.create';
const UPDATE_ACTION = 'plugins::content-manager.explorer.update';
const DELETE_ACTION = 'plugins::content-manager.explorer.delete';
const PUBLISH_ACTION = 'plugins::content-manager.explorer.publish';
const BOUND_ACTIONS = [READ_ACTION, CREATE_ACTION, UPDATE_ACTION, DELETE_ACTION, PUBLISH_ACTION];
const BOUND_ACTIONS_FOR_FIELDS = [READ_ACTION, CREATE_ACTION, UPDATE_ACTION];
const getBoundActionsBySubject = (role, subject) => {
const model = strapi.getModel(subject);
if (role.code === AUTHOR_CODE || !hasDraftAndPublish(model)) {
return [READ_ACTION, UPDATE_ACTION, CREATE_ACTION, DELETE_ACTION];
}
return BOUND_ACTIONS;
};
const actionFieldsAreEqual = (a, b) => {
const aFields = a.fields || [];
const bFields = b.fields || [];