commit
f2bfd1b6e4
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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,53 @@ 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.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,
|
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 +210,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);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue