sails-permissions
Advanced tools
Comparing version 1.3.1 to 1.3.10-beta
@@ -106,2 +106,3 @@ var permissionPolicies = [ | ||
if (model.autoCreatedBy === false) return; | ||
if (model.meta.junctionTable) return; | ||
@@ -108,0 +109,0 @@ _.defaults(model.attributes, { |
@@ -29,13 +29,3 @@ /** | ||
index: true, | ||
notNull: true, | ||
/** | ||
* TODO remove enum and support permissions based on all controller | ||
* actions, including custom ones | ||
*/ | ||
enum: [ | ||
'create', | ||
'read', | ||
'update', | ||
'delete' | ||
] | ||
notNull: true | ||
}, | ||
@@ -42,0 +32,0 @@ |
@@ -16,3 +16,3 @@ /** | ||
var action = PermissionService.getMethod(req.method); | ||
var action = PermissionService.getAction(req.options); | ||
@@ -77,3 +77,2 @@ var body = req.body || req.query; | ||
var user = req.owner; | ||
var method = PermissionService.getMethod(req); | ||
var isResponseArray = _.isArray(_data); | ||
@@ -80,0 +79,0 @@ |
@@ -1,3 +0,1 @@ | ||
var actionUtil = require('sails/lib/hooks/blueprints/actionUtil'); | ||
/** | ||
@@ -8,19 +6,15 @@ * Query the Model that is being acted upon, and set it on the req object. | ||
var modelCache = sails.hooks['sails-permissions']._modelCache; | ||
req.options.modelIdentity = actionUtil.parseModel(req).identity; | ||
if (_.isEmpty(req.options.modelIdentity)) { | ||
return next(); | ||
} | ||
req.options.modelDefinition = sails.models[req.options.modelIdentity]; | ||
req.model = modelCache[req.options.modelIdentity]; | ||
req.options.modelDefinition = sails.models[req.options.model]; | ||
req.model = modelCache[req.options.model]; | ||
if (_.isObject(req.model) && !_.isEmpty(req.model.id)) { | ||
if (_.isObject(req.model) && !_.isUndefined(req.model.id)) { | ||
return next(); | ||
} | ||
sails.log.warn('Model [', req.options.modelIdentity, '] not found in model cache'); | ||
sails.log.warn('Model [', req.options.model, '] not found in model cache'); | ||
// if the model is not found in the cache for some reason, get it from the database | ||
Model.findOne({ identity: req.options.modelIdentity }) | ||
Model.findOne({ identity: req.options.model}) | ||
.then(function (model) { | ||
@@ -31,6 +25,6 @@ if (!_.isObject(model)) { | ||
if (!sails.config.permissions.allowUnknownModelDefinition) { | ||
return next(new Error('Model definition not found: '+ req.options.modelIdentity)); | ||
return next(new Error('Model definition not found: '+ req.options.model)); | ||
} | ||
else { | ||
model = sails.models[req.options.modelIdentity]; | ||
model = sails.models[req.options.model]; | ||
} | ||
@@ -37,0 +31,0 @@ } |
@@ -23,7 +23,6 @@ var Promise = require('bluebird'); | ||
module.exports = function (req, res, next) { | ||
var options = { | ||
var options = _.defaults({ | ||
model: req.model, | ||
method: req.method, | ||
user: req.user | ||
}; | ||
}, req.options); | ||
@@ -38,3 +37,3 @@ if (req.options.unknownModel) { | ||
sails.log.silly('PermissionPolicy:', permissions.length, 'permissions grant', | ||
req.method, 'on', req.model.name, 'for', req.user.username); | ||
PermissionService.getAction(options), 'on', req.model.name, 'for', req.user.username); | ||
@@ -50,47 +49,1 @@ if (!permissions || permissions.length === 0) { | ||
}; | ||
function bindResponsePolicy (req, res) { | ||
res._ok = res.ok; | ||
res.ok = _.bind(responsePolicy, { | ||
req: req, | ||
res: res | ||
}); | ||
} | ||
function responsePolicy (_data, options) { | ||
var req = this.req; | ||
var res = this.res; | ||
var user = req.owner; | ||
var method = PermissionService.getMethod(req); | ||
var data = _.isArray(_data) ? _data : [_data]; | ||
//sails.log('data', _data); | ||
//sails.log('options', options); | ||
// TODO search populated associations | ||
Promise.bind(this) | ||
.map(data, function (object) { | ||
return user.getOwnershipRelation(data); | ||
}) | ||
.then(function (results) { | ||
//sails.log('results', results); | ||
var permitted = _.filter(results, function (result) { | ||
return _.any(req.permissions, function (permission) { | ||
return permission.permits(result.relation, method); | ||
}); | ||
}); | ||
if (permitted.length === 0) { | ||
//sails.log('permitted.length === 0'); | ||
return res.send(404); | ||
} | ||
else if (_.isArray(_data)) { | ||
return res._ok(permitted, options); | ||
} | ||
else { | ||
res._ok(permitted[0], options); | ||
} | ||
}); | ||
} |
@@ -12,3 +12,3 @@ /** | ||
var relations = _.groupBy(permissions, 'relation'); | ||
var action = PermissionService.getMethod(req.method); | ||
var action = PermissionService.getAction(req.options); | ||
@@ -15,0 +15,0 @@ // continue if there exist role Permissions which grant the asserted privilege |
@@ -8,2 +8,12 @@ var Promise = require('bluebird'); | ||
}; | ||
var actionMap = { | ||
create: 'create', | ||
find: 'read', | ||
findOne: 'read', | ||
update: 'update', | ||
destroy: 'delete', | ||
populate: 'read', | ||
add: 'update', | ||
remove: 'update' | ||
}; | ||
@@ -31,4 +41,2 @@ var findRecords = require('sails/lib/hooks/blueprints/actions/find'); | ||
return function (object) { | ||
//sails.log('object', object); | ||
//sails.log('object.owner: ', object.owner, ', owner:', owner); | ||
return object.owner !== owner; | ||
@@ -51,3 +59,6 @@ }; | ||
ok: resolve, | ||
serverError: reject | ||
serverError: reject, | ||
// this isn't perfect, since it returns a 500 error instead of a 404 error | ||
// but it is better than crashing the app when a record doesn't exist | ||
notFound: reject | ||
}); | ||
@@ -61,3 +72,3 @@ }); | ||
* | ||
* @param options.method | ||
* @param options.action | ||
* @param options.model | ||
@@ -67,3 +78,3 @@ * @param options.user | ||
findModelPermissions: function (options) { | ||
var action = PermissionService.getMethod(options.method); | ||
var action = PermissionService.getAction(options); | ||
var permissionCriteria = { | ||
@@ -164,3 +175,3 @@ model: options.model.id, | ||
return [ | ||
'User', options.user.email, 'is not permitted to', options.method, options.model.globalId | ||
'User', options.user.email, 'is not permitted to', options.action, options.model.identity | ||
].join(' '); | ||
@@ -170,6 +181,14 @@ }, | ||
/** | ||
* Given an action, return the CRUD method it maps to. | ||
* Given a request, return the CRUD action or controller action it maps | ||
* to. | ||
* | ||
* @param req.options | ||
* | ||
* If a standard blueprint action, then translate that blueprint action into a | ||
* CRUD action. If a custom controller action, then return the name of that action as-is. | ||
*/ | ||
getMethod: function (method) { | ||
return methodMap[method]; | ||
getAction: function (options) { | ||
var action = actionMap[options.action] || options.action; | ||
return action; | ||
}, | ||
@@ -229,3 +248,6 @@ | ||
* @param options {permission object, or array of permissions objects} | ||
* @param options.role {string} - the role name that the permission is associated with | ||
* @param options.role {string} - the role name that the permission is associated with, | ||
* either this or user should be supplied, but not both | ||
* @param options.user {string} - the user than that the permission is associated with, | ||
* either this or role should be supplied, but not both | ||
* @param options.model {string} - the model name that the permission is associated with | ||
@@ -244,9 +266,14 @@ * @param options.action {string} - the http action that the permission allows | ||
var ok = Promise.map(permissions, function (permission) { | ||
return Model.findOne({name: permission.model}) | ||
.then(function (model) { | ||
var findRole = permission.role ? Role.findOne({name: permission.role}) : null; | ||
var findUser = permission.user ? User.findOne({username: permission.user}) : null; | ||
return Promise.all([findRole, findUser, Model.findOne({name: permission.model})]) | ||
.spread(function (role, user, model) { | ||
permission.model = model.id; | ||
return Role.findOne({name: permission.role}) | ||
.then(function (role) { | ||
if (role && role.id) { | ||
permission.role = role.id; | ||
}); | ||
} else if (user && user.id) { | ||
permission.user = user.id; | ||
} else { | ||
return Promise.reject(new Error('no role or user specified')); | ||
} | ||
}); | ||
@@ -278,4 +305,4 @@ }); | ||
return Role.findOne({name: rolename}).populate('users').then(function (role) { | ||
User.find({username: usernames}).then(function (users) { | ||
role.users.add(users); | ||
return User.find({username: usernames}).then(function (users) { | ||
role.users.add(_.pluck(users, 'id')); | ||
return role.save(); | ||
@@ -314,3 +341,4 @@ }); | ||
* @param options | ||
* @param options.role {string} - the name of the role related to the permission | ||
* @param options.role {string} - the name of the role related to the permission. This, or options.user should be set, but not both. | ||
* @param options.user {string} - the name of the user related to the permission. This, or options.role should be set, but not both. | ||
* @param options.model {string} - the name of the model for the permission | ||
@@ -321,11 +349,23 @@ * @param options.action {string} - the name of the action for the permission | ||
revoke: function (options) { | ||
var ok = Promise.all([Role.findOne({name: options.role}), Model.findOne({name: options.model})]); | ||
ok = ok.then(function (result) { | ||
var role = result[0]; | ||
var model = result[1]; | ||
return Permission.destroy({role: role.id, | ||
var findRole = options.role ? Role.findOne({name: options.role}) : null; | ||
var findUser = options.user ? User.findOne({username: options.user}) : null; | ||
var ok = Promise.all([findRole, findUser, Model.findOne({name: options.model})]); | ||
ok = ok.spread(function (role, user, model) { | ||
var query = { | ||
model: model.id, | ||
action: options.action, | ||
relation: options.relation | ||
}); | ||
}; | ||
if (role && role.id) { | ||
query.role = role.id; | ||
} else if (user && user.id) { | ||
query.user = user.id; | ||
} else { | ||
return Promise.reject(new Error('You must provide either a user or role to revoke the permission from')); | ||
} | ||
return Permission.destroy(query); | ||
}); | ||
@@ -332,0 +372,0 @@ |
{ | ||
"name": "sails-permissions", | ||
"version": "1.3.1", | ||
"version": "1.3.10-beta", | ||
"description": "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.", | ||
@@ -42,3 +42,3 @@ "main": "api/hooks/sails-permissions.js", | ||
"request": "^2.58.0", | ||
"sails": ">0.10.0", | ||
"sails": "balderdashy/sails", | ||
"sails-disk": "^0.10.7", | ||
@@ -56,5 +56,2 @@ "supertest": "^0.15.0" | ||
}, | ||
"peerDependencies": { | ||
"sails-auth": ">=1.3" | ||
}, | ||
"engines": { | ||
@@ -61,0 +58,0 @@ "node": ">= 0.10", |
@@ -80,34 +80,2 @@ var assert = require('assert'); | ||
describe('#getMethod()', function () { | ||
it ('should return \'create\' if POST request', function(done) { | ||
assert.equal(sails.services.permissionservice.getMethod('POST'), 'create'); | ||
done(); | ||
}); | ||
it ('should return \'update\' if PUT request', function(done) { | ||
assert.equal(sails.services.permissionservice.getMethod('PUT'), 'update'); | ||
done(); | ||
}); | ||
it ('should return \'read\' if GET request', function(done) { | ||
assert.equal(sails.services.permissionservice.getMethod('GET'), 'read'); | ||
done(); | ||
}); | ||
it ('should return \'delete\' if DELETE request', function(done) { | ||
assert.equal(sails.services.permissionservice.getMethod('DELETE'), 'delete'); | ||
done(); | ||
}); | ||
}); | ||
describe('#hasPassingCriteria()', function () { | ||
@@ -226,4 +194,4 @@ | ||
assert(role && role.id); | ||
done(); | ||
}); | ||
}) | ||
.done(done, done); | ||
}); | ||
@@ -255,6 +223,35 @@ | ||
assert(permission && permission.id); | ||
done(); | ||
}); | ||
}) | ||
.done(done, done); | ||
}); | ||
it ('should grant a permission directly to a user', function (done) { | ||
var permissionModelId; | ||
// find any existing permission for this action, and delete it | ||
Model.findOne({name: 'Permission'}).then(function (permissionModel) { | ||
permissionModelId = permissionModel.id; | ||
return Permission.destroy({action: 'create', model: permissionModelId, relation: 'role'}); | ||
}) | ||
.then(function (destroyed) { | ||
// make sure we actually destroyed it | ||
return Permission.find({action: 'create', relation: 'role', model: permissionModelId }); | ||
}) | ||
.then(function (permission) { | ||
assert.equal(permission.length, 0); | ||
// create a new permission | ||
var newPermissions = [{user: 'admin', model: 'Permission', action: 'create', relation: 'role', criteria: { where: { x: 1}, blacklist: ['y'] }}, | ||
{user: 'admin', model: 'Role', action: 'update', relation: 'role', criteria: { where: { x: 1}, blacklist: ['y'] }}]; | ||
return sails.services.permissionservice.grant(newPermissions); | ||
}) | ||
.then(function (perm) { | ||
// verify that it was created | ||
return Permission.findOne({action: 'create', relation: 'role', model: permissionModelId}) | ||
}) | ||
.then(function (permission) { | ||
assert(permission && permission.id); | ||
}) | ||
.done(done, done); | ||
}); | ||
it ('should revoke a permission', function (done) { | ||
@@ -265,7 +262,7 @@ | ||
permissionModelId = permissionModel.id; | ||
return Permission.find({action: 'create', relation: 'role', model: permissionModelId }); | ||
return Permission.find({action: 'create', relation: 'role', model: permissionModelId}); | ||
}) | ||
.then(function (permission) { | ||
assert.equal(permission.length, 1); | ||
return sails.services.permissionservice.revoke({role: 'fakeRole', model: 'Permission', relation: 'role', action: 'create'}); | ||
return sails.services.permissionservice.revoke({user: 'admin', model: 'Permission', relation: 'role', action: 'create'}); | ||
}) | ||
@@ -277,3 +274,33 @@ .then(function () { | ||
assert.equal(permission.length, 0); | ||
done(); | ||
}) | ||
.done(done, done); | ||
}); | ||
it ('should not revoke a permission if no user or role is supplied', function (done) { | ||
var newPermissions = [{user: 'admin', model: 'Permission', action: 'create', relation: 'role', criteria: { where: { x: 1}, blacklist: ['y'] }}, | ||
{user: 'admin', model: 'Role', action: 'update', relation: 'role', criteria: { where: { x: 1}, blacklist: ['y'] }}]; | ||
return sails.services.permissionservice.grant(newPermissions) | ||
.then(function() { | ||
// make sure there is already an existing permission for this case | ||
Model.findOne({name: 'Permission'}).then(function (permissionModel) { | ||
permissionModelId = permissionModel.id; | ||
return Permission.find({action: 'create', relation: 'role', model: permissionModelId}); | ||
}) | ||
.then(function (permission) { | ||
assert.equal(permission.length, 1); | ||
return sails.services.permissionservice.revoke({model: 'Permission', relation: 'role', action: 'create'}); | ||
}) | ||
.catch(function (err) { | ||
assert.equal(err.message, 'You must provide either a user or role to revoke the permission from'); | ||
}) | ||
.then(function () { | ||
return Permission.find({action: 'create', relation: 'role', model: permissionModelId }); | ||
}) | ||
.then(function (permission) { | ||
assert.equal(permission.length, 1); | ||
}) | ||
.done(done, done); | ||
}); | ||
@@ -283,2 +310,25 @@ }); | ||
}); | ||
describe('#getAction', function () { | ||
describe('CRUD actions', function () { | ||
it('@findone: should return the "read" action', function () { | ||
assert.equal(PermissionService.getAction({ action: 'findOne' }), 'read'); | ||
}); | ||
it('@find: should return the "read" action', function () { | ||
assert.equal(PermissionService.getAction({ action: 'find' }), 'read'); | ||
}); | ||
it('@create: should return the "create" action', function () { | ||
assert.equal(PermissionService.getAction({ action: 'create' }), 'create'); | ||
}); | ||
}); | ||
describe('custom actions', function () { | ||
it('@upload: should return the "upload" action', function () { | ||
assert.equal(PermissionService.getAction({ action: 'upload' }), 'upload'); | ||
}); | ||
it('@download: should return the "download" action', function () { | ||
assert.equal(PermissionService.getAction({ action: 'upload' }), 'upload'); | ||
}); | ||
}); | ||
}) | ||
//TODO: add unit tests for #findTargetObjects() | ||
@@ -285,0 +335,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
93271
7
2473
1