UNPKG

happn-3

Version:

pub/sub api as a service using primus and mongo & redis or nedb, can work as cluster, single process or embedded using nedb

430 lines (364 loc) 12.9 kB
const CONSTANTS = require('../..').constants; const SD_EVENTS = CONSTANTS.SECURITY_DIRECTORY_EVENTS; const commons = require('happn-commons'); const util = commons.utils; const _ = commons._; const nodeUtil = require('util'); function SecurityGroups(opts) { this.log = opts.logger.createLogger('SecurityGroups'); this.log.$$TRACE('construct(%j)', opts); this.opts = opts; } const PermissionManager = require('./permissions'); SecurityGroups.prototype.initialize = util.maybePromisify(initialize); SecurityGroups.prototype.clearCaches = clearCaches; SecurityGroups.prototype.__attachPermissionsAll = nodeUtil.callbackify(__attachPermissionsAll); SecurityGroups.prototype.__upsertGroup = __upsertGroup; SecurityGroups.prototype.__validate = __validate; SecurityGroups.prototype.upsertGroupWithoutValidation = __upsertGroup; SecurityGroups.prototype.upsertGroup = util.maybePromisify(upsertGroup); SecurityGroups.prototype.deleteGroup = util.maybePromisify(deleteGroup); SecurityGroups.prototype.listGroups = util.maybePromisify(listGroups); SecurityGroups.prototype.getGroup = util.maybePromisify(getGroup); SecurityGroups.prototype.linkGroup = util.maybePromisify(linkGroup); SecurityGroups.prototype.unlinkGroup = util.maybePromisify(unlinkGroup); SecurityGroups.prototype.listPermissions = listPermissions; SecurityGroups.prototype.upsertPermission = upsertPermission; SecurityGroups.prototype.removePermission = removePermission; function initialize(config, securityService, callback) { var _this = this; _this.securityService = securityService; this.permissionManager = PermissionManager.create(config, 'group', this.happn, securityService); _this.cacheService = _this.happn.services.cache; _this.dataService = _this.happn.services.data; _this.utilsService = _this.happn.services.utils; _this.errorService = _this.happn.services.error; _this.cryptoService = _this.happn.services.crypto; _this.sessionService = _this.happn.services.session; if (!config.__cache_groups) { config.__cache_groups = { max: 5e3, maxAge: 0, }; } _this.__cache_groups = _this.cacheService.create('cache_security_groups', { type: 'LRU', cache: config.__cache_groups, }); if (config.persistPermissions !== false) return callback(); //inject the permissions data provider _this.dataService._insertDataProvider( 0, { name: 'volatile_permissions', provider: 'memory', settings: {}, patterns: ['/_SYSTEM/_SECURITY/_PERMISSIONS/*'], }, (e) => { if (e) return callback(e); _this.dataService.addDataProviderPatterns('/_SYSTEM/_SECURITY/_GROUP', [ '/_SYSTEM/_SECURITY/_PERMISSIONS/_*', ]); callback(); } ); } function clearCaches(whatHappnd, changedData) { if (whatHappnd == null) { this.__cache_groups.clear(); if (this.permissionManager) this.permissionManager.cache.clear(); return; } return new Promise((resolve, reject) => { try { if (whatHappnd === SD_EVENTS.UPSERT_GROUP || whatHappnd === SD_EVENTS.DELETE_GROUP) { const groupName = whatHappnd === SD_EVENTS.UPSERT_GROUP ? changedData.name : changedData.obj.name; this.__cache_groups.remove(groupName); if (this.permissionManager) this.permissionManager.cache.remove(groupName); } if ( (whatHappnd === SD_EVENTS.PERMISSION_UPSERTED || whatHappnd === SD_EVENTS.PERMISSION_REMOVED) && changedData.groupName ) { this.__cache_groups.remove(changedData.groupName); if (this.permissionManager) this.permissionManager.cache.remove(changedData.groupName); } return resolve(); } catch (e) { reject(e); } }); } function __validate(validationType, options, obj, callback) { if (validationType === 'user-group') { let optGroup = options[0]; var optUser = options[1]; this.getGroup(optGroup.name, (e, group) => { if (e) return callback(e); if (!group) return callback(new Error('validation error: group does not exist or has not been saved')); this.securityService.users.getUser(optUser.username, (e, user) => { if (e) return callback(e); if (!user) return callback(new Error('validation error: user does not exist or has not been saved')); callback(); }); }); return; } if (obj.name) this.securityService.validateName(obj.name, validationType); if (validationType === 'group') { if (options.parent) { if (!options.parent._meta.path) return callback( new Error( 'validation error: parent group path is not in your request, have you included the _meta?' ) ); //path, parameters, callback return this.dataService.get(options.parent._meta.path, {}, (e, result) => { if (e) return callback(e); if (!result) return callback( new Error('validation error: parent group does not exist: ' + options.parent._meta.path) ); this.securityService.checkOverwrite( validationType, obj, options.parent._meta.path + '/' + obj.name, obj.name, options, callback ); }); } return this.securityService.checkOverwrite( validationType, obj, '/_SYSTEM/_SECURITY/_GROUP/' + obj.name, obj.name, options, callback ); } return callback(new Error('Unknown validation type: ' + validationType)); } function __upsertGroup(group, options) { var groupPath; if (options.parent) groupPath = options.parent._meta.path + '/' + group.name; else groupPath = '/_SYSTEM/_SECURITY/_GROUP/' + group.name; let permissions = this.utilsService.clone(group.permissions); return new Promise((resolve, reject) => { this.dataService.upsert(groupPath, _.omit(group, 'permissions'), async (e, result) => { try { if (e) return reject(e); if (Object.keys(permissions || {}).length > 0) { await this.permissionManager.upsertMultiplePermissions(group.name, permissions); } this.log.debug(`group upserted: ${group.name}`); } catch (e) { reject(e); return; } resolve(_.merge(this.securityService.serialize('group', result), { permissions })); }); }); } function upsertGroup(group, options, callback) { if (typeof options === 'function') callback = options; if (typeof group !== 'object' || group == null) return callback(new Error('group is null or not an object')); this.__validate('group', options, group, async (e) => { if (e) return callback(e); try { let upserted = await this.__upsertGroup(group, options); this.securityService.dataChanged( CONSTANTS.SECURITY_DIRECTORY_EVENTS.UPSERT_GROUP, upserted, null, () => { callback(null, upserted); } ); } catch (e) { callback(e); } }); } function deleteGroup(group, options, callback) { if (typeof options === 'function') callback = options; if (typeof group !== 'object' || group == null) return callback(new Error('group is null or not an object')); this.getGroup(group.name, {}, (e, group) => { if (e) return callback(e); if (!group) return callback(new Error('group you are deleting does not exist')); try { var deletePath = '/_SYSTEM/_SECURITY/_PERMISSIONS/' + group.name + '/*'; this.dataService.remove(deletePath, {}, (e, permissionsDeleteResults) => { if (e) return callback(e); var deletePath = '/_SYSTEM/_SECURITY/_USER/*/_USER_GROUP/' + group.name; this.dataService.remove(deletePath, {}, (e, userGroupDeleteResults) => { if (e) return callback(e); deletePath = '/_SYSTEM/_SECURITY/_GROUP/' + group.name; this.dataService.remove(deletePath, {}, (e, groupDeleteResults) => { if (e) return callback(e); this.log.debug(`group deleted: ${group.name}`); var deleted = { removed: groupDeleteResults.data.removed, obj: group, links: userGroupDeleteResults, permissions: permissionsDeleteResults, }; this.securityService.dataChanged( CONSTANTS.SECURITY_DIRECTORY_EVENTS.DELETE_GROUP, deleted, null, () => { callback(null, deleted); } ); }); }); }); } catch (err) { callback(err); } }); } function listGroups(groupName, options, callback) { if (typeof options === 'function') { callback = options; options = {}; } if (typeof groupName === 'function') { callback = groupName; groupName = '*'; options = {}; } if (!options) options = {}; if (!options.criteria) options.criteria = {}; if (!options.sort) options.sort = { path: 1, }; let searchParameters = { criteria: options.criteria, sort: options.sort, options: {}, }; if (options.limit) searchParameters.options.limit = options.limit; if (options.skip) searchParameters.options.skip = options.skip; if (options.collation) searchParameters.options.collation = options.collation; if (groupName[groupName.length - 1] !== '*') groupName += '*'; var searchPath = '/_SYSTEM/_SECURITY/_GROUP/' + groupName; if (options.count) { this.dataService.count(searchPath, searchParameters, (e, results) => { if (e) return callback(e); callback(null, results.data); }); return; } try { this.dataService.get(searchPath, searchParameters, (e, groups) => { if (e) return callback(e); var extracted = this.dataService.extractData(groups); if (options.skipPermissions) { callback(null, extracted); return; } this.__attachPermissionsAll(extracted, callback); }); } catch (e) { callback(e); } } function getGroup(groupName, options, callback) { if (typeof options === 'function') callback = options; if (!groupName || groupName.indexOf('*') > 0) return callback(new Error('invalid group name: ' + groupName)); var cachedGroup = this.__cache_groups.get(groupName); if (cachedGroup) return callback(null, cachedGroup); this.dataService.get( '/_SYSTEM/_SECURITY/_GROUP/' + groupName, { sort: { path: 1, }, }, (e, result) => { if (e) return callback(e); if (result == null) return callback(null, null); var group = result.data; this.permissionManager .attachPermissions(group) .then((attached) => { this.__cache_groups.set(groupName, attached, { clone: false }); callback(null, attached); }) .catch(callback); } ); } function linkGroup(group, user, options, callback) { if (typeof options === 'function') { callback = options; options = {}; } this.__validate('user-group', [group, user], options, (e) => { if (e) return callback(e); this.dataService.upsert( '/_SYSTEM/_SECURITY/_USER/' + user.username + '/_USER_GROUP/' + group.name, options, (e, result) => { if (e) return callback(e); var upserted = this.securityService.serialize('user-group', result, options); this.securityService.dataChanged( CONSTANTS.SECURITY_DIRECTORY_EVENTS.LINK_GROUP, upserted, user, () => { callback(null, upserted); } ); } ); }); } function unlinkGroup(group, user, options, callback) { if (typeof options === 'function') callback = options; this.__validate('user-group', [group, user], null, (e) => { if (e) return callback(e); var groupLinkPath = '/_SYSTEM/_SECURITY/_USER/' + user.username + '/_USER_GROUP/' + group.name; this.dataService.remove(groupLinkPath, {}, (e, result) => { if (e) return callback(e); this.securityService.dataChanged( CONSTANTS.SECURITY_DIRECTORY_EVENTS.UNLINK_GROUP, { path: groupLinkPath, permissions: group.permissions }, user, () => { callback(null, result); } ); }); }); } function __attachPermissionsAll(groups) { var promises = []; groups.forEach((group) => { promises.push(this.permissionManager.attachPermissions(group)); }); return Promise.all(promises); } function listPermissions(groupName) { return this.permissionManager.listPermissions(groupName); } function removePermission(groupName, path, action) { return this.permissionManager.removePermission(groupName, path, action); } function upsertPermission(groupName, path, action, authorized) { return this.permissionManager.upsertPermission(groupName, path, action, authorized); } module.exports = SecurityGroups;