582 lines
19 KiB
JavaScript
Raw Normal View History

'use strict';
2017-11-17 14:22:59 +01:00
const fs = require('fs')
const path = require('path');
2017-11-16 17:59:41 +01:00
const _ = require('lodash');
const request = require('request');
2017-12-07 18:16:18 +01:00
/**
* UsersPermissions.js service
*
* @description: A set of functions similar to controller's actions to avoid code duplication.
*/
module.exports = {
createRole: async (params) => {
if (!strapi.plugins['content-manager']) {
return new Error('This feature requires to install the Content Manager plugin');
}
2017-11-27 16:49:56 +01:00
if (!params.type) {
params.type = _.snakeCase(_.deburr(_.toLower(params.name)));
}
const role = await strapi.query('role', 'users-permissions').create(_.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('permission', 'users-permissions').addPermission({
role: role._id || role.id,
type,
controller,
action: action.toLowerCase(),
...params.permissions[type].controllers[controller][action]
}));
});
});
2017-11-27 16:49:56 +01:00
return acc;
}, []);
// Use Content Manager business logic to handle relation.
arrayOfPromises.push(strapi.plugins['content-manager'].services['contentmanager'].edit({
id: role._id || role.id,
model: 'role'
}, {
users: params.users
}, 'users-permissions'));
2017-12-01 16:06:16 +01:00
return await Promise.all(arrayOfPromises);
2017-11-27 17:02:45 +01:00
},
2018-03-12 16:37:20 +01:00
deleteRole: async (roleID, publicRoleID) => {
const role = await strapi.query('role', 'users-permissions').findOne({ id: roleID }, ['users', 'permissions']);
if (!role) {
throw new Error('Cannot found this role');
}
if (role.type === 'root') {
return new Error(`You cannot delete the root admin role.`);
}
// Move users to guest role.
const arrayOfPromises = role.users.reduce((acc, user) => {
acc.push(strapi.query('user', 'users-permissions').update({
id: user._id || user.id
}, {
2018-03-12 16:37:20 +01:00
role: publicRoleID
}))
return acc;
}, []);
2017-11-27 17:50:51 +01:00
// Remove permissions related to this role.
role.permissions.forEach(permission => {
arrayOfPromises.push(strapi.query('permission', 'users-permissions').delete({
id: permission._id || permission.id
}));
})
2017-12-01 16:06:16 +01:00
// Delete the role.
arrayOfPromises.push(strapi.query('role', 'users-permissions').delete({
id: roleID
2017-12-01 16:06:16 +01:00
}));
return await Promise.all(arrayOfPromises);
2017-11-27 16:49:56 +01:00
},
getPlugins: (plugin, lang = 'en') => {
return new Promise((resolve, reject) => {
request({
uri: `https://marketplace.strapi.io/plugins?lang=${lang}`,
json: true,
headers: {
'cache-control': 'max-age=3600'
}
}, (err, response, body) => {
if (err) {
return reject(err);
}
resolve(body);
});
});
},
getActions: (plugins = [], withInfo = true) => {
2017-11-17 14:22:59 +01:00
const generateActions = (data) => (
Object.keys(data).reduce((acc, key) => {
if (_.isFunction(data[key])) {
acc[key] = { enabled: false, policy: '' };
}
2017-11-17 14:22:59 +01:00
return acc;
}, {}));
const appControllers = Object.keys(strapi.api || {}).reduce((acc, key) => {
2017-11-17 14:22:59 +01:00
acc.controllers[key] = generateActions(strapi.api[key].controllers[key]);
2017-11-16 17:59:41 +01:00
return acc;
}, { controllers: {} });
const pluginsPermissions = Object.keys(strapi.plugins).reduce((acc, key) => {
const initialState = {
controllers: {}
};
if (withInfo) {
initialState.information = plugins.find(plugin => plugin.id === key) || {};
}
2017-11-20 14:35:24 +01:00
acc[key] = Object.keys(strapi.plugins[key].controllers).reduce((obj, k) => {
2017-11-17 14:22:59 +01:00
obj.controllers[k] = generateActions(strapi.plugins[key].controllers[k]);
return obj;
}, initialState);
return acc;
}, {});
2017-11-16 17:59:41 +01:00
const permissions = {
application: {
controllers: appControllers.controllers,
},
2017-11-16 17:59:41 +01:00
};
2017-12-11 11:14:07 +01:00
return _.merge(permissions, pluginsPermissions);;
2017-11-17 16:36:57 +01:00
},
2017-11-17 14:22:59 +01:00
getRole: async (roleID, plugins) => {
const role = await strapi.query('role', 'users-permissions').findOne({ id: roleID }, ['users', 'permissions']);
if (!role) {
throw new Error('Cannot found this role');
}
2017-11-27 17:50:51 +01:00
2018-01-22 18:19:44 +01:00
// Group by `type`.
role.permissions = role.permissions.reduce((acc, permission) => {
_.set(acc, `${permission.type}.controllers.${permission.controller}.${permission.action}`, {
enabled: _.toNumber(permission.enabled) == true,
2018-01-22 18:19:44 +01:00
policy: permission.policy
});
2018-01-22 18:19:44 +01:00
if (permission.type !== 'application' && !acc[permission.type].information) {
acc[permission.type].information = plugins.find(plugin => plugin.id === permission.type) || {};
}
return acc;
}, {});
return role;
2017-11-27 17:50:51 +01:00
},
2017-11-30 12:27:04 +01:00
getRoles: async () => {
const roles = await strapi.query('role', 'users-permissions').find({ sort: 'name ASC' }, []);
2017-11-27 16:04:57 +01:00
for (let i = 0; i < roles.length; ++i) {
roles[i].id = roles[i].id || roles[i]._id;
roles[i].nb_users = await strapi.query('user', 'users-permissions').count({ role: roles[i].id });
}
2017-11-27 16:04:57 +01:00
return roles;
2017-11-27 16:04:57 +01:00
},
2017-11-30 16:34:43 +01:00
getRoutes: async () => {
const routes = Object.keys(strapi.api || {}).reduce((acc, current) => {
2017-12-07 18:16:18 +01:00
return acc.concat(strapi.api[current].config.routes);
}, []);
2017-12-07 18:16:18 +01:00
const pluginsRoutes = Object.keys(strapi.plugins || {}).reduce((acc, current) => {
2017-11-30 16:34:43 +01:00
acc[current] = strapi.plugins[current].config.routes;
return acc;
2017-12-07 18:16:18 +01:00
}, []);
return _.merge({ application: routes }, pluginsRoutes);
2017-11-30 16:34:43 +01:00
},
updatePermissions: async function (cb) {
const actions = strapi.plugins['users-permissions'].config.actions || [];
// Aggregate first level actions.
const appActions = Object.keys(strapi.api || {}).reduce((acc, api) => {
Object.keys(strapi.api[api].controllers)
.map(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);
2017-11-17 16:36:57 +01:00
});
return acc;
}, []);
2017-11-17 16:36:57 +01:00
// Aggregate plugins' actions.
const pluginsActions = Object.keys(strapi.plugins).reduce((acc, plugin) => {
Object.keys(strapi.plugins[plugin].controllers)
.map(controller => {
const actions = Object.keys(strapi.plugins[plugin].controllers[controller])
.filter(action => _.isFunction(strapi.plugins[plugin].controllers[controller][action]))
.map(action => `${plugin}.${controller}.${action.toLowerCase()}`);
acc = acc.concat(actions);
});
2017-12-07 10:16:36 +01:00
return acc;
}, []);
2017-11-17 16:36:57 +01:00
// Merge array into one.
const currentActions = appActions.concat(pluginsActions);
// Count permissions available.
const permissions = await strapi.query('permission', 'users-permissions').count();
// Compare to know if actions have been added or removed from controllers.
if (!_.isEqual(actions, currentActions) || permissions < 1) {
const splitted = (str) => {
const [type, controller, action] = str.split('.');
return { type, controller, action };
};
const defaultPolicy = (obj, role) => {
2018-03-12 16:37:20 +01:00
const isCallback = obj.action === 'callback' && obj.controller === 'auth' && obj.type === 'users-permissions' && role.type === 'public';
2018-01-25 14:36:12 +01:00
const isConnect = obj.action === 'connect' && obj.controller === 'auth' && obj.type === 'users-permissions';
2018-03-12 16:37:20 +01:00
const isRegister = obj.action === 'register' && obj.controller === 'auth' && obj.type === 'users-permissions' && role.type === 'public';
const isPassword = obj.action === 'forgotpassword' && obj.controller === 'auth' && obj.type === 'users-permissions' && role.type === 'public';
const isNewPassword = obj.action === 'changepassword' && obj.controller === 'auth' && obj.type === 'users-permissions' && role.type === 'public';
const isInit = obj.action === 'init' && obj.controller === 'userspermissions';
const isMe = obj.action === 'me' && obj.controller === 'user' && obj.type === 'users-permissions';
const isReload = obj.action === 'autoreload';
2018-01-25 14:36:12 +01:00
const enabled = isCallback || isRegister || role.type === 'root' || isInit || isPassword || isNewPassword || isMe || isReload || isConnect;
return Object.assign(obj, { enabled, policy: '' });
};
// Retrieve roles
const roles = await strapi.query('role', 'users-permissions').find();
// We have to know the difference to add or remove
// the permissions entries in the database.
const toRemove = _.difference(actions, currentActions).map(splitted);
const toAdd = (permissions < 1 ? currentActions : _.difference(currentActions, actions))
.map(splitted);
// Execute request to update entries in database for each role.
await Promise.all(
roles.map(role =>
Promise.all(
toAdd
.map(action => defaultPolicy(action, role))
.map(action => strapi.query('permission', 'users-permissions')
.addPermission(Object.assign(action, { role: role.id || role._id }))
)
)
),
Promise.all(toRemove.map(action => strapi.query('permission', 'users-permissions').removePermission(action)))
);
return this.writeActions(currentActions, cb);
2017-11-17 14:22:59 +01:00
}
2017-11-20 14:35:24 +01:00
cb();
2017-11-17 16:36:57 +01:00
},
2017-11-17 14:22:59 +01:00
initialize: async function (cb) {
const roles = await strapi.query('role', 'users-permissions').count();
// It's has been already initialized.
if (roles > 0) {
return await this.updatePermissions(cb);
}
// Create two first default roles.
await Promise.all([
strapi.query('role', 'users-permissions').create({
name: 'Administrator',
description: 'These users have all access in the project.',
type: 'root'
}),
strapi.query('role', 'users-permissions').create({
name: 'Authenticated',
description: 'Default role given to authenticated user.',
type: 'authenticated'
}),
strapi.query('role', 'users-permissions').create({
2018-03-12 16:37:20 +01:00
name: 'Public',
description: 'Default role given to unauthenticated user.',
2018-03-12 16:37:20 +01:00
type: 'public'
})
]);
await this.updatePermissions(cb);
},
2018-01-22 18:19:44 +01:00
updateRole: async function (roleID, body) {
const [role, root, authenticated] = await Promise.all([
2018-01-22 18:19:44 +01:00
this.getRole(roleID, []),
strapi.query('role', 'users-permissions').findOne({ type: 'root' }, []),
strapi.query('role', 'users-permissions').findOne({ type: 'authenticated' }, [])
2018-01-22 18:19:44 +01:00
]);
2017-11-27 17:50:51 +01:00
2018-01-22 18:19:44 +01:00
const arrayOfPromises = 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 (_.differenceWith([bodyAction], [currentAction]).length > 0) {
acc.push(strapi.query('permission', 'users-permissions').update({
role: roleID,
type,
controller,
action: action.toLowerCase()
2018-01-22 18:19:44 +01:00
}, bodyAction));
}
});
});
2018-01-22 18:19:44 +01:00
return acc;
}, []);
2017-12-01 16:06:16 +01:00
arrayOfPromises.push(strapi.query('role', 'users-permissions').update({
id: roleID,
}, _.pick(body, ['name', 'description'])));
2018-02-24 17:42:33 +01:00
// stringify mongoDB _id for add/remove matching
if (role._id ? '_id' : 'id' === '_id') {
role.users.reduce((acc, user) => {
const key = role._id ? '_id' : 'id';
user[key] = user[key].toString();
acc.push(user);
return acc;
}, []);
}
2018-01-22 18:19:44 +01:00
// Add user to this role.
_.differenceBy(body.users, role.users, role._id ? '_id' : 'id')
.filter(user => user.role !== `${root._id || root.id}`.toString())
.forEach(user => {
arrayOfPromises.push(this.updateUserRole(user, roleID));
})
2017-12-05 16:44:54 +01:00
// Remove user to this role and link him to authenticated.
_.differenceBy(role.users, body.users, role._id ? '_id' : 'id')
.filter(user => user.role !== `${root._id || root.id}`.toString())
.forEach(user => {
arrayOfPromises.push(this.updateUserRole(user, authenticated._id || authenticated.id));
});
2018-01-22 18:19:44 +01:00
2018-01-22 18:19:44 +01:00
return Promise.all(arrayOfPromises);
2017-11-27 17:50:51 +01:00
},
2017-12-07 10:16:36 +01:00
updateUserRole: async (user, role) => {
return strapi.query('user', 'users-permissions').update({
id: user._id || user.id
}, {
2017-12-07 10:16:36 +01:00
role: role.toString()
});
},
writeActions: (data, cb) => {
const actionsPath = path.join(strapi.config.appPath, 'plugins', 'users-permissions', 'config', 'actions.json');
2017-11-17 16:36:57 +01:00
try {
// Disable auto-reload.
strapi.reload.isWatching = false;
// Rewrite actions.json file.
fs.writeFileSync(actionsPath, JSON.stringify({ actions: data }), 'utf8');
// Set value to AST to avoid restart.
_.set(strapi.plugins['users-permissions'], 'config.actions', data);
// Disable auto-reload.
strapi.reload.isWatching = true;
cb();
2017-11-17 16:36:57 +01:00
} catch(err) {
strapi.log.error(err);
}
2017-12-01 16:06:16 +01:00
},
syncSchema: async (cb) => {
if (strapi.plugins['users-permissions'].models.user.orm !== 'bookshelf') {
2018-01-11 15:00:34 +01:00
return cb();
}
// Extract necessary information from plugin's models.
const {
user: { collectionName: userTableName, connection: userConnection, client: userClient },
role: { collectionName: roleTableName, connection: roleConnection, client: roleClient },
permission: { collectionName: permissionTableName, connection: permissionConnection, client: permissionClient }
} = strapi.plugins['users-permissions'].models;
const details = {
user: {
tableName: userTableName,
connection: userConnection,
client: userClient
},
role: {
tableName: roleTableName,
connection: roleConnection,
client: roleClient
},
permission: {
tableName: permissionTableName,
connection: permissionConnection,
client: permissionClient
}
};
// Check if the tables are existing.
const hasTables = await Promise.all(Object.keys(details).map(name =>
strapi.connections[details[name].connection].schema.hasTable(details[name].tableName)
));
const missingTables = [];
const tablesToCreate = [];
for (let index = 0; index < hasTables.length; ++index) {
const hasTable = hasTables[index];
const currentModel = Object.keys(details)[index];
2018-01-25 11:19:53 +01:00
const quote = details[currentModel].client === 'pg' ? '"' : '`';
2018-01-11 15:00:34 +01:00
if (!hasTable) {
missingTables.push(`
TABLE \`${details[currentModel].tableName}\` DOESN'T EXIST`);
switch (currentModel) {
case 'user':
tablesToCreate.push(`
2018-01-25 11:19:53 +01:00
CREATE TABLE ${quote}${details[currentModel].tableName}${quote} (
id ${details[currentModel].client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY,
2018-01-11 15:00:34 +01:00
username text,
email text,
provider text,
2018-01-25 11:19:53 +01:00
role ${details[currentModel].client === 'pg' ? 'integer' : 'int'},
${quote}resetPasswordToken${quote} text,
2018-01-11 15:00:34 +01:00
password text,
updated_at ${details[currentModel].client === 'pg' ? 'timestamp with time zone' : 'timestamp'},
created_at ${details[currentModel].client === 'pg' ? 'timestamp with time zone' : 'timestamp'}
);`);
break;
case 'role':
tablesToCreate.push(`
2018-01-25 11:19:53 +01:00
CREATE TABLE ${quote}${details[currentModel].tableName}${quote} (
id ${details[currentModel].client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY,
name text,
description text,
type text
);`);
break;
case 'permission':
tablesToCreate.push(`
2018-01-25 11:19:53 +01:00
CREATE TABLE ${quote}${details[currentModel].tableName}${quote} (
id ${details[currentModel].client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY,
role ${details[currentModel].client === 'pg' ? 'integer' : 'int'},
type text,
controller text,
action text,
enabled boolean,
policy text
);`);
break;
default:
2018-01-11 15:00:34 +01:00
}
}
}
if (!_.isEmpty(tablesToCreate)) {
tablesToCreate.unshift(`
2018-01-11 15:00:34 +01:00
1 EXECUTE THE FOLLOWING SQL QUERY`);
2018-01-11 15:00:34 +01:00
tablesToCreate.push(`
2 RESTART YOUR SERVER`)
strapi.log.warn(missingTables.concat(tablesToCreate).join(''));
// Stop the server.
strapi.stop();
}
const missingColumns = [];
const tablesToAlter = [];
for (let index = 0; index < hasTables.length; ++index) {
const currentModel = Object.keys(details)[index];
2018-01-25 11:19:53 +01:00
const quote = details[currentModel].client === 'pg' ? '"' : '`';
const attributes = {
id: {
type: details[currentModel].client === 'pg' ? 'integer' : 'int'
},
..._.cloneDeep(strapi.plugins['users-permissions'].models[currentModel].attributes)
2018-01-11 15:00:34 +01:00
};
// Add created_at and updated_at attributes for the model User.
if (currentModel === 'user') {
Object.assign(attributes, {
created_at: {
type: details[currentModel].client === 'pg' ? 'timestamp with time zone' : 'timestamp'
},
updated_at: {
type: details[currentModel].client === 'pg' ? 'timestamp with time zone' : 'timestamp'
}
});
}
const columns = Object.keys(attributes);
// Check if there are the required attributes.
const hasColumns = await Promise.all(columns.map(attribute =>
strapi.connections[details[currentModel].connection].schema.hasColumn(details[currentModel].tableName, attribute)
));
hasColumns.forEach((hasColumn, index) => {
const currentColumn = columns[index];
const attribute = attributes[currentColumn];
if (!hasColumn && !attribute.collection) {
2018-01-25 11:19:53 +01:00
const currentType = attribute.model ? 'integer' : attribute.type;
const type = currentType === 'string' ? 'text' : currentType;
missingColumns.push(`
TABLE \`${details[currentModel].tableName}\` HAS MISSING COLUMNS`);
tablesToAlter.push(`
2018-01-25 11:19:53 +01:00
ALTER TABLE ${quote}${details[currentModel].tableName}${quote} ADD ${details[currentModel].client === 'pg' ? `${quote}${currentColumn}${quote}` : `${currentColumn}`} ${type};`);
}
});
}
if (!_.isEmpty(tablesToAlter)) {
tablesToAlter.unshift(`
1 EXECUTE THE FOLLOWING SQL QUERIES`);
tablesToAlter.push(`
2 RESTART YOUR SERVER`)
strapi.log.warn(missingColumns.concat(tablesToAlter).join(''));
// Stop the server.
return strapi.stop();
}
cb();
2018-01-25 08:38:46 +01:00
},
template: (layout, data) => {
const compiledObject = _.template(layout);
return compiledObject(data);
}
};