Signed-off-by: Pierre Noël <petersg83@gmail.com>
This commit is contained in:
Pierre Noël 2020-10-19 16:34:02 +02:00
parent 1bc8ae4d0f
commit 907d6c9d33
8 changed files with 287 additions and 270 deletions

View File

@ -20,7 +20,7 @@ module.exports = async () => {
await strapi.admin.services.permission.ensureBoundPermissionsInDatabase();
await strapi.admin.services.user.migrateUsers();
await strapi.admin.services.role.createRolesIfNoneExist();
await strapi.admin.services.permission.resetSuperAdminPermissions();
await strapi.admin.services.role.resetSuperAdminPermissions();
await strapi.admin.services.role.displayWarningIfNoSuperAdmin();
await strapi.admin.services.user.displayWarningIfUsersDontHaveRole();
};

View File

@ -168,11 +168,11 @@ describe('Role controller', () => {
admin: {
services: {
role: {
assignPermissions,
findOne: findOneRole,
getSuperAdmin: jest.fn(() => undefined),
},
permission: {
assign: assignPermissions,
conditionProvider: {
getAll: jest.fn(() => [{ id: 'admin::is-creator' }]),
},

View File

@ -129,7 +129,10 @@ module.exports = {
permissionsToAssign = input.permissions;
}
const permissions = await strapi.admin.services.permission.assign(role.id, permissionsToAssign);
const permissions = await strapi.admin.services.role.assignPermissions(
role.id,
permissionsToAssign
);
ctx.body = {
data: permissions,

View File

@ -96,7 +96,10 @@ module.exports = {
return ctx.notFound('role.notFound');
}
const permissions = await strapi.admin.services.permission.assign(role.id, input.permissions);
const permissions = await strapi.admin.services.role.assignPermissions(
role.id,
input.permissions
);
ctx.body = {
data: permissions,

View File

@ -19,86 +19,6 @@ describe('Permission Service', () => {
});
});
describe('Assign permissions', () => {
test('Delete previous permissions', async () => {
const createMany = jest.fn(() => Promise.resolve([]));
const getSuperAdmin = jest.fn(() => Promise.resolve({ id: 0 }));
const sendDidUpdateRolePermissions = jest.fn();
const find = jest.fn(() => Promise.resolve([{ id: 3 }]));
const deleteFn = jest.fn();
const getAll = jest.fn(() => []);
global.strapi = {
admin: {
services: {
metrics: { sendDidUpdateRolePermissions },
permission: { actionProvider: { getAll } },
role: { getSuperAdmin },
}
},
query() {
return { delete: deleteFn, createMany, find };
},
};
await permissionService.assign(1, []);
expect(deleteFn).toHaveBeenCalledWith({ id_in: [3] });
});
test('Create new permissions', async () => {
const permissions = Array(5)
.fill(0)
.map((v, i) => ({ action: `action-${i}` }));
const deleteFn = jest.fn(() => Promise.resolve([]));
const createMany = jest.fn(() => Promise.resolve([]));
const getSuperAdmin = jest.fn(() => Promise.resolve({ id: 0 }));
const sendDidUpdateRolePermissions = jest.fn();
const find = jest.fn(() => Promise.resolve([]));
const getAll = jest.fn(() => permissions.map(perm => ({ actionId: perm.action })));
const removeUnkownConditionIds = jest.fn(conds => _.intersection(conds, ['cond']));
global.strapi = {
admin: {
services: {
metrics: { sendDidUpdateRolePermissions },
role: { getSuperAdmin },
permission: {
actionProvider: { getAll },
conditionProvider: {
getAll: jest.fn(() => [{ id: 'admin::is-creator' }]),
},
},
condition: {
removeUnkownConditionIds,
},
},
},
query() {
return { delete: deleteFn, createMany, find };
},
};
const permissionsToAssign = [...permissions];
permissionsToAssign[4] = {
...permissions[4],
conditions: ['cond', 'unknown-cond'],
};
await permissionService.assign(1, permissionsToAssign);
expect(createMany).toHaveBeenCalledTimes(1);
expect(createMany).toHaveBeenCalledWith([
{ action: 'action-0', conditions: [], fields: null, role: 1, subject: null },
{ action: 'action-1', conditions: [], fields: null, role: 1, subject: null },
{ action: 'action-2', conditions: [], fields: null, role: 1, subject: null },
{ action: 'action-3', conditions: [], fields: null, role: 1, subject: null },
{ action: 'action-4', conditions: ['cond'], fields: null, role: 1, subject: null },
]);
});
});
describe('Find User Permissions', () => {
test('Find calls the right db query', async () => {
const find = jest.fn(({ role_in }) => role_in);
@ -229,70 +149,4 @@ describe('Permission Service', () => {
permissionService.actionProvider.getAllByMap = prevGetAllByMap;
});
});
describe('resetSuperAdminPermissions', () => {
test('No superAdmin role exist', async () => {
const getSuperAdmin = jest.fn(() => Promise.resolve(undefined));
const createMany = jest.fn();
global.strapi = {
query: () => ({ createMany }),
admin: { services: { role: { getSuperAdmin } } },
};
await permissionService.resetSuperAdminPermissions();
expect(createMany).toHaveBeenCalledTimes(0);
});
test('Reset super admin permissions', async () => {
const actions = [
{
actionId: 'action-1',
subjects: ['country'],
section: 'contentTypes',
},
];
const permissions = [
{
action: 'action-1',
subject: 'country',
fields: ['name'],
conditions: [],
},
];
const getAll = jest.fn(() => actions);
const getAllConditions = jest.fn(() => []);
const find = jest.fn(() => [{ action: 'action-2', id: 2 }]);
const deleteFn = jest.fn(() => []);
const getPermissionsWithNestedFields = jest.fn(() => [...permissions]); // cloned, otherwise it is modified inside resetSuperAdminPermissions()
const getSuperAdmin = jest.fn(() => Promise.resolve({ id: 1 }));
const createMany = jest.fn(() => Promise.resolve([{ ...permissions[0], role: { id: 1 } }]));
const removeUnkownConditionIds = jest.fn(conds => conds);
global.strapi = {
query: () => ({ createMany, find, delete: deleteFn }),
admin: {
services: {
permission: {
actionProvider: { getAll },
conditionProvider: { getAll: getAllConditions },
},
condition: { removeUnkownConditionIds },
'content-type': { getPermissionsWithNestedFields },
role: { getSuperAdmin },
},
},
};
await permissionService.resetSuperAdminPermissions();
expect(deleteFn).toHaveBeenCalledWith({ id_in: [2] });
expect(createMany).toHaveBeenCalledWith([
{
...permissions[0],
role: 1,
},
]);
});
});
});

View File

@ -275,7 +275,6 @@ describe('Role', () => {
});
});
});
describe('Count roles', () => {
test('Count roles without params', async () => {
const count = jest.fn(() => Promise.resolve(2));
@ -302,7 +301,6 @@ describe('Role', () => {
expect(count).toHaveBeenCalledWith(params);
});
});
describe('createRolesIfNoneExist', () => {
test("Don't create roles if one already exist", async () => {
const count = jest.fn(() => Promise.resolve(1));
@ -405,21 +403,26 @@ describe('Role', () => {
restrictedSubjects: ['plugins::users-permissions.user'],
});
expect(createMany).toHaveBeenCalledTimes(2);
expect(createMany).toHaveBeenNthCalledWith(1, 2, [
...permissions,
...defaultPermissions.map(d => ({
...d,
conditions: [],
})),
]);
expect(createMany).toHaveBeenNthCalledWith(
1,
[
...permissions,
...defaultPermissions.map(d => ({
...d,
conditions: [],
})),
].map(p => ({ ...p, role: 2 }))
);
expect(createMany).toHaveBeenNthCalledWith(2, 3, [
{ ...permissions[0], conditions: ['admin::is-creator'] },
...defaultPermissions,
]);
expect(createMany).toHaveBeenNthCalledWith(
2,
[
{ ...permissions[0], conditions: ['admin::is-creator'] },
...defaultPermissions,
].map(p => ({ ...p, role: 3 }))
);
});
});
describe('displayWarningIfNoSuperAdmin', () => {
test('superAdmin role exists & a user is superAdmin', async () => {
const findOne = jest.fn(() => ({ id: 1 }));
@ -470,4 +473,145 @@ describe('Role', () => {
expect(warn).toHaveBeenCalledWith("Your application doesn't have a super admin user.");
});
});
describe('resetSuperAdminPermissions', () => {
test('No superAdmin role exist', async () => {
const getSuperAdmin = jest.fn(() => Promise.resolve(undefined));
const createMany = jest.fn();
global.strapi = {
query: () => ({ createMany }),
admin: { services: { role: { getSuperAdmin } } },
};
await roleService.resetSuperAdminPermissions();
expect(createMany).toHaveBeenCalledTimes(0);
});
test('Reset super admin permissions', async () => {
const actions = [
{
actionId: 'action-1',
subjects: ['country'],
section: 'contentTypes',
},
];
const permissions = [
{
action: 'action-1',
subject: 'country',
fields: ['name'],
conditions: [],
},
];
const getAll = jest.fn(() => actions);
const getAllConditions = jest.fn(() => []);
const find = jest.fn(() => [{ action: 'action-2', id: 2 }]);
const getPermissionsWithNestedFields = jest.fn(() => [...permissions]); // cloned, otherwise it is modified inside resetSuperAdminPermissions()
const deleteByIds = jest.fn();
const getSuperAdmin = jest.fn(() => Promise.resolve({ id: 1 }));
const createMany = jest.fn(() => Promise.resolve([{ ...permissions[0], role: { id: 1 } }]));
const removeUnkownConditionIds = jest.fn(conds => conds);
global.strapi = {
admin: {
services: {
permission: {
createMany,
find,
actionProvider: { getAll },
conditionProvider: { getAll: getAllConditions },
deleteByIds,
},
condition: { removeUnkownConditionIds },
'content-type': { getPermissionsWithNestedFields },
role: { getSuperAdmin },
},
},
};
await roleService.resetSuperAdminPermissions();
expect(deleteByIds).toHaveBeenCalledWith([2]);
expect(createMany).toHaveBeenCalledWith([
{
...permissions[0],
role: 1,
},
]);
});
});
describe('Assign permissions', () => {
test('Delete previous permissions', async () => {
const createMany = jest.fn(() => Promise.resolve([]));
const getSuperAdmin = jest.fn(() => Promise.resolve({ id: 0 }));
const sendDidUpdateRolePermissions = jest.fn();
const find = jest.fn(() => Promise.resolve([{ id: 3 }]));
const deleteByIds = jest.fn();
const getAll = jest.fn(() => []);
global.strapi = {
admin: {
services: {
metrics: { sendDidUpdateRolePermissions },
permission: { find, createMany, actionProvider: { getAll }, deleteByIds },
role: { getSuperAdmin },
},
},
};
await roleService.assignPermissions(1, []);
expect(deleteByIds).toHaveBeenCalledWith([3]);
});
test('Create new permissions', async () => {
const permissions = Array(5)
.fill(0)
.map((v, i) => ({ action: `action-${i}` }));
const createMany = jest.fn(() => Promise.resolve([]));
const getSuperAdmin = jest.fn(() => Promise.resolve({ id: 0 }));
const sendDidUpdateRolePermissions = jest.fn();
const find = jest.fn(() => Promise.resolve([]));
const getAll = jest.fn(() => permissions.map(perm => ({ actionId: perm.action })));
const removeUnkownConditionIds = jest.fn(conds => _.intersection(conds, ['cond']));
global.strapi = {
admin: {
services: {
metrics: { sendDidUpdateRolePermissions },
role: { getSuperAdmin },
permission: {
find,
createMany,
actionProvider: { getAll },
conditionProvider: {
getAll: jest.fn(() => [{ id: 'admin::is-creator' }]),
},
},
condition: {
removeUnkownConditionIds,
},
},
},
};
const permissionsToAssign = [...permissions];
permissionsToAssign[4] = {
...permissions[4],
conditions: ['cond', 'unknown-cond'],
};
await roleService.assignPermissions(1, permissionsToAssign);
expect(createMany).toHaveBeenCalledTimes(1);
expect(createMany).toHaveBeenCalledWith([
{ action: 'action-0', conditions: [], fields: null, role: 1, subject: null },
{ action: 'action-1', conditions: [], fields: null, role: 1, subject: null },
{ action: 'action-2', conditions: [], fields: null, role: 1, subject: null },
{ action: 'action-3', conditions: [], fields: null, role: 1, subject: null },
{ action: 'action-4', conditions: ['cond'], fields: null, role: 1, subject: null },
]);
});
});
});

View File

@ -3,7 +3,6 @@
const _ = require('lodash');
const { flatMap, filter } = require('lodash/fp');
const pmap = require('p-map');
const { validatePermissionsExist } = require('../validation/permission');
const createPermissionsManager = require('./permission/permissions-manager');
const createConditionProvider = require('./permission/condition-provider');
const createPermissionEngine = require('./permission/engine');
@ -15,20 +14,6 @@ const { createPermission } = require('../domain/permission');
const conditionProvider = createConditionProvider();
const engine = createPermissionEngine(conditionProvider);
const fieldsToCompare = ['action', 'subject', 'fields', 'conditions'];
const getPermissionWithSortedFields = perm => {
const sortedPerm = _.cloneDeep(perm);
if (Array.isArray(sortedPerm.fields)) {
sortedPerm.fields.sort();
}
return sortedPerm;
};
const arePermissionsEqual = (perm1, perm2) =>
_.isEqual(
_.pick(getPermissionWithSortedFields(perm1), fieldsToCompare),
_.pick(getPermissionWithSortedFields(perm2), fieldsToCompare)
);
/**
* Removes unwanted fields from a permission
* @param perm
@ -62,9 +47,7 @@ const deleteByIds = ids => {
* @param permissions
* @returns {Promise<*[]|*>}
*/
const createMany = async (roleId, permissions) => {
permissions.forEach(p => p.role = roleId);
const createMany = async permissions => {
return strapi.query('permission', 'admin').createMany(permissions);
};
@ -87,57 +70,6 @@ const find = (params = {}) => {
return strapi.query('permission', 'admin').find(params, []);
};
/**
* Assign permissions to a role
* @param {string|int} roleId - role ID
* @param {Array<Permission{action,subject,fields,conditions}>} permissions - permissions to assign to the role
*/
const assign = async (roleId, permissions = []) => {
try {
await validatePermissionsExist(permissions);
} catch (err) {
throw strapi.errors.badRequest('ValidationError', err);
}
const superAdmin = await strapi.admin.services.role.getSuperAdmin();
const isSuperAdmin = superAdmin && superAdmin.id === roleId;
const permissionsWithRole = permissions.map(permission =>
createPermission({
...permission,
conditions: strapi.admin.services.condition.removeUnkownConditionIds(permission.conditions),
role: roleId,
})
);
const existingPermissions = await find({ role: roleId, _limit: -1 });
const permissionsToAdd = _.differenceWith(
permissionsWithRole,
existingPermissions,
arePermissionsEqual
);
const permissionsToDelete = _.differenceWith(
existingPermissions,
permissionsWithRole,
arePermissionsEqual
);
const permissionsToReturn = _.differenceBy(existingPermissions, permissionsToDelete, 'id');
if (permissionsToDelete.length > 0) {
await deleteByIds(permissionsToDelete.map(p => p.id));
}
if (permissionsToAdd.length > 0) {
const createdPermissions = await createMany(roleId, permissionsToAdd);
permissionsToReturn.push(...createdPermissions.map(p => ({ ...p, role: p.role.id })));
}
if (!isSuperAdmin && (permissionsToAdd.length || permissionsToDelete.length)) {
await strapi.admin.services.metrics.sendDidUpdateRolePermissions();
}
return permissionsToReturn;
};
/**
* Find all permissions for a user
* @param roles
@ -250,49 +182,16 @@ const ensureBoundPermissionsInDatabase = async () => {
fields: BOUND_ACTIONS_FOR_FIELDS.includes(action) ? fields : null,
})
);
await createMany(editorRole.id, permissions);
await createMany(permissions);
}
}
};
/**
* Reset super admin permissions (giving it all permissions)
* @returns {Promise<>}
*/
const resetSuperAdminPermissions = async () => {
const superAdminRole = await strapi.admin.services.role.getSuperAdmin();
if (!superAdminRole) {
return;
}
const allActions = strapi.admin.services.permission.actionProvider.getAll();
const contentTypesActions = allActions.filter(a => a.section === 'contentTypes');
const permissions = strapi.admin.services['content-type'].getPermissionsWithNestedFields(
contentTypesActions
);
const otherActions = allActions.filter(a => a.section !== 'contentTypes');
otherActions.forEach(action => {
if (action.subjects) {
const newPerms = action.subjects.map(subject =>
createPermission({ action: action.actionId, subject })
);
permissions.push(...newPerms);
} else {
permissions.push(createPermission({ action: action.actionId }));
}
});
await assign(superAdminRole.id, permissions);
};
module.exports = {
createMany,
find,
deleteByRolesIds,
deleteByIds,
assign,
sanitizePermission,
findUserPermissions,
actionProvider,
@ -300,6 +199,5 @@ module.exports = {
engine,
conditionProvider,
cleanPermissionInDatabase,
resetSuperAdminPermissions,
ensureBoundPermissionsInDatabase,
};

View File

@ -5,6 +5,7 @@ const { set } = require('lodash/fp');
const { generateTimestampCode, stringIncludes } = require('strapi-utils');
const { SUPER_ADMIN_CODE } = require('./constants');
const { createPermission } = require('../domain/permission');
const { validatePermissionsExist } = require('../validation/permission');
const ACTIONS = {
publish: 'plugins::content-manager.explorer.publish',
@ -14,6 +15,22 @@ const sanitizeRole = role => {
return _.omit(role, ['users', 'permissions']);
};
const fieldsToCompare = ['action', 'subject', 'fields', 'conditions'];
const getPermissionWithSortedFields = perm => {
const sortedPerm = _.cloneDeep(perm);
if (Array.isArray(sortedPerm.fields)) {
sortedPerm.fields.sort();
}
return sortedPerm;
};
const arePermissionsEqual = (perm1, perm2) =>
_.isEqual(
_.pick(getPermissionWithSortedFields(perm1), fieldsToCompare),
_.pick(getPermissionWithSortedFields(perm2), fieldsToCompare)
);
/**
* Create and save a role in database
* @param attributes A partial role object
@ -233,8 +250,8 @@ const createRolesIfNoneExist = async ({ createPermissionsForAdmin = false } = {}
authorPermissions.push(...getDefaultPluginPermissions({ isAuthor: true }));
// assign permissions to roles
await strapi.admin.services.permission.createMany(editorRole.id, editorPermissions);
await strapi.admin.services.permission.createMany(authorRole.id, authorPermissions);
await addPermissions(editorRole.id, editorPermissions);
await addPermissions(authorRole.id, authorPermissions);
if (createPermissionsForAdmin) {
await strapi.admin.services.permission.resetSuperAdminPermissions();
@ -268,6 +285,101 @@ const displayWarningIfNoSuperAdmin = async () => {
}
};
/**
* Assign permissions to a role
* @param {string|int} roleId - role ID
* @param {Array<Permission{action,subject,fields,conditions}>} permissions - permissions to assign to the role
*/
const assignPermissions = async (roleId, permissions = []) => {
try {
await validatePermissionsExist(permissions);
} catch (err) {
throw strapi.errors.badRequest('ValidationError', err);
}
const superAdmin = await strapi.admin.services.role.getSuperAdmin();
const isSuperAdmin = superAdmin && superAdmin.id === roleId;
const permissionsWithRole = permissions.map(permission =>
createPermission({
...permission,
conditions: strapi.admin.services.condition.removeUnkownConditionIds(permission.conditions),
role: roleId,
})
);
const existingPermissions = await strapi.admin.services.permission.find({
role: roleId,
_limit: -1,
});
const permissionsToAdd = _.differenceWith(
permissionsWithRole,
existingPermissions,
arePermissionsEqual
);
const permissionsToDelete = _.differenceWith(
existingPermissions,
permissionsWithRole,
arePermissionsEqual
);
const permissionsToReturn = _.differenceBy(existingPermissions, permissionsToDelete, 'id');
if (permissionsToDelete.length > 0) {
await strapi.admin.services.permission.deleteByIds(permissionsToDelete.map(p => p.id));
}
if (permissionsToAdd.length > 0) {
const createdPermissions = await addPermissions(roleId, permissionsToAdd);
permissionsToReturn.push(...createdPermissions.map(p => ({ ...p, role: p.role.id })));
}
if (!isSuperAdmin && (permissionsToAdd.length || permissionsToDelete.length)) {
await strapi.admin.services.metrics.sendDidUpdateRolePermissions();
}
return permissionsToReturn;
};
const addPermissions = async (roleId, permissions) => {
permissions.forEach(p => {
p.role = roleId;
});
return strapi.admin.services.permission.createMany(permissions);
};
/**
* Reset super admin permissions (giving it all permissions)
* @returns {Promise<>}
*/
const resetSuperAdminPermissions = async () => {
const superAdminRole = await strapi.admin.services.role.getSuperAdmin();
if (!superAdminRole) {
return;
}
const allActions = strapi.admin.services.permission.actionProvider.getAll();
const contentTypesActions = allActions.filter(a => a.section === 'contentTypes');
const permissions = strapi.admin.services['content-type'].getPermissionsWithNestedFields(
contentTypesActions
);
const otherActions = allActions.filter(a => a.section !== 'contentTypes');
otherActions.forEach(action => {
if (action.subjects) {
const newPerms = action.subjects.map(subject =>
createPermission({ action: action.actionId, subject })
);
permissions.push(...newPerms);
} else {
permissions.push(createPermission({ action: action.actionId }));
}
});
await assignPermissions(superAdminRole.id, permissions);
};
module.exports = {
sanitizeRole,
create,
@ -284,4 +396,7 @@ module.exports = {
getSuperAdminWithUsersCount,
createRolesIfNoneExist,
displayWarningIfNoSuperAdmin,
addPermissions,
assignPermissions,
resetSuperAdminPermissions,
};