UNPKG

@inspire-platform/sails-hook-permissions

Version:

Comprehensive user permissions and entitlements system for sails.js and Waterline. Supports user authentication with passport.js, role-based permissioning, object ownership, and row-level security.

327 lines (268 loc) 10 kB
/* * Generate access policies. */ 'use strict'; var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); var _ = require('lodash'); var helpers = require('./helpers'); var accessPolicy = { /** * Format and return user access policy. * * @param {Object} user User object * @param {Object} options Options object * @param {Object} options.modelCriteria Criteria to pass to findModels. * @returns {Promise} */ user: function getUserPolicy(user) { var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; return new Promise(function (resolve) { var defOptions = { modelCriteria: {} }; _.defaults(options, defOptions); return Promise.all([helpers.findModels(options.modelCriteria), helpers.findUserPermissions(user, { populate: ['criteria', 'objectFilters'] })]).then(function (result) { var _result = _slicedToArray(result, 2); var models = _result[0]; var permissions = _result[1]; // map all model ids var modelMap = {}; // policy to return var policy = {}; // loop all models _.forEach(models, function (model) { // map model id modelMap[model.id] = model.identity; // arrange by model identity policy[model.identity] = _.pick(model, ['id', 'name', 'identity']); // init actions policy[model.identity].actions = { create: false, read: false, update: false, 'delete': false }; // init criteria policy[model.identity].criteria = { create: [], read: [], update: [], 'delete': [] }; // init object filters policy[model.identity].objectFilters = { create: [], read: [], update: [], 'delete': [] }; }); // loop all permissions _.forEach(permissions, function (permission) { // look up model property in map var modelProperty = modelMap[permission.model]; // set permission to true on model policy[modelProperty].actions[permission.action] = true; // permission has criteria? if (permission.criteria.length) { // yes, append each onto criteria for the action _.map(permission.criteria, 'where').forEach(function (o) { policy[modelProperty].criteria[permission.action].push(o); }); } // permission has object filters? if (permission.objectFilters.length) { // yes, append each onto object filters for the action _.map(permission.objectFilters, function (o) { return _.pick(o, 'objectId'); }).forEach(function (o) { // does NOT already exist on target? if (_.findIndex(policy[modelProperty].objectFilters[permission.action], { 'objectId': o.objectId }) < 0) { // not exists, push it policy[modelProperty].objectFilters[permission.action].push(o); } }); } }); return resolve(policy); }); }); }, /** * Check policy for grant of specific model action. * * If criteria and/or object filters are set, these are resolved in an object * so the implementing code can further decide how to handle special conditions. * * @param {Object} policy Policy object * @param {String} model Model id * @param {String} action Action name * @returns {Promise} */ grantCheck: function grantCheck(policy, model, action) { return new Promise(function (resolve, reject) { // model policy exists in policy? if (false === model in policy) { return reject(new Error('Model "' + model + '" does not exist in policy.')); } // action exists in model policy? if (false === action in policy[model].actions) { return reject(new Error('The "' + accessPolicy + '" does not exist in the "' + model + '" model policy.')); } // have permission for model action? if (true === policy[model].actions[action]) { var special = {}; var modelPolicy = policy[model]; if (modelPolicy.criteria[action].length) { special.criteria = modelPolicy.criteria[action]; } if (modelPolicy.objectFilters[action].length) { special.objectFilters = modelPolicy.objectFilters[action]; } // any special conditions? if (false === _.isEmpty(special)) { return resolve(special); } else { // has full permissions to this action return resolve(true); } } // no permissions at all return resolve(false); }); }, /** * Build grant find criteria for model policy. * * @param {Object} policy Policy object * @param {String} model Model id * @returns {Promise} */ grantFindCriteria: function grantFindCriteria(policy, model) { return accessPolicy.grantCheck(policy, model, 'read').then(function (result) { var criteria = {}; var conditions = []; // start building criteria switch (true) { // full perm case result: break; // partial perm case _.isPlainObject(result): // any criteria? if (true === _.has(result, 'criteria') && true === _.isArray(result.criteria) && 1 <= result.criteria.length) { // yes, more than one? if (result.criteria.length > 1) { // yes, condition is an or conditions.push({ or: result.criteria }); } else { // just one conditions.push(result.criteria[0]); } } // any object filters? if (true === _.has(result, 'objectFilters') && true === _.isArray(result.objectFilters) && 1 <= result.objectFilters.length) { // yes, more than one? if (result.objectFilters.length > 1) { // yes, condition is an or conditions.push({ or: result.objectFilters.map(function (o) { return { id: o.objectId }; }) }); } else { // just one conditions.push({ id: result.objectFilters[0].objectId }); } } break; // no perm, resolve false default: return false; } // determine final criteria if (conditions.length > 1) { // multiple conditions, it's an `and` criteria.where = { and: conditions }; } else if (conditions.length === 1) { // only one condition criteria.where = conditions[0]; } // resolve criteria return criteria; }); }, /** * Execute find on model using policy as filter. * * @param {Object} policy Policy object * @param {String} model Model id * @param {Object} options Options * @param {Object} options.customCriteria Valid Waterline query criteria * @returns {Promise} */ grantFind: function grantFind(policy, model) { var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; _.defaults(options, { customCriteria: {} }); return accessPolicy.grantFindCriteria(policy, model).then(function (grantCriteria) { // final criteria var criteria = {}; // have custom criteria? if (false === _.isEmpty(options.customCriteria)) { // any grant criteria? if (true === _.isEmpty(grantCriteria)) { // no, use custom criteria as is criteria = options.customCriteria; } else { // conditions var conditions = []; // use everything except `where` criteria = _.omit(options.customCriteria, 'where'); // if where exists, push onto the conditions if ('where' in options.customCriteria) { conditions.push(options.customCriteria.where); } // add grant criteria conditions.push(grantCriteria.where); // add conditions criteria.where = { and: conditions }; } } else { // use grant criteria as is criteria = grantCriteria; } // execute find return sails.models[model].find(criteria); }); }, /** * Find only whitelisted objects for model policy by applying permission criteria and object filters. * * If model policy grants full permissions, an empty array is returned. * * @param {Object} policy Policy object * @param {String} model Model id * @returns {Promise} */ grantFindWhiteList: function grantFindWhiteList(policy, model) { return accessPolicy.grantFindCriteria(policy, model).then(function (grantCriteria) { // any grant criteria? if (false === _.isEmpty(grantCriteria)) { // yes, white list exists... execute find return sails.models[model].find(grantCriteria); } else { // no, have full permissions... return empty array return []; } }); } }; module.exports = accessPolicy;