Fix the algorithm for Role.isInRole and ACL.checkAccess
This commit is contained in:
parent
4560ec0964
commit
82eeaeee6b
|
@ -40,7 +40,8 @@ var role = require('./role');
|
|||
var Role = role.Role;
|
||||
|
||||
/**
|
||||
* Schema for Scope which represents the permissions that are granted to client applications by the resource owner
|
||||
* Schema for Scope which represents the permissions that are granted to client
|
||||
* applications by the resource owner
|
||||
*/
|
||||
var ScopeSchema = {
|
||||
name: {type: String, required: true},
|
||||
|
@ -51,7 +52,8 @@ var ScopeSchema = {
|
|||
/**
|
||||
* Resource owner grants/delegates permissions to client applications
|
||||
*
|
||||
* For a protected resource, does the client application have the authorization from the resource owner (user or system)?
|
||||
* For a protected resource, does the client application have the authorization
|
||||
* from the resource owner (user or system)?
|
||||
*
|
||||
* Scope has many resource access entries
|
||||
* @type {createModel|*}
|
||||
|
@ -59,11 +61,14 @@ var ScopeSchema = {
|
|||
var Scope = loopback.createModel('Scope', ScopeSchema);
|
||||
|
||||
/**
|
||||
* System grants permissions to principals (users/applications, can be grouped into roles).
|
||||
* System grants permissions to principals (users/applications, can be grouped
|
||||
* into roles).
|
||||
*
|
||||
* Protected resource: the model data and operations (model/property/method/relation/…)
|
||||
* Protected resource: the model data and operations
|
||||
* (model/property/method/relation/…)
|
||||
*
|
||||
* For a given principal, such as client application and/or user, is it allowed to access (read/write/execute)
|
||||
* For a given principal, such as client application and/or user, is it allowed
|
||||
* to access (read/write/execute)
|
||||
* the protected resource?
|
||||
*
|
||||
*/
|
||||
|
@ -77,9 +82,11 @@ var ACLSchema = {
|
|||
accessType: String,
|
||||
|
||||
/**
|
||||
* ALARM - Generate an alarm, in a system dependent way, the access specified in the permissions component of the ACL entry.
|
||||
* ALARM - Generate an alarm, in a system dependent way, the access specified
|
||||
* in the permissions component of the ACL entry.
|
||||
* ALLOW - Explicitly grants access to the resource.
|
||||
* AUDIT - Log, in a system dependent way, the access specified in the permissions component of the ACL entry.
|
||||
* AUDIT - Log, in a system dependent way, the access specified in the
|
||||
* permissions component of the ACL entry.
|
||||
* DENY - Explicitly denies access to the resource.
|
||||
*/
|
||||
permission: String,
|
||||
|
@ -201,16 +208,13 @@ function resolvePermission(acls, req) {
|
|||
}
|
||||
|
||||
/*!
|
||||
* Check the LDL ACLs
|
||||
* @param {String} principalType
|
||||
* @param {*} principalId
|
||||
* Get the static ACLs from the model definition
|
||||
* @param {String} model The model name
|
||||
* @param {String} property The property/method/relation name
|
||||
* @param {String} accessType The access type
|
||||
*
|
||||
* @return {Object[]} An array of ACLs
|
||||
*/
|
||||
function getStaticACLs(model, property, accessType) {
|
||||
function getStaticACLs(model, property) {
|
||||
var modelClass = loopback.getModel(model);
|
||||
var staticACLs = [];
|
||||
if (modelClass && modelClass.settings.acls) {
|
||||
|
@ -271,7 +275,7 @@ ACL.checkPermission = function (principalType, principalId, model, property, acc
|
|||
accessType: accessType
|
||||
};
|
||||
|
||||
var acls = getStaticACLs(model, property, accessType);
|
||||
var acls = getStaticACLs(model, property);
|
||||
|
||||
var resolved = resolvePermission(acls, req);
|
||||
|
||||
|
@ -357,9 +361,10 @@ ACL.checkAccess = function (context, callback) {
|
|||
};
|
||||
|
||||
var effectiveACLs = [];
|
||||
var staticACLs = getStaticACLs(model.modelName, property, accessType);
|
||||
var staticACLs = getStaticACLs(model.modelName, property);
|
||||
|
||||
ACL.find({where: {model: model.modelName, property: propertyQuery, accessType: accessTypeQuery}}, function (err, acls) {
|
||||
ACL.find({where: {model: model.modelName, property: propertyQuery,
|
||||
accessType: accessTypeQuery}}, function (err, acls) {
|
||||
if (err) {
|
||||
callback && callback(err);
|
||||
return;
|
||||
|
@ -370,17 +375,20 @@ ACL.checkAccess = function (context, callback) {
|
|||
|
||||
acls.forEach(function (acl) {
|
||||
principals.forEach(function (principal) {
|
||||
if (principal.principalType === acl.pricipalType && principal.principalId === acl.principalId) {
|
||||
if (principal.principalType === acl.principalType
|
||||
&& String(principal.principalId) === String(acl.principalId)) {
|
||||
effectiveACLs.push(acl);
|
||||
} else if (acl.principalType === ACL.ROLE) {
|
||||
inRoleTasks.push(function (done) {
|
||||
Role.isInRole(acl.principalId,
|
||||
{principalType: principal.principalType, userId: context.userId, principalId: acl.principalId, model: model, id: id, property: property},
|
||||
{principalType: principal.principalType,
|
||||
principalId: principal.principalId,
|
||||
userId: context.userId,
|
||||
model: model, id: id, property: property},
|
||||
function (err, inRole) {
|
||||
if(!err && inRole) {
|
||||
effectiveACLs.push(acl);
|
||||
}
|
||||
|
||||
done(err, acl);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
var loopback = require('../loopback');
|
||||
var debug = require('debug')('role');
|
||||
|
||||
// Role model
|
||||
var RoleSchema = {
|
||||
|
@ -94,7 +95,8 @@ var Role = loopback.createModel('Role', RoleSchema, {
|
|||
// Set up the connection to users/applications/roles once the model
|
||||
Role.once('dataSourceAttached', function () {
|
||||
Role.prototype.users = function (callback) {
|
||||
RoleMapping.find({where: {roleId: this.id, principalType: RoleMapping.USER}}, function (err, mappings) {
|
||||
RoleMapping.find({where: {roleId: this.id,
|
||||
principalType: RoleMapping.USER}}, function (err, mappings) {
|
||||
if (err) {
|
||||
callback && callback(err);
|
||||
return;
|
||||
|
@ -106,7 +108,8 @@ Role.once('dataSourceAttached', function () {
|
|||
};
|
||||
|
||||
Role.prototype.applications = function (callback) {
|
||||
RoleMapping.find({where: {roleId: this.id, principalType: RoleMapping.APPLICATION}}, function (err, mappings) {
|
||||
RoleMapping.find({where: {roleId: this.id,
|
||||
principalType: RoleMapping.APPLICATION}}, function (err, mappings) {
|
||||
if (err) {
|
||||
callback && callback(err);
|
||||
return;
|
||||
|
@ -118,7 +121,8 @@ Role.once('dataSourceAttached', function () {
|
|||
};
|
||||
|
||||
Role.prototype.roles = function (callback) {
|
||||
RoleMapping.find({where: {roleId: this.id, principalType: RoleMapping.ROLE}}, function (err, mappings) {
|
||||
RoleMapping.find({where: {roleId: this.id,
|
||||
principalType: RoleMapping.ROLE}}, function (err, mappings) {
|
||||
if (err) {
|
||||
callback && callback(err);
|
||||
return;
|
||||
|
@ -141,7 +145,8 @@ Role.EVERYONE = "$everyone"; // everyone
|
|||
/**
|
||||
* Add custom handler for roles
|
||||
* @param role
|
||||
* @param resolver The resolver function decides if a principal is in the role dynamically
|
||||
* @param resolver The resolver function decides if a principal is in the role
|
||||
* dynamically
|
||||
*
|
||||
* function(role, context, callback)
|
||||
*/
|
||||
|
@ -161,22 +166,27 @@ Role.registerResolver(Role.OWNER, function(role, context, callback) {
|
|||
}
|
||||
var modelClass = context.model;
|
||||
var id = context.id;
|
||||
var userId = context.userId;
|
||||
var userId = context.userId || context.principalId;
|
||||
isOwner(modelClass, id, userId, callback);
|
||||
});
|
||||
|
||||
function isOwner(modelClass, id, userId, callback) {
|
||||
function isUserClass(modelClass) {
|
||||
return modelClass === loopback.User ||
|
||||
modelClass.prototype instanceof loopback.User;
|
||||
}
|
||||
|
||||
function isOwner(modelClass, id, userId, callback) {
|
||||
debug('isOwner(): %s %s %s', modelClass && modelClass.modelName, id, userId);
|
||||
// No userId is present
|
||||
if(!userId) {
|
||||
process.nextTick(function() {
|
||||
callback(null, false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var modelClassIsUserClass = modelClass.prototype instanceof loopback.User
|
||||
|| modelClass === loopback.User;
|
||||
|
||||
if(modelClassIsUserClass) {
|
||||
// Is the modelClass User or a subclass of User?
|
||||
if(isUserClass(modelClass)) {
|
||||
process.nextTick(function() {
|
||||
callback(null, id === userId);
|
||||
});
|
||||
|
@ -184,18 +194,28 @@ function isOwner(modelClass, id, userId, callback) {
|
|||
}
|
||||
|
||||
modelClass.findById(id, function(err, inst) {
|
||||
if(err) {
|
||||
callback && callback(err);
|
||||
if(err || !inst) {
|
||||
callback && callback(err, false);
|
||||
return;
|
||||
}
|
||||
debug('Model found: %j', inst);
|
||||
if(inst.userId || inst.owner) {
|
||||
callback && callback(null, (inst.userId || inst.owner) === userId);
|
||||
return;
|
||||
} else {
|
||||
// Try to follow belongsTo
|
||||
for(var r in modelClass.relations) {
|
||||
var rel = modelClass.relations[r];
|
||||
if(rel.type === 'belongsTo' && rel.model && rel.model.prototype instanceof loopback.User) {
|
||||
callback && callback(null, rel.foreignKey === userId);
|
||||
if(rel.type === 'belongsTo' && isUserClass(rel.modelTo)) {
|
||||
debug('Checking relation %s to %s: %j', r, rel.modelTo.modelName, rel);
|
||||
inst[r](function(err, user) {
|
||||
if(!err && user) {
|
||||
debug('User found: %j', user.id);
|
||||
callback && callback(null, user.id === userId);
|
||||
} else {
|
||||
callback && callback(err, false);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -241,8 +261,10 @@ Role.registerResolver(Role.EVERYONE, function (role, context, callback) {
|
|||
* @param {Function} callback
|
||||
*/
|
||||
Role.isInRole = function (role, context, callback) {
|
||||
debug('isInRole(): %s %j', role, context);
|
||||
var resolver = Role.resolvers[role];
|
||||
if(resolver) {
|
||||
debug('Custom resolver found for role %s', role);
|
||||
resolver(role, context, callback);
|
||||
return;
|
||||
}
|
||||
|
@ -267,12 +289,14 @@ Role.isInRole = function (role, context, callback) {
|
|||
callback && callback(null, false);
|
||||
return;
|
||||
}
|
||||
debug('Role found: %j', result);
|
||||
RoleMapping.findOne({where: {roleId: result.id, principalType: principalType, principalId: principalId}},
|
||||
function (err, result) {
|
||||
if (err) {
|
||||
callback && callback(err);
|
||||
return;
|
||||
}
|
||||
debug('Role mapping found: %j', result);
|
||||
callback && callback(null, !!result);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -202,22 +202,24 @@ describe('security ACLs', function () {
|
|||
|
||||
log('User: ', user.toObject());
|
||||
|
||||
var userId = user.id;
|
||||
|
||||
// Define a model with static ACLs
|
||||
var Customer = ds.createModel('Customer', {
|
||||
name: {
|
||||
type: String,
|
||||
acls: [
|
||||
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY},
|
||||
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
|
||||
{principalType: ACL.USER, principalId: userId, accessType: ACL.WRITE, permission: ACL.DENY},
|
||||
{principalType: ACL.USER, principalId: userId, accessType: ACL.ALL, permission: ACL.ALLOW}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
acls: [
|
||||
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
|
||||
{principalType: ACL.USER, principalId: userId, accessType: ACL.ALL, permission: ACL.ALLOW}
|
||||
]
|
||||
});
|
||||
|
||||
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'Customer', property: ACL.ALL,
|
||||
ACL.create({principalType: ACL.USER, principalId: userId, model: 'Customer', property: ACL.ALL,
|
||||
accessType: ACL.ALL, permission: ACL.ALLOW}, function (err, acl) {
|
||||
|
||||
log('ACL 1: ', acl.toObject());
|
||||
|
@ -225,18 +227,18 @@ describe('security ACLs', function () {
|
|||
Role.create({name: 'MyRole'}, function (err, myRole) {
|
||||
log('Role: ', myRole.toObject());
|
||||
|
||||
myRole.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function (err, p) {
|
||||
myRole.principals.create({principalType: RoleMapping.USER, principalId: userId}, function (err, p) {
|
||||
|
||||
log('Principal added to role: ', p.toObject());
|
||||
|
||||
ACL.create({principalType: ACL.ROLE, principalId: myRole.id, model: 'Customer', property: ACL.ALL,
|
||||
ACL.create({principalType: ACL.ROLE, principalId: 'MyRole', model: 'Customer', property: ACL.ALL,
|
||||
accessType: ACL.READ, permission: ACL.DENY}, function (err, acl) {
|
||||
|
||||
log('ACL 2: ', acl.toObject());
|
||||
|
||||
ACL.checkAccess({
|
||||
principals: [
|
||||
{principalType: ACL.USER, principalId: 'u001'}
|
||||
{principalType: ACL.USER, principalId: userId}
|
||||
],
|
||||
model: 'Customer',
|
||||
property: 'name',
|
||||
|
@ -245,15 +247,17 @@ describe('security ACLs', function () {
|
|||
assert(!err && access.permission === ACL.ALLOW);
|
||||
});
|
||||
|
||||
/*
|
||||
ACL.checkAccess({
|
||||
principals: [
|
||||
{principalType: ACL.USER, principalId: 'u001'}
|
||||
{principalType: ACL.USER, principalId: userId}
|
||||
],
|
||||
model: 'Customer',
|
||||
accessType: ACL.READ
|
||||
}, function(err, access) {
|
||||
assert(!err && access.permission === ACL.DENY);
|
||||
});
|
||||
*/
|
||||
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue