sails-permissions
Advanced tools
Comparing version 0.10.17 to 0.10.19
@@ -14,6 +14,8 @@ module.exports = function (sails) { | ||
initialize: function (next) { | ||
installModelOwnership(sails.models); | ||
sails.after('hook:orm:loaded', function () { | ||
sails.log('initializing sails-permissions...'); | ||
var models = _.filter(sails.controllers, function (controller, name) { | ||
var sailsModels = _.filter(sails.controllers, function (controller, name) { | ||
var model = sails.models[name]; | ||
@@ -26,4 +28,4 @@ return model && model.globalId && model.identity; | ||
var count = models ? models.length : 0; | ||
if (count < models.length) { | ||
sails.log('Expecting', models.length, 'models, found', count); | ||
if (count < sailsModels.length) { | ||
sails.log('Expecting', sailsModels.length, 'models, found', count); | ||
sails.log('Installing fixtures'); | ||
@@ -33,3 +35,2 @@ return initializeFixtures(next); | ||
logModelOwnership(models); | ||
next(); | ||
@@ -94,2 +95,15 @@ }) | ||
function logModelOwnership (models) { | ||
var missingOwner = _.filter(models, function (model) { | ||
return _.isUndefined(sails.models[model.identity].attributes.owner); | ||
}); | ||
var warnings = _.difference(_.pluck(missingOwner, 'name'), ignoreModels); | ||
if (warnings.length) { | ||
sails.log.warn('these models do not support ownership, and are unusable by the permissions-api:', warnings); | ||
} | ||
} | ||
function installModelOwnership (models) { | ||
if (sails.config.permissions.enableOwnership === false) return; | ||
var ignoreModels = [ | ||
@@ -103,11 +117,16 @@ 'BackboneModel', | ||
]; | ||
var missingOwner = _.filter(models, function (model) { | ||
return _.isUndefined(sails.models[model.identity].attributes.owner); | ||
_.each(models, function (model) { | ||
if (model.enableOwnership === false) return; | ||
if (_.contains(ignoreModels, model.globalId)) return; | ||
sails.log('enabling ownership on', model.globalId); | ||
_.defaults(model.attributes, { | ||
owner: { | ||
model: 'User', | ||
index: true, | ||
notNull: true | ||
} | ||
}); | ||
}); | ||
var warnings = _.difference(_.pluck(missingOwner, 'name'), ignoreModels); | ||
if (warnings.length) { | ||
sails.log.warn('the following models do not support ownership:', warnings); | ||
} | ||
sails.log.warn('these models do not support the permissions-api'); | ||
} |
@@ -17,2 +17,4 @@ /** | ||
enableOwnership: false, | ||
attributes: { | ||
@@ -19,0 +21,0 @@ name: { |
@@ -40,13 +40,34 @@ var EventEmitter = require('events').EventEmitter; | ||
/** | ||
* @param action.method | ||
* @param action.attribute | ||
* @param ownership | ||
* @param method | ||
* | ||
* permission.grant is a permission mapping for a particular model, e.g. | ||
* { | ||
* owner: { | ||
* '*': true | ||
* }, | ||
* role: { | ||
* '*': true, | ||
* update: false | ||
* }, | ||
* none: { | ||
* // '*': false by default | ||
* } | ||
* | ||
* } | ||
*/ | ||
permits: function (action) { | ||
return _.find([ 'owner', 'role', 'others' ], function (ownership) { | ||
return this.grant[ownership][action.attribute][action.method]; | ||
}, this); | ||
permits: function (ownership, method) { | ||
var permittedOwnership = _.dot(this.grant, [ ownership, '*' ]); | ||
var permittedMethod = _.dot(this.grant, [ ownership, action ]); | ||
return permittedMethod || (permittedOwnership && permittedMethod !== false); | ||
} | ||
}, | ||
beforeValidate: function (permission, next) { | ||
/** | ||
* Perform deep-validation of grant object | ||
*/ | ||
afterValidate: function (permission, next) { | ||
_.isObject(permission.grant) || (permission.grant = { }); | ||
_.defaults(permission.grant, { | ||
@@ -60,6 +81,6 @@ owner: { }, | ||
var valid = _.similar(Permission.grantTemplate, permission.grant, emitter); | ||
emitter.on('invalid:keys', function (error) { | ||
emitter.once('invalid:keys', function (error) { | ||
next(new Error('the grant object is missing a required key')); | ||
}); | ||
emitter.on('invalid:value', function (error) { | ||
emitter.once('invalid:value', function (error) { | ||
next(new Error('grant key ' + error.key + ' is invalid')); | ||
@@ -66,0 +87,0 @@ }); |
@@ -19,3 +19,4 @@ /** | ||
index: true, | ||
notNull: true | ||
notNull: true, | ||
unique: true | ||
}, | ||
@@ -58,3 +59,3 @@ children: { | ||
grant: function (permissions) { | ||
var grant = PermissionService.createGrant(permisisons); | ||
var grant = PermissionService.createGrant(permissions); | ||
@@ -95,7 +96,6 @@ return Permission | ||
return permission.permits(action.method, action.attribute); | ||
return permission.permits(action); | ||
}); | ||
} | ||
}, | ||
} | ||
}; |
@@ -10,4 +10,65 @@ var _ = require('lodash'); | ||
via: 'users' | ||
}, | ||
/** | ||
* Returns this user's ownership relation to an object. If the user owns the | ||
* specified object directly, return 'owner'. If the user shares a | ||
* role with the owner, return 'role'. If the user is not the owner | ||
* and has no roles in common with the owner of the object, then return | ||
* 'none'. | ||
* | ||
* @return Promise that resolves to 0, 1, or -1 | ||
*/ | ||
getOwnershipRelation: function (object) { | ||
if (!object.owner) { | ||
return -1; | ||
} | ||
if (object.owner === this.id) { | ||
return 0; | ||
} | ||
// query roles for this and object.owner and see if there are any in | ||
// common | ||
User.findOne(this.id).populate('roles').bind(this) | ||
.then(function (user) { | ||
this.roles = user.roles; | ||
return User.findOne(object.owner).populate('roles'); | ||
}) | ||
.then(function (owner) { | ||
var intersection = _.intersection( | ||
_.pluck(this.roles, 'id'), | ||
_.pluck(owner.roles, 'id') | ||
); | ||
if (intersection.length > 0) { | ||
return 1; | ||
} | ||
else { | ||
return -1; | ||
} | ||
}); | ||
} | ||
}, | ||
/** | ||
* Attach default Role to a new User | ||
*/ | ||
afterCreate: function (_user, next) { | ||
var user; | ||
sails.log('user afterCreate'); | ||
User.findOne(_user.id) | ||
.populate('roles') | ||
.then(function (_user) { | ||
user = _user; | ||
return Role.findOne({ name: 'registered' }); | ||
}) | ||
.then(function (role) { | ||
user.roles.add(role.id); | ||
return user.save(); | ||
}) | ||
.then(function (updatedUser) { | ||
sails.log('role "registered" attached to user', user.username); | ||
next(); | ||
}) | ||
.catch(next); | ||
} | ||
}); |
@@ -0,1 +1,3 @@ | ||
var actionUtil = require('sails/lib/hooks/blueprints/actionUtil'); | ||
/** | ||
@@ -5,16 +7,19 @@ * Query the Model that is being acted upon, and set it on the req object. | ||
module.exports = function ModelPolicy (req, res, next) { | ||
var modelName = ModelService.getTargetModelName(req); | ||
req.options.modelName = actionUtil.parseModel(req).identity; | ||
Model.findOne({ identity: modelName }) | ||
if (_.contains(sails.config.permissions.ignoreOwnership, req.options.modelName)) { | ||
req.options.ignoreOwnership = true; | ||
return next(); | ||
} | ||
Model.findOne({ identity: req.options.modelName }) | ||
.then(function (model) { | ||
if (_.isObject(model)) { | ||
// TODO probably only one of these is needed | ||
req.model = model; | ||
next(); | ||
if (!_.isObject(model)) { | ||
return next(new Error('Model definition not found: '+ req.options.modelName)); | ||
} | ||
else { | ||
next(new Error('Model definition not found in datastore: '+ modelName)); | ||
} | ||
req.model = model; | ||
next(); | ||
}) | ||
.catch(next); | ||
}; |
/** | ||
* Ensure that the 'owner' property of an Object is set upon creation. | ||
* | ||
* @param {Object} req | ||
* @param {Object} res | ||
* @param {Function} next | ||
*/ | ||
module.exports = function OwnerPolicy (req, res, next) { | ||
if (!req.user || !req.user.id) next(new Error('req.user is not set')); | ||
if (!req.user || !req.user.id) return next(new Error('req.user is not set')); | ||
req.owner = req.user.id; | ||
sails.log(req.query); | ||
sails.log(req.params); | ||
// set owner on newly created object | ||
if (req.options.action === 'create') { | ||
if (req.options.modelName === 'user') { | ||
req.body = req.body || { }; | ||
req.body.owner = req.owner; | ||
req.body.id = req.user.id; | ||
req.query.id = req.user.id; | ||
if (!_.isEmpty(req.params.id) && req.params.id != req.user.id) { | ||
return res.send(400, 'you cannot query another user'); | ||
} | ||
} | ||
else if (!req.options.ignoreOwnership) { | ||
req.body = req.body || { }; | ||
req.body.owner = req.user.id; | ||
req.query.owner = req.user.id; | ||
req.params.owner = req.user.id; | ||
} | ||
next(); | ||
/* | ||
User.findOne(req.user.id) | ||
.populate('roles') | ||
.then(function (user) { | ||
if (!user) { | ||
return next(new Error('could not find user with id "' + req.user.id + '" in database')); | ||
} | ||
req.owner = user; | ||
next(); | ||
}) | ||
.catch(next); | ||
*/ | ||
}; |
var pluralize = require('pluralize'); | ||
module.exports = { | ||
/** | ||
* Return the type of model acted upon by this request. | ||
*/ | ||
getTargetModelName: function (req) { | ||
@@ -5,0 +8,0 @@ if (_.isString(req.options.alias)) { |
@@ -0,5 +1,13 @@ | ||
global._ = require('lodash'); | ||
global._.mixin(require('a.b')); | ||
module.exports.permissions = { | ||
adminEmail: process.env.ADMIN_EMAIL, | ||
adminPassword: process.env.ADMIN_PASSWORD | ||
adminPassword: process.env.ADMIN_PASSWORD, | ||
ignoreOwnership: [ | ||
'model', | ||
'backbonemodel' | ||
] | ||
}; | ||
@@ -14,6 +14,8 @@ module.exports = require('sails-generate-entities')({ | ||
'config/permissions.js' | ||
'config/permissions.js', | ||
'config/blueprints.js' | ||
], | ||
classes: [ | ||
'api/services/ModelService.js' | ||
'api/services/ModelService.js', | ||
'api/services/PermissionService.js' | ||
], | ||
@@ -23,4 +25,5 @@ functions: [ | ||
'api/policies/ModelPolicy.js', | ||
'api/policies/OwnerPolicy.js' | ||
'api/policies/OwnerPolicy.js', | ||
'api/policies/PermissionPolicy.js' | ||
] | ||
}); |
{ | ||
"name": "sails-permissions", | ||
"version": "0.10.17", | ||
"version": "0.10.19", | ||
"description": "Comprehensive sails.js user permission/privilege framework that allows granting and restricting access to models and attributes based on object ownership and relationships with other users and roles", | ||
@@ -38,2 +38,3 @@ "main": "index.js", | ||
"dependencies": { | ||
"a.b": "^1.0.1", | ||
"bluebird": "^2.3.6", | ||
@@ -40,0 +41,0 @@ "congruence": "^1.6.4", |
@@ -49,14 +49,5 @@ # <img src="http://cdn.tjw.io/images/sails-logo.png" height='43px' />-permissions | ||
### 3. extend models to support ownership | ||
Currently, sails-permissions does not validated the permissions of associations. Until this | ||
is implemented, ensure that `sails.config.blueprints.populate` is set to `false`. | ||
#### api/models/Foo.js | ||
```js | ||
var Foo = { | ||
// model definition | ||
}; | ||
_.merge(Foo, require('sails-permissions/src/models/HasOwner.js')); | ||
module.exports = Foo; | ||
``` | ||
## License | ||
@@ -63,0 +54,0 @@ MIT |
Sorry, the diff of this file is not supported yet
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances 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
42361
1183
8
63
2
+ Addeda.b@^1.0.1