mirror of
https://github.com/strapi/strapi.git
synced 2025-08-22 07:38:41 +00:00
258 lines
7.2 KiB
JavaScript
258 lines
7.2 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
flatMap,
|
|
reject,
|
|
isNil,
|
|
isArray,
|
|
prop,
|
|
xor,
|
|
eq,
|
|
uniq,
|
|
map,
|
|
difference,
|
|
differenceWith,
|
|
pipe,
|
|
} = require('lodash/fp');
|
|
const pmap = require('p-map');
|
|
const { EDITOR_CODE } = require('../constants');
|
|
const { getBoundActionsBySubject, BOUND_ACTIONS_FOR_FIELDS } = require('../../domain/role');
|
|
const { getService } = require('../../utils');
|
|
const permissionDomain = require('../../domain/permission/index');
|
|
|
|
/**
|
|
* Delete permissions of roles in database
|
|
* @param rolesIds ids of roles
|
|
* @returns {Promise<array>}
|
|
*/
|
|
const deleteByRolesIds = async rolesIds => {
|
|
// FIXME: need to delete associations in delete many
|
|
await strapi.query('strapi::permission').deleteMany({
|
|
where: {
|
|
role: { id: rolesIds },
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Delete permissions
|
|
* @param ids ids of permissions
|
|
* @returns {Promise<array>}
|
|
*/
|
|
const deleteByIds = async ids => {
|
|
for (const id of ids) {
|
|
await strapi.query('strapi::permission').delete({ where: { id } });
|
|
}
|
|
|
|
// FIXME: find a way to do delete many with auto association deletes (FKs should do the job)$
|
|
// await strapi.query('strapi::permission').deleteMany({ where: { id: ids } });
|
|
};
|
|
|
|
/**
|
|
* Create many permissions
|
|
* @param permissions
|
|
* @returns {Promise<*[]|*>}
|
|
*/
|
|
const createMany = async permissions => {
|
|
const createdPermissions = [];
|
|
for (const permission of permissions) {
|
|
const newPerm = await strapi.query('strapi::permission').create({ data: permission });
|
|
createdPermissions.push(newPerm);
|
|
}
|
|
|
|
return permissionDomain.toPermission(createdPermissions);
|
|
|
|
// FIXME:
|
|
// await strapi.query('strapi::permission').createMany({ data: permissions })
|
|
};
|
|
|
|
/**
|
|
* Update a permission
|
|
* @returns {Promise<*[]|*>}
|
|
* @param params
|
|
* @param attributes
|
|
*/
|
|
const update = async (params, attributes) => {
|
|
const updatedPermission = await strapi
|
|
.query('strapi::permission')
|
|
.update({ where: params, data: attributes });
|
|
|
|
return permissionDomain.toPermission(updatedPermission);
|
|
};
|
|
|
|
/**
|
|
* Find assigned permissions in the database
|
|
* @param params query params to find the permissions
|
|
* @returns {Promise<Permission[]>}
|
|
*/
|
|
const findMany = async (params = {}) => {
|
|
const rawPermissions = await strapi.query('strapi::permission').findMany(params);
|
|
|
|
return permissionDomain.toPermission(rawPermissions);
|
|
};
|
|
|
|
/**
|
|
* Find all permissions for a user
|
|
* @param user - user
|
|
* @returns {Promise<Permission[]>}
|
|
*/
|
|
const findUserPermissions = async user => {
|
|
return findMany({ where: { role: { users: { id: user.id } } } });
|
|
};
|
|
|
|
const filterPermissionsToRemove = async permissions => {
|
|
const { actionProvider } = getService('permission');
|
|
|
|
const permissionsToRemove = [];
|
|
|
|
for (const permission of permissions) {
|
|
const { subjects, options = {} } = actionProvider.get(permission.action) || {};
|
|
const { applyToProperties } = options;
|
|
|
|
const invalidProperties = await Promise.all(
|
|
(applyToProperties || []).map(async property => {
|
|
const applies = await actionProvider.appliesToProperty(
|
|
property,
|
|
permission.action,
|
|
permission.subject
|
|
);
|
|
|
|
return applies && isNil(permissionDomain.getProperty(property, permission));
|
|
})
|
|
);
|
|
|
|
const isRegisteredAction = actionProvider.has(permission.action);
|
|
const hasInvalidProperties = isArray(applyToProperties) && invalidProperties.every(eq(true));
|
|
const isInvalidSubject = isArray(subjects) && !subjects.includes(permission.subject);
|
|
|
|
// If the permission has an invalid action, an invalid subject or invalid properties, then add it to the toBeRemoved collection
|
|
if (!isRegisteredAction || isInvalidSubject || hasInvalidProperties) {
|
|
permissionsToRemove.push(permission);
|
|
}
|
|
}
|
|
|
|
return permissionsToRemove;
|
|
};
|
|
|
|
/**
|
|
* Removes permissions in database that don't exist anymore
|
|
* @returns {Promise<>}
|
|
*/
|
|
const cleanPermissionsInDatabase = async () => {
|
|
const pageSize = 200;
|
|
|
|
const contentTypeService = getService('content-type');
|
|
|
|
const total = await strapi.query('strapi::permission').count();
|
|
const pageCount = Math.ceil(total / pageSize);
|
|
|
|
for (let page = 0; page < pageCount; page++) {
|
|
// 1. Find invalid permissions and collect their ID to delete them later
|
|
const results = await strapi
|
|
.query('strapi::permission')
|
|
.findMany({ limit: pageSize, offset: page * pageSize });
|
|
|
|
const permissions = permissionDomain.toPermission(results);
|
|
const permissionsToRemove = await filterPermissionsToRemove(permissions);
|
|
const permissionsIdToRemove = map(prop('id'), permissionsToRemove);
|
|
|
|
// 2. Clean permissions' fields (add required ones, remove the non-existing ones)
|
|
const remainingPermissions = permissions.filter(
|
|
permission => !permissionsIdToRemove.includes(permission.id)
|
|
);
|
|
|
|
const permissionsWithCleanFields = contentTypeService.cleanPermissionFields(
|
|
remainingPermissions
|
|
);
|
|
|
|
// Update only the ones that need to be updated
|
|
const permissionsNeedingToBeUpdated = differenceWith(
|
|
(a, b) => {
|
|
return a.id === b.id && xor(a.properties.fields, b.properties.fields).length === 0;
|
|
},
|
|
permissionsWithCleanFields,
|
|
remainingPermissions
|
|
);
|
|
|
|
const updatePromiseProvider = permission => {
|
|
return update({ id: permission.id }, permission);
|
|
};
|
|
|
|
// Execute all the queries, update the database
|
|
await Promise.all([
|
|
deleteByIds(permissionsIdToRemove),
|
|
pmap(permissionsNeedingToBeUpdated, updatePromiseProvider, {
|
|
concurrency: 100,
|
|
stopOnError: true,
|
|
}),
|
|
]);
|
|
}
|
|
};
|
|
|
|
const ensureBoundPermissionsInDatabase = async () => {
|
|
if (strapi.EE) {
|
|
return;
|
|
}
|
|
|
|
const contentTypes = Object.values(strapi.contentTypes);
|
|
const editorRole = await strapi.query('strapi::role').findOne({
|
|
where: { code: EDITOR_CODE },
|
|
});
|
|
|
|
if (isNil(editorRole)) {
|
|
return;
|
|
}
|
|
|
|
for (const contentType of contentTypes) {
|
|
const boundActions = getBoundActionsBySubject(editorRole, contentType.uid);
|
|
|
|
const permissions = await findMany({
|
|
where: {
|
|
subject: contentType.uid,
|
|
action: boundActions,
|
|
role: { id: editorRole.id },
|
|
},
|
|
});
|
|
|
|
if (permissions.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const fields = pipe(
|
|
flatMap(permissionDomain.getProperty('fields')),
|
|
reject(isNil),
|
|
uniq
|
|
)(permissions);
|
|
|
|
// Handle the scenario where permissions are missing
|
|
const missingActions = difference(map('action', permissions), boundActions);
|
|
|
|
if (missingActions.length > 0) {
|
|
const permissions = pipe(
|
|
// Create a permission skeleton from the action id
|
|
map(action => ({ action, subject: contentType.uid, role: editorRole.id })),
|
|
// Use the permission domain to create a clean permission from the given object
|
|
map(permissionDomain.create),
|
|
// Adds the fields property if the permission action is eligible
|
|
map(permission =>
|
|
BOUND_ACTIONS_FOR_FIELDS.includes(permission.action)
|
|
? permissionDomain.setProperty('fields', fields, permission)
|
|
: permission
|
|
)
|
|
)(missingActions);
|
|
|
|
await createMany(permissions);
|
|
}
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
createMany,
|
|
findMany,
|
|
deleteByRolesIds,
|
|
deleteByIds,
|
|
findUserPermissions,
|
|
cleanPermissionsInDatabase,
|
|
ensureBoundPermissionsInDatabase,
|
|
};
|