@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
JavaScript
/*
* Generate access policies.
*/
;
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;