commit
f2bfd1b6e4
|
@ -165,6 +165,14 @@ loopback.memory = function (name) {
|
|||
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
|
||||
*/
|
||||
|
|
|
@ -120,6 +120,30 @@ function overridePermission(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
|
||||
* @param principalType
|
||||
|
@ -132,9 +156,53 @@ function overridePermission(p1, p2) {
|
|||
ACL.checkPermission = function (principalType, principalId, model, property, accessType, callback) {
|
||||
property = 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 staticACLs = [];
|
||||
var modelClass = loopback.getModel(model); {
|
||||
if(modelClass && modelClass.settings.acls) {
|
||||
modelClass.settings.acls.forEach(function(acl) {
|
||||
staticACLs.push({
|
||||
model: model,
|
||||
property: acl.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] // regular property
|
||||
|| (modelClass._scopeMeta && modelClass._scopeMeta[property]) // relation/scope
|
||||
|| modelClass[property] // static method
|
||||
|| modelClass.prototype[property]); // prototype method
|
||||
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,
|
||||
model: model, property: propertyQuery, accessType: accessTypeQuery}},
|
||||
function (err, acls) {
|
||||
|
@ -142,17 +210,7 @@ ACL.checkPermission = function (principalType, principalId, model, property, acc
|
|||
callback && callback(err);
|
||||
return;
|
||||
}
|
||||
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;
|
||||
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});
|
||||
var resolvedPermission = resolvePermission(acls, defaultPermission);
|
||||
callback && callback(null, resolvedPermission);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -20,15 +20,15 @@ describe('security scopes', function () {
|
|||
|
||||
// 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);
|
||||
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},
|
||||
function (err, resource) {
|
||||
// console.log(resource);
|
||||
Scope.checkPermission('user', 'user', ACL.ALL, ACL.ALL, checkResult);
|
||||
Scope.checkPermission('user', 'user', 'name', ACL.ALL, checkResult);
|
||||
Scope.checkPermission('user', 'user', 'name', ACL.READ, checkResult);
|
||||
Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, checkResult);
|
||||
Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, checkResult);
|
||||
Scope.checkPermission('userScope', 'User', 'name', ACL.READ, checkResult);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -41,25 +41,25 @@ describe('security scopes', function () {
|
|||
|
||||
// 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);
|
||||
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) {
|
||||
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) {
|
||||
// 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
|
||||
});
|
||||
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
|
||||
});
|
||||
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);
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
@ -76,17 +76,17 @@ describe('security ACLs', function () {
|
|||
var ds = loopback.createDataSource({connector: loopback.Memory});
|
||||
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) {
|
||||
|
||||
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) {
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue