'use strict'; const _ = require('lodash'); const request = require('request'); const { getService } = require('../utils'); const DEFAULT_PERMISSIONS = [ { action: 'admincallback', controller: 'auth', type: 'users-permissions', roleType: 'public' }, { action: 'adminregister', controller: 'auth', type: 'users-permissions', roleType: 'public' }, { action: 'callback', controller: 'auth', type: 'users-permissions', roleType: 'public' }, { action: 'connect', controller: 'auth', type: 'users-permissions', roleType: null }, { action: 'forgotpassword', controller: 'auth', type: 'users-permissions', roleType: 'public' }, { action: 'resetpassword', controller: 'auth', type: 'users-permissions', roleType: 'public' }, { action: 'register', controller: 'auth', type: 'users-permissions', roleType: 'public' }, { action: 'emailconfirmation', 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 }) => ({ async createRole(params) { if (!params.type) { params.type = _.snakeCase(_.deburr(_.toLower(params.name))); } const role = await strapi .query('plugin::users-permissions.role') .create({ data: _.omit(params, ['users', 'permissions']) }); const arrayOfPromises = Object.keys(params.permissions || {}).reduce((acc, type) => { Object.keys(params.permissions[type].controllers).forEach(controller => { Object.keys(params.permissions[type].controllers[controller]).forEach(action => { acc.push( strapi.query('plugin::users-permissions.permission').create({ data: { role: role.id, type, controller, action: action.toLowerCase(), ...params.permissions[type].controllers[controller][action], }, }) ); }); }); return acc; }, []); // Use Content Manager business logic to handle relation. if (params.users && params.users.length > 0) arrayOfPromises.push( strapi.query('plugin::users-permissions.role').update({ where: { id: role.id, }, data: { users: params.users }, }) ); return await Promise.all(arrayOfPromises); }, async deleteRole(roleID, publicRoleID) { const role = await strapi .query('plugin::users-permissions.role') .findOne({ where: { id: roleID }, populate: ['users', 'permissions'] }); if (!role) { throw new Error('Cannot find this role'); } // Move users to guest role. const arrayOfPromises = role.users.reduce((acc, user) => { acc.push( strapi.query('plugin::users-permissions.user').update({ where: { id: user.id, }, data: { role: publicRoleID, }, }) ); return acc; }, []); // Remove permissions related to this role. role.permissions.forEach(permission => { arrayOfPromises.push( strapi.query('plugin::users-permissions.permission').delete({ where: { id: permission.id }, }) ); }); // Delete the role. arrayOfPromises.push( strapi.query('plugin::users-permissions.role').delete({ where: { id: roleID } }) ); return await Promise.all(arrayOfPromises); }, getPlugins(lang = 'en') { return new Promise(resolve => { request( { uri: `https://marketplace.strapi.io/plugins?lang=${lang}`, json: true, timeout: 3000, headers: { 'cache-control': 'max-age=3600', }, }, (err, response, body) => { if (err || response.statusCode !== 200) { return resolve([]); } resolve(body); } ); }); }, getActions() { const generateActions = data => Object.keys(data).reduce((acc, key) => { if (_.isFunction(data[key])) { acc[key] = { enabled: false, policy: '' }; } return acc; }, {}); const appControllers = Object.keys(strapi.api || {}) .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; }, { controllers: {} } ); const pluginsPermissions = Object.keys(strapi.plugins).reduce((acc, key) => { const initialState = { controllers: {}, }; const pluginControllers = strapi.plugin(key).controllers; 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) { const role = await strapi .query('plugin::users-permissions.role') .findOne({ where: { id: roleID }, populate: ['permissions'] }); if (!role) { throw new Error('Cannot find this role'); } // Group by `type`. const permissions = role.permissions.reduce((acc, permission) => { _.set(acc, `${permission.type}.controllers.${permission.controller}.${permission.action}`, { enabled: _.toNumber(permission.enabled) == true, policy: permission.policy, }); if (permission.type !== 'application' && !acc[permission.type].information) { acc[permission.type].information = plugins.find(plugin => plugin.id === permission.type) || {}; } return acc; }, {}); return { ...role, permissions, }; }, async getRoles() { const roles = await strapi.query('plugin::users-permissions.role').findMany({ sort: ['name'] }); for (let i = 0; i < roles.length; ++i) { roles[i].nb_users = await strapi .query('plugin::users-permissions.user') .count({ where: { role: { id: roles[i].id } } }); } return roles; }, async getRoutes() { const routes = Object.keys(strapi.api || {}).reduce((acc, current) => { return acc.concat(_.get(strapi.api[current].config, 'routes', [])); }, []); const pluginsRoutes = Object.keys(strapi.plugins).reduce((acc, current) => { const routes = strapi.plugin(current).routes.reduce((acc, curr) => { const prefix = curr.config.prefix; const path = prefix !== undefined ? `${prefix}${curr.path}` : `/${current}${curr.path}`; _.set(curr, 'path', path); return acc.concat(curr); }, []); acc[current] = routes; return acc; }, {}); return _.merge({ application: routes }, pluginsRoutes); }, async updatePermissions() { const roles = await strapi.query('plugin::users-permissions.role').findMany(); const rolesMap = _.keyBy(roles, 'id'); const dbPermissions = await strapi .query('plugin::users-permissions.permission') .findMany({ populate: ['role'] }); let permissionsFoundInDB = dbPermissions.map(permission => { const { type, controller, action, role } = permission; return `${type}.${controller}.${action}.${role.id}`; }); permissionsFoundInDB = _.uniq(permissionsFoundInDB); // Aggregate first level actions. const appActions = Object.keys(strapi.api || {}).reduce((acc, api) => { 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; }, []); // Aggregate plugins' actions. const pluginsActions = Object.keys(strapi.plugins).reduce((acc, plugin) => { const pluginControllers = strapi.plugin(plugin).controllers; Object.keys(pluginControllers).forEach(controller => { const controllerActions = pluginControllers[controller]; const actions = Object.keys(controllerActions) .filter(action => _.isFunction(controllerActions[action])) .map(action => `${plugin}.${controller}.${action.toLowerCase()}`); acc = acc.concat(actions); }); return acc; }, []); 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 } } }); }) ); } }, async initialize() { const roleCount = await strapi.query('plugin::users-permissions.role').count(); if (roleCount === 0) { await strapi.query('plugin::users-permissions.role').create({ data: { name: 'Authenticated', description: 'Default role given to authenticated user.', type: 'authenticated', }, }); await strapi.query('plugin::users-permissions.role').create({ data: { name: 'Public', description: 'Default role given to unauthenticated user.', type: 'public', }, }); } return getService('users-permissions').updatePermissions(); }, async updateRole(roleID, body) { const [role, authenticated] = await Promise.all([ this.getRole(roleID, []), strapi.query('plugin::users-permissions.role').findOne({ where: { type: 'authenticated' } }), ]); await strapi.query('plugin::users-permissions.role').update({ where: { id: roleID }, data: _.pick(body, ['name', 'description']), }); await Promise.all( Object.keys(body.permissions || {}).reduce((acc, type) => { Object.keys(body.permissions[type].controllers).forEach(controller => { Object.keys(body.permissions[type].controllers[controller]).forEach(action => { const bodyAction = body.permissions[type].controllers[controller][action]; const currentAction = _.get( role.permissions, `${type}.controllers.${controller}.${action}`, {} ); if (!_.isEqual(bodyAction, currentAction)) { acc.push( strapi.query('plugin::users-permissions.permission').update({ where: { role: roleID, type, controller, action: action.toLowerCase(), }, data: bodyAction, }) ); } }); }); return acc; }, []) ); // Add user to this role. const newUsers = _.differenceBy(body.users, role.users, 'id'); await Promise.all(newUsers.map(user => this.updateUserRole(user, roleID))); const oldUsers = _.differenceBy(role.users, body.users, 'id'); await Promise.all(oldUsers.map(user => this.updateUserRole(user, authenticated.id))); }, async updateUserRole(user, role) { return strapi .query('plugin::users-permissions.user') .update({ where: { id: user.id }, data: { role } }); }, template(layout, data) { const compiledObject = _.template(layout); return compiledObject(data); }, });