Refactor u-p permission syntax

This commit is contained in:
Alexandre Bodin 2021-09-06 22:33:55 +02:00
parent 7aa012da32
commit 561235e7cf
9 changed files with 215 additions and 324 deletions

View File

@ -5,7 +5,7 @@ const role = require('./role');
const user = require('./user'); const user = require('./user');
module.exports = { module.exports = {
permission, permission: { schema: permission },
role, role: { schema: role },
user, user: { schema: user },
}; };

View File

@ -1,7 +1,31 @@
'use strict'; 'use strict';
const schema = require('./schema');
module.exports = { module.exports = {
schema, collectionName: 'up_permissions',
info: {
name: 'permission',
description: '',
singularName: 'permission',
pluralName: 'permissions',
displayName: 'Permission',
},
pluginOptions: {
'content-manager': {
visible: false,
},
},
attributes: {
action: {
type: 'string',
required: true,
configurable: false,
},
role: {
type: 'relation',
relation: 'manyToOne',
target: 'plugin::users-permissions.role',
inversedBy: 'permissions',
configurable: false,
},
},
}; };

View File

@ -1,48 +0,0 @@
{
"collectionName": "up_permissions",
"info": {
"name": "permission",
"description": "",
"singularName": "permission",
"pluralName": "permissions",
"displayName": "Permission"
},
"pluginOptions": {
"content-manager": {
"visible": false
}
},
"attributes": {
"type": {
"type": "string",
"required": true,
"configurable": false
},
"controller": {
"type": "string",
"required": true,
"configurable": false
},
"action": {
"type": "string",
"required": true,
"configurable": false
},
"enabled": {
"type": "boolean",
"required": true,
"configurable": false
},
"policy": {
"type": "string",
"configurable": false
},
"role": {
"type": "relation",
"relation": "manyToOne",
"target": "plugin::users-permissions.role",
"inversedBy": "permissions",
"configurable": false
}
}
}

View File

@ -1,7 +1,48 @@
'use strict'; 'use strict';
const schema = require('./schema');
module.exports = { module.exports = {
schema, collectionName: 'up_roles',
info: {
name: 'role',
description: '',
singularName: 'role',
pluralName: 'roles',
displayName: 'Role',
},
pluginOptions: {
'content-manager': {
visible: false,
},
},
attributes: {
name: {
type: 'string',
minLength: 3,
required: true,
configurable: false,
},
description: {
type: 'string',
configurable: false,
},
type: {
type: 'string',
unique: true,
configurable: false,
},
permissions: {
type: 'relation',
relation: 'oneToMany',
target: 'plugin::users-permissions.permission',
mappedBy: 'role',
configurable: false,
},
users: {
type: 'relation',
relation: 'oneToMany',
target: 'plugin::users-permissions.user',
mappedBy: 'role',
configurable: false,
},
},
}; };

View File

@ -1,46 +0,0 @@
{
"collectionName": "up_roles",
"info": {
"name": "role",
"description": "",
"singularName": "role",
"pluralName": "roles",
"displayName": "Role"
},
"pluginOptions": {
"content-manager": {
"visible": false
}
},
"attributes": {
"name": {
"type": "string",
"minLength": 3,
"required": true,
"configurable": false
},
"description": {
"type": "string",
"configurable": false
},
"type": {
"type": "string",
"unique": true,
"configurable": false
},
"permissions": {
"type": "relation",
"relation": "oneToMany",
"target": "plugin::users-permissions.permission",
"mappedBy": "role",
"configurable": false
},
"users": {
"type": "relation",
"relation": "oneToMany",
"target": "plugin::users-permissions.user",
"mappedBy": "role",
"configurable": false
}
}
}

View File

@ -1,11 +1,72 @@
'use strict'; 'use strict';
const schema = require('./schema');
const schemaConfig = require('./schema-config'); const schemaConfig = require('./schema-config');
module.exports = { module.exports = {
schema: { collectionName: 'up_users',
...schema, info: {
config: schemaConfig, // TODO: to handle differently for V4 name: 'user',
description: '',
singularName: 'user',
pluralName: 'users',
displayName: 'User',
}, },
options: {
draftAndPublish: false,
timestamps: true,
},
attributes: {
username: {
type: 'string',
minLength: 3,
unique: true,
configurable: false,
required: true,
},
email: {
type: 'email',
minLength: 6,
configurable: false,
required: true,
},
provider: {
type: 'string',
configurable: false,
},
password: {
type: 'password',
minLength: 6,
configurable: false,
private: true,
},
resetPasswordToken: {
type: 'string',
configurable: false,
private: true,
},
confirmationToken: {
type: 'string',
configurable: false,
private: true,
},
confirmed: {
type: 'boolean',
default: false,
configurable: false,
},
blocked: {
type: 'boolean',
default: false,
configurable: false,
},
role: {
type: 'relation',
relation: 'manyToOne',
target: 'plugin::users-permissions.role',
inversedBy: 'users',
configurable: false,
},
},
config: schemaConfig, // TODO: to handle differently for V4
}; };

View File

@ -1,66 +0,0 @@
{
"collectionName": "up_users",
"info": {
"name": "user",
"description": "",
"singularName": "user",
"pluralName": "users",
"displayName": "User"
},
"options": {
"draftAndPublish": false,
"timestamps": true
},
"attributes": {
"username": {
"type": "string",
"minLength": 3,
"unique": true,
"configurable": false,
"required": true
},
"email": {
"type": "email",
"minLength": 6,
"configurable": false,
"required": true
},
"provider": {
"type": "string",
"configurable": false
},
"password": {
"type": "password",
"minLength": 6,
"configurable": false,
"private": true
},
"resetPasswordToken": {
"type": "string",
"configurable": false,
"private": true
},
"confirmationToken": {
"type": "string",
"configurable": false,
"private": true
},
"confirmed": {
"type": "boolean",
"default": false,
"configurable": false
},
"blocked": {
"type": "boolean",
"default": false,
"configurable": false
},
"role": {
"type": "relation",
"relation": "manyToOne",
"target": "plugin::users-permissions.role",
"inversedBy": "users",
"configurable": false
}
}
}

View File

@ -40,7 +40,6 @@ module.exports = strapi => {
.findMany({ .findMany({
where: { where: {
role: user.role.id, role: user.role.id,
enabled: true,
}, },
}); });
@ -74,7 +73,6 @@ module.exports = strapi => {
.findMany({ .findMany({
where: { where: {
role: { type: 'public' }, role: { type: 'public' },
enabled: true,
}, },
}); });

View File

@ -1,36 +1,22 @@
'use strict'; 'use strict';
const _ = require('lodash'); const _ = require('lodash');
const { filter, map, pipe, prop } = require('lodash/fp');
const { getService } = require('../utils'); const { getService } = require('../utils');
const DEFAULT_PERMISSIONS = [ const DEFAULT_PERMISSIONS = [
{ action: 'admincallback', controller: 'auth', type: 'users-permissions', roleType: 'public' }, { action: 'plugin::users-permissions.auth.admincallback', roleType: 'public' },
{ action: 'adminregister', controller: 'auth', type: 'users-permissions', roleType: 'public' }, { action: 'plugin::users-permissions.auth.adminregister', roleType: 'public' },
{ action: 'callback', controller: 'auth', type: 'users-permissions', roleType: 'public' }, { action: 'plugin::users-permissions.auth.callback', roleType: 'public' },
{ action: 'connect', controller: 'auth', type: 'users-permissions', roleType: null }, { action: 'plugin::users-permissions.auth.connect', roleType: null },
{ action: 'forgotpassword', controller: 'auth', type: 'users-permissions', roleType: 'public' }, { action: 'plugin::users-permissions.auth.forgotpassword', roleType: 'public' },
{ action: 'resetpassword', controller: 'auth', type: 'users-permissions', roleType: 'public' }, { action: 'plugin::users-permissions.auth.resetpassword', roleType: 'public' },
{ action: 'register', controller: 'auth', type: 'users-permissions', roleType: 'public' }, { action: 'plugin::users-permissions.auth.register', roleType: 'public' },
{ { action: 'plugin::users-permissions.auth.emailconfirmation', roleType: 'public' },
action: 'emailconfirmation', { action: 'plugin::users-permissions.user.me', roleType: null },
controller: 'auth',
type: 'users-permissions',
roleType: 'public',
},
{ action: 'me', controller: 'user', type: 'users-permissions', roleType: null },
]; ];
const isEnabledByDefault = (permission, role) => {
return DEFAULT_PERMISSIONS.some(
defaultPerm =>
(defaultPerm.action === null || permission.action === defaultPerm.action) &&
(defaultPerm.controller === null || permission.controller === defaultPerm.controller) &&
(defaultPerm.type === null || permission.type === defaultPerm.type) &&
(defaultPerm.roleType === null || role.type === defaultPerm.roleType)
);
};
module.exports = ({ strapi }) => ({ module.exports = ({ strapi }) => ({
async createRole(params) { async createRole(params) {
if (!params.type) { if (!params.type) {
@ -141,50 +127,29 @@ module.exports = ({ strapi }) => ({
}, },
getActions() { getActions() {
const generateActions = data => const actionMap = {};
Object.keys(data).reduce((acc, key) => {
if (_.isFunction(data[key])) {
acc[key] = { enabled: false, policy: '' };
}
return acc; _.forEach(strapi.api, (api, apiName) => {
}, {}); const controllers = _.mapValues(api.controllers, controller => {
return _.mapValues(controller, () => {
return { enabled: false, policy: '' };
});
});
const appControllers = Object.keys(strapi.api || {}) actionMap[`api::${apiName}`] = { controllers };
.filter(key => !!strapi.api[key].controllers) });
.reduce(
(acc, key) => {
Object.keys(strapi.api[key].controllers).forEach(controller => {
acc.controllers[controller] = generateActions(strapi.api[key].controllers[controller]);
});
return acc; _.forEach(strapi.plugins, (plugin, pluginName) => {
}, const controllers = _.mapValues(plugin.controllers, controller => {
{ controllers: {} } return _.mapValues(controller, () => {
); return { enabled: false, policy: '' };
});
});
const pluginsPermissions = Object.keys(strapi.plugins).reduce((acc, key) => { actionMap[`plugin::${pluginName}`] = { controllers };
const initialState = { });
controllers: {},
};
const pluginControllers = strapi.plugin(key).controllers; return actionMap;
acc[key] = Object.keys(pluginControllers).reduce((obj, k) => {
obj.controllers[k] = generateActions(pluginControllers[k]);
return obj;
}, initialState);
return acc;
}, {});
const permissions = {
application: {
controllers: appControllers.controllers,
},
};
return _.merge(permissions, pluginsPermissions);
}, },
async getRole(roleID, plugins) { async getRole(roleID, plugins) {
@ -198,14 +163,17 @@ module.exports = ({ strapi }) => ({
// Group by `type`. // Group by `type`.
const permissions = role.permissions.reduce((acc, permission) => { const permissions = role.permissions.reduce((acc, permission) => {
_.set(acc, `${permission.type}.controllers.${permission.controller}.${permission.action}`, { const [type, controller, action] = permission.action.split('.');
enabled: _.toNumber(permission.enabled) == true,
policy: permission.policy, _.set(acc, `${type}.controllers.${controller}.${action}`, {
enabled: true,
policy: '',
}); });
if (permission.type !== 'application' && !acc[permission.type].information) { if (permission.action.startsWith('plugin')) {
acc[permission.type].information = const [, pluginName] = type.split('::');
plugins.find(plugin => plugin.id === permission.type) || {};
acc[type].information = plugins.find(plugin => plugin.id === pluginName) || {};
} }
return acc; return acc;
@ -283,96 +251,55 @@ module.exports = ({ strapi }) => ({
async updatePermissions() { async updatePermissions() {
const roles = await strapi.query('plugin::users-permissions.role').findMany(); const roles = await strapi.query('plugin::users-permissions.role').findMany();
const dbPermissions = await strapi.query('plugin::users-permissions.permission').findMany();
const rolesMap = _.keyBy(roles, 'id'); const permissionsFoundInDB = _.uniq(_.map(dbPermissions, 'action'));
const dbPermissions = await strapi const appActions = _.flatMap(strapi.api, (api, apiName) => {
.query('plugin::users-permissions.permission') return _.flatMap(api.controllers, (controller, controllerName) => {
.findMany({ populate: ['role'] }); return _.keys(controller).map(actionName => {
return `api::${apiName}.${controllerName}.${_.toLower(actionName)}`;
let permissionsFoundInDB = dbPermissions.map(permission => { });
const { type, controller, action, role } = permission; });
return `${type}.${controller}.${action}.${role.id}`;
}); });
permissionsFoundInDB = _.uniq(permissionsFoundInDB); const pluginsActions = _.flatMap(strapi.plugins, (plugin, pluginName) => {
return _.flatMap(plugin.controllers, (controller, controllerName) => {
// Aggregate first level actions. return _.keys(controller).map(actionName => {
const appActions = Object.keys(strapi.api || {}).reduce((acc, api) => { return `plugin::${pluginName}.${controllerName}.${_.toLower(actionName)}`;
Object.keys(_.get(strapi.api[api], 'controllers', {})).forEach(controller => { });
const actions = Object.keys(strapi.api[api].controllers[controller])
.filter(action => _.isFunction(strapi.api[api].controllers[controller][action]))
.map(action => `application.${controller}.${action.toLowerCase()}`);
acc = acc.concat(actions);
}); });
});
return acc; const allActions = [...appActions, ...pluginsActions];
}, []);
// Aggregate plugins' actions. const toDelete = _.difference(permissionsFoundInDB, allActions);
const pluginsActions = Object.keys(strapi.plugins).reduce((acc, plugin) => {
const pluginControllers = strapi.plugin(plugin).controllers;
Object.keys(pluginControllers).forEach(controller => { await Promise.all(
const controllerActions = pluginControllers[controller]; toDelete.map(action => {
return strapi.query('plugin::users-permissions.permission').delete({ where: { action } });
})
);
const actions = Object.keys(controllerActions) if (permissionsFoundInDB.length === 0) {
.filter(action => _.isFunction(controllerActions[action])) // create default permissions
.map(action => `${plugin}.${controller}.${action.toLowerCase()}`); for (const role of roles) {
const toCreate = pipe(
filter(({ roleType }) => roleType === role.type || roleType === null),
map(prop('action'))
)(DEFAULT_PERMISSIONS);
acc = acc.concat(actions); await Promise.all(
}); toCreate.map(action => {
return strapi.query('plugin::users-permissions.permission').create({
return acc; data: {
}, []); action,
role: role.id,
const actionsFoundInFiles = appActions.concat(pluginsActions); },
});
const permissionsFoundInFiles = []; })
);
for (const role of roles) { }
actionsFoundInFiles.forEach(action => {
permissionsFoundInFiles.push(`${action}.${role.id}`);
});
}
// Compare to know if actions have been added or removed from controllers.
if (!_.isEqual(permissionsFoundInDB.sort(), permissionsFoundInFiles.sort())) {
const splitted = str => {
const [type, controller, action, roleId] = str.split('.');
return { type, controller, action, roleId };
};
// We have to know the difference to add or remove the permissions entries in the database.
const toRemove = _.difference(permissionsFoundInDB, permissionsFoundInFiles).map(splitted);
const toAdd = _.difference(permissionsFoundInFiles, permissionsFoundInDB).map(splitted);
const query = strapi.query('plugin::users-permissions.permission');
// Execute request to update entries in database for each role.
await Promise.all(
toAdd.map(permission => {
return query.create({
data: {
type: permission.type,
controller: permission.controller,
action: permission.action,
enabled: isEnabledByDefault(permission, rolesMap[permission.roleId]),
policy: '',
role: permission.roleId,
},
});
})
);
await Promise.all(
toRemove.map(permission => {
const { type, controller, action, roleId } = permission;
return query.delete({ where: { type, controller, action, role: { id: roleId } } });
})
);
} }
}, },