Allows LDL level ACLs

This commit is contained in:
Raymond Feng 2013-11-15 09:41:26 -08:00
parent 9bc762c09c
commit 8381b05da1
3 changed files with 128 additions and 28 deletions

View File

@ -165,6 +165,14 @@ loopback.memory = function (name) {
return memory; return memory;
} }
/**
* Loop up a model class by name
* @param {String} modelName The model name
*/
loopback.getModel = function(modelName) {
return loopback.Model.dataSource.models[modelName];
};
/* /*
* Built in models / services * Built in models / services
*/ */

View File

@ -120,6 +120,30 @@ function overridePermission(p1, p2) {
return i1 > i2 ? p1 : p2; return i1 > i2 ? p1 : p2;
} }
/*!
* Resolve permission from the ACLs
* @param acls
* @param defaultPermission
* @returns {*|Object|Mixed}
*/
function resolvePermission(acls, defaultPermission) {
var resolvedPermission = acls.reduce(function (previousValue, currentValue, index, array) {
// If the property is the same or the previous one is ACL.ALL (ALL)
if (previousValue.property === currentValue.property || (previousValue.property === ACL.ALL && currentValue.property)) {
previousValue.property = currentValue.property;
// Check if the accessType applies
if (previousValue.accessType === currentValue.accessType
|| previousValue.accessType === ACL.ALL
|| currentValue.accessType === ACL.ALL
|| !currentValue.accessType) {
previousValue.permission = overridePermission(previousValue.permission, currentValue.permission);
}
}
return previousValue;
}, defaultPermission);
return resolvedPermission;
}
/** /**
* Check if the given principal is allowed to access the model/property * Check if the given principal is allowed to access the model/property
* @param principalType * @param principalType
@ -132,9 +156,49 @@ function overridePermission(p1, p2) {
ACL.checkPermission = function (principalType, principalId, model, property, accessType, callback) { ACL.checkPermission = function (principalType, principalId, model, property, accessType, callback) {
property = property || ACL.ALL; property = property || ACL.ALL;
var propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]}; var propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]};
accessType = accessType || ACL.aLL; accessType = accessType || ACL.ALL;
var accessTypeQuery = (accessType === ACL.ALL) ? undefined : {inq: [accessType, ACL.ALL]}; var accessTypeQuery = (accessType === ACL.ALL) ? undefined : {inq: [accessType, ACL.ALL]};
var staticACLs = [];
var modelClass = loopback.getModel(model); {
if(modelClass && modelClass.settings.acls) {
modelClass.settings.acls.forEach(function(acl) {
staticACLs.push({
model: model,
property: ACL.ALL,
principalType: acl.principalType,
principalId: acl.principalId, // TODO: Should it be a name?
accessType: acl.accessType,
permission: acl.permission
});
});
}
var prop = modelClass && modelClass.definition.properties[property];
if(prop && prop.acls) {
prop.acls.forEach(function(acl) {
staticACLs.push({
model: model,
property: property,
principalType: acl.principalType,
principalId: acl.principalId,
accessType: acl.accessType,
permission: acl.permission
});
});
}
}
var defaultPermission = {principalType: principalType, principalId: principalId,
model: model, property: ACL.ALL, accessType: accessType, permission: ACL.ALLOW};
defaultPermission = resolvePermission(staticACLs, defaultPermission);
if(defaultPermission.permission === ACL.DENY) {
// Fail fast
callback && callback(null, defaultPermission);
return;
}
ACL.find({where: {principalType: principalType, principalId: principalId, ACL.find({where: {principalType: principalType, principalId: principalId,
model: model, property: propertyQuery, accessType: accessTypeQuery}}, model: model, property: propertyQuery, accessType: accessTypeQuery}},
function (err, acls) { function (err, acls) {
@ -142,17 +206,7 @@ ACL.checkPermission = function (principalType, principalId, model, property, acc
callback && callback(err); callback && callback(err);
return; return;
} }
var resolvedPermission = acls.reduce(function (previousValue, currentValue, index, array) { var resolvedPermission = resolvePermission(acls, defaultPermission);
// If the property is the same or the previous one is ACL.ALL (ALL)
if (previousValue.property === currentValue.property || (previousValue.property === ACL.ALL && currentValue.property)) {
previousValue.property = currentValue.property;
if (previousValue.accessType === currentValue.accessType || (previousValue.accessType === ACL.ALL && currentValue.accessType)) {
previousValue.accessType = currentValue.accessType;
}
previousValue.permission = overridePermission(previousValue.permission, currentValue.permission);
}
return previousValue;
}, {principalType: principalType, principalId: principalId, model: model, property: ACL.ALL, accessType: ACL.ALL, permission: ACL.ALLOW});
callback && callback(null, resolvedPermission); callback && callback(null, resolvedPermission);
}); });
}; };

View File

@ -20,15 +20,15 @@ describe('security scopes', function () {
// console.log(Scope.relations); // console.log(Scope.relations);
Scope.create({name: 'user', description: 'access user information'}, function (err, scope) { Scope.create({name: 'userScope', description: 'access user information'}, function (err, scope) {
// console.log(scope); // console.log(scope);
ACL.create({principalType: ACL.SCOPE, principalId: scope.id, model: 'user', property: ACL.ALL, ACL.create({principalType: ACL.SCOPE, principalId: scope.id, model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW}, accessType: ACL.ALL, permission: ACL.ALLOW},
function (err, resource) { function (err, resource) {
// console.log(resource); // console.log(resource);
Scope.checkPermission('user', 'user', ACL.ALL, ACL.ALL, checkResult); Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, checkResult);
Scope.checkPermission('user', 'user', 'name', ACL.ALL, checkResult); Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, checkResult);
Scope.checkPermission('user', 'user', 'name', ACL.READ, checkResult); Scope.checkPermission('userScope', 'User', 'name', ACL.READ, checkResult);
}); });
}); });
@ -41,25 +41,25 @@ describe('security scopes', function () {
// console.log(Scope.relations); // console.log(Scope.relations);
Scope.create({name: 'user', description: 'access user information'}, function (err, scope) { Scope.create({name: 'userScope', description: 'access user information'}, function (err, scope) {
// console.log(scope); // console.log(scope);
ACL.create({principalType: ACL.SCOPE, principalId: scope.id, ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
model: 'user', property: 'name', accessType: ACL.READ, permission: ACL.ALLOW}, model: 'User', property: 'name', accessType: ACL.READ, permission: ACL.ALLOW},
function (err, resource) { function (err, resource) {
ACL.create({principalType: ACL.SCOPE, principalId: scope.id, ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
model: 'user', property: 'name', accessType: ACL.WRITE, permission: ACL.DENY}, model: 'User', property: 'name', accessType: ACL.WRITE, permission: ACL.DENY},
function (err, resource) { function (err, resource) {
// console.log(resource); // console.log(resource);
Scope.checkPermission('user', 'user', ACL.ALL, ACL.ALL, function (err, perm) { Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, function (err, perm) {
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
}); });
Scope.checkPermission('user', 'user', 'name', ACL.ALL, function (err, perm) { Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, function (err, perm) {
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
}); });
Scope.checkPermission('user', 'user', 'name', ACL.READ, function (err, perm) { Scope.checkPermission('userScope', 'User', 'name', ACL.READ, function (err, perm) {
assert(perm.permission === ACL.ALLOW); assert(perm.permission === ACL.ALLOW);
}); });
Scope.checkPermission('user', 'user', 'name', ACL.WRITE, function (err, perm) { Scope.checkPermission('userScope', 'User', 'name', ACL.WRITE, function (err, perm) {
assert(perm.permission === ACL.DENY); assert(perm.permission === ACL.DENY);
}); });
}); });
@ -76,17 +76,17 @@ describe('security ACLs', function () {
var ds = loopback.createDataSource({connector: loopback.Memory}); var ds = loopback.createDataSource({connector: loopback.Memory});
ACL.attachTo(ds); ACL.attachTo(ds);
ACL.create({principalType: 'user', principalId: 'u001', model: 'user', property: ACL.ALL, ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW}, function (err, acl) { accessType: ACL.ALL, permission: ACL.ALLOW}, function (err, acl) {
ACL.create({principalType: 'user', principalId: 'u001', model: 'user', property: ACL.ALL, ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.DENY}, function (err, acl) { accessType: ACL.READ, permission: ACL.DENY}, function (err, acl) {
ACL.checkPermission('user', 'u001', 'user', 'name', ACL.READ, function (err, perm) { ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, function (err, perm) {
assert(perm.permission === ACL.DENY); assert(perm.permission === ACL.DENY);
}); });
ACL.checkPermission('user', 'u001', 'user', 'name', ACL.ALL, function (err, perm) { ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, function (err, perm) {
assert(perm.permission === ACL.DENY); assert(perm.permission === ACL.DENY);
}); });
@ -96,6 +96,44 @@ describe('security ACLs', function () {
}); });
it("should honor static ACLs from the model", function () {
var ds = loopback.createDataSource({connector: loopback.Memory});
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}
]
}
}, {
acls: [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
]
});
/*
Customer.settings.acls = [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
];
*/
ACL.attachTo(ds);
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, function (err, perm) {
assert(perm.permission === ACL.DENY);
});
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, function (err, perm) {
assert(perm.permission === ACL.ALLOW);
});
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.ALL, function (err, perm) {
assert(perm.permission === ACL.DENY);
});
});
}); });