'use strict';
/**
* @module core-loaders
*/
/**
* Provides functions to interpret permissions definition from core and plugins.
*
* Permissions comes from 2 different things :
* - Core and plugin's configuration files
* - Groups of users which are entities
*
* @class permissionLoader
* @static
*/
var path = require('path');
var openVeoApi = require('@openveo/api');
var GroupProvider = process.require('app/server/providers/GroupProvider.js');
var storage = process.require('app/server/storage.js');
/**
* Makes sure all permissions of a plugin are prefixed by the name of the plugin.
*
* If a permission is not prefixed by the name of the plugin, the prefix is automatically added.
*
* @method prefixPermissions
* @private
* @param {String} pluginName Plugin's name
* @param {Object} permissions Plugin's permissions
*/
function prefixPermissions(pluginName, permissions) {
if (pluginName && permissions) {
permissions.forEach(function(permission) {
if (permission.id && permission.id.indexOf(pluginName + '-') !== 0)
permission.id = pluginName + '-' + permission.id;
if (permission.permissions)
prefixPermissions(pluginName, permission.permissions);
});
}
}
/**
* Generates add/update/delete permissions for entities.
*
* Permission's translation keys for name and description are generated using the formats
* "PERMISSIONS.{PLUGIN_NAME}_{OPERATION}_{ENTITY_NAME}_NAME" and
* "PERMISSIONS.{PLUGIN_NAME}_{OPERATION}_{ENTITY_NAME}_DESCRIPTION".
*
* Content entities won't generate any permissions.
*
* @example
* var permissionLoader= process.require('app/server/loaders/permissionLoader.js');
* var entities = {
* {
* core: {
* mountPath: '/',
* path: '/home/openveo/',
* entities: {
* applications: 'app/server/controllers/ApplicationController'
* }
* },
* publish: {
* mountPath: '/publish',
* path: '/home/openveo/node_modules/@openveo/publish/',
* entities: {
* videos: 'app/server/controllers/VideoController'
* }
* }
* }
* };
*
* console.log(permissionLoader.generateEntityPermissions(entities));
* // [
* // {
* // label: 'CORE.PERMISSIONS.GROUP_APPLICATIONS',
* // permissions: [
* // {
* // id : 'core-add-applications',
* // name : 'CORE.PERMISSIONS.ADD_APPLICATIONS_NAME',
* // description : 'CORE.PERMISSIONS.ADD_APPLICATIONS_DESCRIPTION',
* // paths : [ 'put /applications*' ]
* // },
* // {
* // id : 'core-update-applications',
* // name : 'CORE.PERMISSIONS.UPDATE_APPLICATIONS_NAME',
* // description : 'CORE.PERMISSIONS.UPDATE_APPLICATIONS_DESCRIPTION',
* // paths : [ 'post /applications*' ]
* // },
* // {
* // id : 'core-delete-applications',
* // name : 'CORE.PERMISSIONS.DELETE_APPLICATIONS_NAME',
* // description : 'CORE.PERMISSIONS.DELETE_APPLICATIONS_DESCRIPTION',
* // paths : [ 'delete /applications*' ]
* // }
* // ]
* // },
* // {
* // label: 'PUBLISH.PERMISSIONS.GROUP_VIDEOS',
* // permissions: [
* // {
* // id : 'publish-add-videos',
* // name : 'PUBLISH.PERMISSIONS.ADD_VIDEOS_NAME',
* // description : 'PUBLISH.PERMISSIONS.ADD_VIDEOS_DESCRIPTION',
* // paths : [ 'put /publish/videos*' ]
* // },
* // {
* // id : 'publish-update-videos',
* // name : 'PUBLISH.PERMISSIONS.UPDATE_VIDEOS_NAME',
* // description : 'PUBLISH.PERMISSIONS.UPDATE_VIDEOS_DESCRIPTION',
* // paths : [ 'post /publish/videos*' ]
* // },
* // {
* // id : 'publish-delete-videos',
* // name : 'PUBLISH.PERMISSIONS.DELETE_VIDEOS_NAME',
* // description : 'PUBLISH.PERMISSIONS.DELETE_VIDEOS_DESCRIPTION',
* // paths : [ 'delete /publish/videos*' ]
* // }
* // ]
* // }
* // ]
*
* @method generateEntityPermissions
* @static
* @param {Object} pluginsEntities The list of entities ordered by plugins
* @return {Object} Permissions for all entities
*/
module.exports.generateEntityPermissions = function(pluginsEntities) {
var permissions = [];
if (pluginsEntities) {
var operations = {
add: 'put ',
update: 'post ',
delete: 'delete '
};
for (var pluginName in pluginsEntities) {
var plugin = pluginsEntities[pluginName];
var pluginNameUC = pluginName.toUpperCase();
var mountPath = (plugin.mountPath === '/') ? '/' : plugin.mountPath + '/';
for (var entityId in plugin.entities) {
var EntityController = require(path.join(plugin.path, plugin.entities[entityId]));
if (!(new EntityController() instanceof openVeoApi.controllers.ContentController)) {
var entityIdUC = entityId.toUpperCase();
var entityIdLowerCase = entityId.toLowerCase();
var group = {
label: pluginNameUC + '.PERMISSIONS.GROUP_' + entityIdUC,
permissions: []
};
for (var operation in operations) {
var operationUC = operation.toUpperCase();
group.permissions.push({
id: pluginName + '-' + operation + '-' + entityIdLowerCase,
name: pluginNameUC + '.PERMISSIONS.' + operationUC + '_' + entityIdUC + '_NAME',
description: pluginNameUC + '.PERMISSIONS.' + operationUC + '_' + entityIdUC + '_DESCRIPTION',
paths: [operations[operation] + mountPath + entityIdLowerCase + '*']
});
}
permissions.push(group);
}
}
}
}
return permissions;
};
/**
* Builds entities' scopes.
*
* @example
* // List of entities by plugin
* {
* publish: {
* mountPath: '/publish',
* path: '/home/openveo/node_modules/@openveo/publish',
* entities: {
* videos: 'app/server/controllers/VideoController'
* }
* }
* }
*
* @example
* // Result
* [
* {
* id: 'publish-get-videos',
* name: 'PUBLISH.WS_SCOPES.GET_VIDEOS_NAME',
* description: 'PUBLISH.WS_SCOPES.GET_VIDEOS_DESCRIPTON',
* paths: [
* 'get /publish/videos*'
* ]
* },
* {
* id: 'publish-add-videos',
* name: 'PUBLISH.WS_SCOPES.ADD_VIDEOS_NAME',
* description: 'PUBLISH.WS_SCOPES.ADD_VIDEOS_DESCRIPTON',
* paths: [
* 'put /publish/videos*'
* ]
* },
* {
* id: 'publish-update-videos',
* name: 'PUBLISH.WS_SCOPES.UPDATE_VIDEOS_NAME',
* description: 'PUBLISH.WS_SCOPES.UPDATE_VIDEOS_DESCRIPTON',
* paths: [
* 'post /publish/videos*'
* ]
* },
* {
* id: 'publish-delete-videos',
* name: 'PUBLISH.WS_SCOPES.DELETE_VIDEOS_NAME',
* description: 'PUBLISH.WS_SCOPES.DELETE_VIDEOS_DESCRIPTON',
* paths: [
* 'delete /publish/videos*'
* ]
* }
* ]
*
* @method generateEntityScopes
* @static
* @param {Object} pluginsEntities The list of entities
* @return {Array} The list of web service scopes for all entities exposed by all plugins
*/
module.exports.generateEntityScopes = function(pluginsEntities) {
var scopes = [];
if (pluginsEntities) {
var operations = {
get: 'get ',
add: 'put ',
update: 'post ',
delete: 'delete '
};
for (var pluginName in pluginsEntities) {
var plugin = pluginsEntities[pluginName];
var pluginNameUC = pluginName.toUpperCase();
var mountPath = (plugin.mountPath === '/') ? '/' : plugin.mountPath + '/';
for (var entityId in plugin.entities) {
for (var operation in operations) {
var operationUC = operation.toUpperCase();
var entityIdUC = entityId.toUpperCase();
var entityIdLowerCase = entityId.toLowerCase();
scopes.push({
id: pluginName + '-' + operation + '-' + entityIdLowerCase,
name: pluginNameUC + '.WS_SCOPES.' + operationUC + '_' + entityIdUC + '_NAME',
description: pluginNameUC + '.WS_SCOPES.' + operationUC + '_' + entityIdUC + '_DESCRIPTION',
paths: [operations[operation] + mountPath + entityIdLowerCase + '*']
});
}
}
}
}
return scopes;
};
/**
* Reorganizes orphaned top permissions into a generic group.
*
* @example
* var permissionLoader= process.require('app/server/loaders/permissionLoader.js');
* var permissions = [
* {
* 'id' : 'orphaned-permission',
* 'name' : 'ORPHANED_PERM_NAME',
* 'description' : 'ORPHANED_PERM_DESCRIPTION'
* }
* ];
* console.log(permissionLoader.groupOrphanedPermissions(permissions));
* // [
* // {
* // label: 'CORE.PERMISSIONS.GROUP_OTHERS',
* // permissions: [
* // {
* // 'id' : 'orphaned-permission',
* // 'name' : 'ORPHANED_PERM_NAME',
* // 'description' : 'ORPHANED_PERM_DESCRIPTION'
* // }
* // ]
* // }
* // ]
*
* @method groupOrphanedPermissions
* @static
* @param {Object} permissions The list of permissions with group
* permissions and eventually orphaned permission not attached to any group
* @return {Object} The same list of permissions except that orphaned
* permissions are extracted into a generic group
* @throws {TypeError} An error if permissions is not a valid array
*/
module.exports.groupOrphanedPermissions = function(permissions) {
if (!permissions || Object.prototype.toString.call(permissions) !== '[object Array]')
throw new TypeError('permissions must be a valid array');
var formattedPermissions = [];
var orphanedGroup = {
label: 'CORE.PERMISSIONS.GROUP_OTHERS',
permissions: []
};
var groupLabelList = [];
for (var i = 0; i < permissions.length; i++) {
// Orphaned permission
if (permissions[i].id)
orphanedGroup.permissions.push(permissions[i]);
// Group permission
else if (permissions[i].label) {
var index = groupLabelList.indexOf(permissions[i].label);
if (index == -1) {
// label treated for first time
groupLabelList.push(permissions[i].label);
formattedPermissions.push(permissions[i]);
} else {
formattedPermissions[index].permissions =
formattedPermissions[index].permissions.concat(permissions[i].permissions);
}
}
}
if (orphanedGroup.permissions.length)
formattedPermissions.push(orphanedGroup);
return formattedPermissions;
};
/**
* Builds the list of scopes.
*
* @method buildScopes
* @static
* @param {Object} entities Entities to build scopes from
* @param {Array} plugins The list of plugins
* @return {Array} The list of generated scopes
*/
module.exports.buildScopes = function(entities, plugins, callback) {
var scopes = this.generateEntityScopes(entities);
// Get plugin's scopes
if (plugins) {
plugins.forEach(function(plugin) {
if (plugin.webServiceScopes)
scopes = scopes.concat(plugin.webServiceScopes);
});
}
return scopes;
};
/**
* Builds the list of permissions.
*
* add/delete/update permissions are generated for each entity and get/update/delete permissions are
* created for each group.
*
* Orphaned permissions are grouped in a generic group of permissions.
*
* @method buildPermissions
* @static
* @async
* @param {Object} entities Entities to build permissions from
* @param {Array} plugins The list of plugins
* @param {Function} callback Function to call when its done with :
* - **Error** An error if something went wrong
* - **Array** The list of generated persmissions
*/
module.exports.buildPermissions = function(entities, plugins, callback) {
var self = this;
var permissions = this.generateEntityPermissions(entities);
var groupProvider = new GroupProvider(storage.getDatabase());
// Get plugin's permissions
if (plugins) {
plugins.forEach(function(plugin) {
if (plugin.permissions) {
// Permission id must be prefixed by the name of the plugin
// Make sure it is
prefixPermissions(plugin.name, plugin.permissions);
permissions = permissions.concat(plugin.permissions);
}
});
}
groupProvider.generateGroupPermissions(function(error, groupsPermissions) {
if (error)
return callback(error);
permissions = permissions.concat(groupsPermissions);
permissions = self.groupOrphanedPermissions(permissions);
callback(null, permissions);
});
};