mirror of
https://github.com/strapi/strapi.git
synced 2025-09-26 17:00:55 +00:00
feat: partially update rbac permissions
This commit is contained in:
parent
04fc8a0875
commit
e8105938c7
@ -146,7 +146,7 @@ module.exports = {
|
|||||||
return ctx.notFound('role.notFound');
|
return ctx.notFound('role.notFound');
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissions = await roleService.assignPermissions(role.id, input.permissions);
|
const permissions = await roleService.updatePermissions(role.id, input.permissions);
|
||||||
|
|
||||||
const sanitizedPermissions = permissions.map(permissionService.sanitizePermission);
|
const sanitizedPermissions = permissions.map(permissionService.sanitizePermission);
|
||||||
|
|
||||||
|
@ -182,6 +182,7 @@ const cleanPermissionsInDatabase = async () => {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createMany,
|
createMany,
|
||||||
|
update,
|
||||||
findMany,
|
findMany,
|
||||||
deleteByRolesIds,
|
deleteByRolesIds,
|
||||||
deleteByIds,
|
deleteByIds,
|
||||||
|
@ -1,11 +1,22 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const { set, omit, pick, prop, isArray, differenceWith, differenceBy } = require('lodash/fp');
|
const {
|
||||||
|
set,
|
||||||
|
omit,
|
||||||
|
pick,
|
||||||
|
prop,
|
||||||
|
isArray,
|
||||||
|
differenceWith,
|
||||||
|
differenceBy,
|
||||||
|
flow,
|
||||||
|
map,
|
||||||
|
} = require('lodash/fp');
|
||||||
const deepEqual = require('fast-deep-equal');
|
const deepEqual = require('fast-deep-equal');
|
||||||
const {
|
const {
|
||||||
generateTimestampCode,
|
generateTimestampCode,
|
||||||
stringIncludes,
|
stringIncludes,
|
||||||
|
mapAsync,
|
||||||
hooks: { createAsyncSeriesWaterfallHook },
|
hooks: { createAsyncSeriesWaterfallHook },
|
||||||
} = require('@strapi/utils');
|
} = require('@strapi/utils');
|
||||||
const { ApplicationError } = require('@strapi/utils').errors;
|
const { ApplicationError } = require('@strapi/utils').errors;
|
||||||
@ -315,6 +326,78 @@ const displayWarningIfNoSuperAdmin = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partially update a role permissions in database.
|
||||||
|
* Permissions will use the format of:
|
||||||
|
* {
|
||||||
|
* connect: [permission1, permission2],
|
||||||
|
* disconnect: [permission3, permission4]
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param {*} roleId
|
||||||
|
* @param {*} permissions
|
||||||
|
*/
|
||||||
|
const partialAssignPermissions = async (roleId, permissions = {}) => {
|
||||||
|
const superAdmin = await getService('role').getSuperAdmin();
|
||||||
|
const isSuperAdmin = superAdmin && superAdmin.id === roleId;
|
||||||
|
const permissionsWithRole = flow(
|
||||||
|
map(set('role', roleId)), // Assign role
|
||||||
|
map(permissionDomain.create) // Map permission to domain
|
||||||
|
);
|
||||||
|
|
||||||
|
const connect = permissionsWithRole(permissions.connect) || [];
|
||||||
|
const disconnect = permissionsWithRole(permissions.disconnect) || [];
|
||||||
|
|
||||||
|
await validatePermissionsExist(connect);
|
||||||
|
|
||||||
|
const permissionsToCreate = connect.filter((permission) => !permission.id);
|
||||||
|
const permissionsToUpdate = connect.filter((permission) => permission.id);
|
||||||
|
const permissionsToDelete = disconnect;
|
||||||
|
|
||||||
|
const existingPermissions = await getService('permission').findMany({
|
||||||
|
where: { role: { id: roleId } },
|
||||||
|
populate: ['role'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find permissions that do not exist in db
|
||||||
|
const invalidUpdatePermissions = differenceBy('id', permissionsToUpdate, existingPermissions);
|
||||||
|
const invalidDeletePermissions = differenceBy('id', permissionsToDelete, existingPermissions);
|
||||||
|
|
||||||
|
if (invalidUpdatePermissions.length !== 0) {
|
||||||
|
throw new ApplicationError('Some permissions to update do not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalidDeletePermissions.length !== 0) {
|
||||||
|
throw new ApplicationError('Some permissions to delete do not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array of final permissions to return
|
||||||
|
const permissionsToReturn = differenceBy('id', existingPermissions, [
|
||||||
|
...permissionsToDelete,
|
||||||
|
...permissionsToUpdate,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (permissionsToDelete.length > 0) {
|
||||||
|
await getService('permission').deleteByIds(permissionsToDelete.map(prop('id')));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissionsToCreate.length > 0) {
|
||||||
|
const newPermissions = await addPermissions(roleId, permissionsToCreate);
|
||||||
|
permissionsToReturn.push(...newPermissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissionsToUpdate.length > 0) {
|
||||||
|
const updatedPermissions = await updatePermissions(roleId, permissionsToUpdate);
|
||||||
|
permissionsToReturn.push(...updatedPermissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSuperAdmin && (connect.length || disconnect.length)) {
|
||||||
|
await getService('metrics').sendDidUpdateRolePermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissionsToReturn;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign permissions to a role
|
* Assign permissions to a role
|
||||||
* @param {string|int} roleId - role ID
|
* @param {string|int} roleId - role ID
|
||||||
@ -368,7 +451,6 @@ const assignPermissions = async (roleId, permissions = []) => {
|
|||||||
return permissionsToReturn;
|
return permissionsToReturn;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const addPermissions = async (roleId, permissions) => {
|
const addPermissions = async (roleId, permissions) => {
|
||||||
const { conditionProvider, createMany } = getService('permission');
|
const { conditionProvider, createMany } = getService('permission');
|
||||||
const { sanitizeConditions } = permissionDomain;
|
const { sanitizeConditions } = permissionDomain;
|
||||||
@ -381,6 +463,21 @@ const addPermissions = async (roleId, permissions) => {
|
|||||||
return createMany(permissionsWithRole);
|
return createMany(permissionsWithRole);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updatePermissions = async (roleId, permissions) => {
|
||||||
|
// TODO: Update many
|
||||||
|
const { conditionProvider, update } = getService('permission');
|
||||||
|
const { sanitizeConditions } = permissionDomain;
|
||||||
|
|
||||||
|
const permissionsWithRole = permissions
|
||||||
|
.map(set('role', roleId))
|
||||||
|
.map(sanitizeConditions(conditionProvider))
|
||||||
|
.map(permissionDomain.create);
|
||||||
|
|
||||||
|
return mapAsync(permissionsWithRole, (permission) => {
|
||||||
|
const { id, ...attributes } = permission;
|
||||||
|
return update({ id }, attributes);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const isContentTypeAction = (action) => action.section === CONTENT_TYPE_SECTION;
|
const isContentTypeAction = (action) => action.section === CONTENT_TYPE_SECTION;
|
||||||
|
|
||||||
@ -457,6 +554,7 @@ module.exports = {
|
|||||||
displayWarningIfNoSuperAdmin,
|
displayWarningIfNoSuperAdmin,
|
||||||
addPermissions,
|
addPermissions,
|
||||||
hasSuperAdminRole,
|
hasSuperAdminRole,
|
||||||
|
updatePermissions: partialAssignPermissions,
|
||||||
assignPermissions,
|
assignPermissions,
|
||||||
resetSuperAdminPermissions,
|
resetSuperAdminPermissions,
|
||||||
checkRolesIdForDeletion,
|
checkRolesIdForDeletion,
|
||||||
|
@ -89,31 +89,20 @@ const fieldsPropertyValidation = (action) =>
|
|||||||
checkNilFields(action)
|
checkNilFields(action)
|
||||||
);
|
);
|
||||||
|
|
||||||
const updatePermissions = yup
|
const permission = yup
|
||||||
.object()
|
|
||||||
.shape({
|
|
||||||
permissions: yup
|
|
||||||
.array()
|
|
||||||
.required()
|
|
||||||
.of(
|
|
||||||
yup
|
|
||||||
.object()
|
.object()
|
||||||
.shape({
|
.shape({
|
||||||
action: yup
|
action: yup
|
||||||
.string()
|
.string()
|
||||||
.required()
|
.required()
|
||||||
.test(
|
.test('action-validity', 'action is not an existing permission action', function (actionId) {
|
||||||
'action-validity',
|
|
||||||
'action is not an existing permission action',
|
|
||||||
function (actionId) {
|
|
||||||
// If the action field is Nil, ignore the test and let the required check handle the error
|
// If the action field is Nil, ignore the test and let the required check handle the error
|
||||||
if (isNil(actionId)) {
|
if (isNil(actionId)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!getActionFromProvider(actionId);
|
return !!getActionFromProvider(actionId);
|
||||||
}
|
}),
|
||||||
),
|
|
||||||
subject: yup
|
subject: yup
|
||||||
.string()
|
.string()
|
||||||
.nullable()
|
.nullable()
|
||||||
@ -136,10 +125,7 @@ const updatePermissions = yup
|
|||||||
}),
|
}),
|
||||||
properties: yup
|
properties: yup
|
||||||
.object()
|
.object()
|
||||||
.test(
|
.test('properties-structure', 'Invalid property set at ${path}', function (properties) {
|
||||||
'properties-structure',
|
|
||||||
'Invalid property set at ${path}',
|
|
||||||
function (properties) {
|
|
||||||
const action = getActionFromProvider(this.options.parent.action);
|
const action = getActionFromProvider(this.options.parent.action);
|
||||||
const hasNoProperties = isEmpty(properties) || isNil(properties);
|
const hasNoProperties = isEmpty(properties) || isNil(properties);
|
||||||
|
|
||||||
@ -157,11 +143,8 @@ const updatePermissions = yup
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.keys(properties).every((property) =>
|
return Object.keys(properties).every((property) => applyToProperties.includes(property));
|
||||||
applyToProperties.includes(property)
|
})
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.test(
|
.test(
|
||||||
'fields-property',
|
'fields-property',
|
||||||
'Invalid fields property at ${path}',
|
'Invalid fields property at ${path}',
|
||||||
@ -193,8 +176,15 @@ const updatePermissions = yup
|
|||||||
),
|
),
|
||||||
conditions: yup.array().of(yup.string()),
|
conditions: yup.array().of(yup.string()),
|
||||||
})
|
})
|
||||||
.noUnknown()
|
.noUnknown();
|
||||||
)
|
|
||||||
|
const updatePermissions = yup
|
||||||
|
.object()
|
||||||
|
.shape({
|
||||||
|
permissions: yup
|
||||||
|
.array()
|
||||||
|
.required()
|
||||||
|
.of(permission)
|
||||||
.test(
|
.test(
|
||||||
'duplicated-permissions',
|
'duplicated-permissions',
|
||||||
'Some permissions are duplicated (same action and subject)',
|
'Some permissions are duplicated (same action and subject)',
|
||||||
@ -213,5 +203,6 @@ module.exports = {
|
|||||||
roles,
|
roles,
|
||||||
isAPluginName,
|
isAPluginName,
|
||||||
arrayOfConditionNames,
|
arrayOfConditionNames,
|
||||||
|
permission,
|
||||||
updatePermissions,
|
updatePermissions,
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,12 @@ const { yup, validateYupSchema } = require('@strapi/utils');
|
|||||||
const { getService } = require('../utils');
|
const { getService } = require('../utils');
|
||||||
const validators = require('./common-validators');
|
const validators = require('./common-validators');
|
||||||
|
|
||||||
|
const updatePermissions = yup.object().shape({
|
||||||
|
// TODO: Add id
|
||||||
|
connect: yup.array().of(validators.permission),
|
||||||
|
disconnect: yup.array().of(yup.object().shape({ id: yup.strapiID().required() })),
|
||||||
|
});
|
||||||
|
|
||||||
const checkPermissionsSchema = yup.object().shape({
|
const checkPermissionsSchema = yup.object().shape({
|
||||||
permissions: yup.array().of(
|
permissions: yup.array().of(
|
||||||
yup
|
yup
|
||||||
@ -50,7 +56,7 @@ const actionsExistSchema = yup
|
|||||||
// exports
|
// exports
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
validatedUpdatePermissionsInput: validateYupSchema(validators.updatePermissions),
|
validatedUpdatePermissionsInput: validateYupSchema(updatePermissions),
|
||||||
validatePermissionsExist: validateYupSchema(actionsExistSchema),
|
validatePermissionsExist: validateYupSchema(actionsExistSchema),
|
||||||
validateCheckPermissionsInput: validateYupSchema(checkPermissionsSchema),
|
validateCheckPermissionsInput: validateYupSchema(checkPermissionsSchema),
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user