commit
52b1588152
|
@ -32,6 +32,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var loopback = require('../loopback');
|
var loopback = require('../loopback');
|
||||||
|
var async = require('async');
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -91,6 +96,7 @@ var ACL = loopback.createModel('ACL', ACLSchema);
|
||||||
|
|
||||||
ACL.ALL = '*';
|
ACL.ALL = '*';
|
||||||
|
|
||||||
|
ACL.DEFAULT = 'DEFAULT';
|
||||||
ACL.ALLOW = 'ALLOW';
|
ACL.ALLOW = 'ALLOW';
|
||||||
ACL.ALARM = 'ALARM';
|
ACL.ALARM = 'ALARM';
|
||||||
ACL.AUDIT = 'AUDIT';
|
ACL.AUDIT = 'AUDIT';
|
||||||
|
@ -106,6 +112,7 @@ ACL.ROLE = 'ROLE';
|
||||||
ACL.SCOPE = 'SCOPE';
|
ACL.SCOPE = 'SCOPE';
|
||||||
|
|
||||||
var permissionOrder = {
|
var permissionOrder = {
|
||||||
|
DEFAULT: 0,
|
||||||
ALLOW: 1,
|
ALLOW: 1,
|
||||||
ALARM: 2,
|
ALARM: 2,
|
||||||
AUDIT: 3,
|
AUDIT: 3,
|
||||||
|
@ -144,62 +151,80 @@ function resolvePermission(acls, defaultPermission) {
|
||||||
return resolvedPermission;
|
return resolvedPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*!
|
||||||
* Check if the given principal is allowed to access the model/property
|
* Check the LDL ACLs
|
||||||
* @param principalType
|
* @param principalType
|
||||||
* @param principalId
|
* @param principalId
|
||||||
* @param model
|
* @param {String} model The model name
|
||||||
* @param property
|
* @param {String} property The property/method/relation name
|
||||||
* @param accessType
|
* @param {String} accessType The access type
|
||||||
* @param callback
|
*
|
||||||
|
* @returns {{principalType: *, principalId: *, model: *, property: string, accessType: *, permission: string}}
|
||||||
*/
|
*/
|
||||||
ACL.checkPermission = function (principalType, principalId, model, property, accessType, callback) {
|
function getStaticPermission(principalType, principalId, model, property, accessType) {
|
||||||
property = property || ACL.ALL;
|
var modelClass = loopback.getModel(model);
|
||||||
var propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]};
|
|
||||||
accessType = accessType || ACL.ALL;
|
|
||||||
var accessTypeQuery = (accessType === ACL.ALL) ? undefined : {inq: [accessType, ACL.ALL]};
|
|
||||||
|
|
||||||
var staticACLs = [];
|
var staticACLs = [];
|
||||||
var modelClass = loopback.getModel(model); {
|
if (modelClass && modelClass.settings.acls) {
|
||||||
if(modelClass && modelClass.settings.acls) {
|
modelClass.settings.acls.forEach(function (acl) {
|
||||||
modelClass.settings.acls.forEach(function(acl) {
|
staticACLs.push({
|
||||||
staticACLs.push({
|
model: model,
|
||||||
model: model,
|
property: acl.property || ACL.ALL,
|
||||||
property: acl.property || ACL.ALL,
|
principalType: acl.principalType,
|
||||||
principalType: acl.principalType,
|
principalId: acl.principalId, // TODO: Should it be a name?
|
||||||
principalId: acl.principalId, // TODO: Should it be a name?
|
accessType: acl.accessType,
|
||||||
accessType: acl.accessType,
|
permission: acl.permission
|
||||||
permission: acl.permission
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
var prop = modelClass &&
|
}
|
||||||
(modelClass.definition.properties[property] // regular property
|
var prop = modelClass &&
|
||||||
|
(modelClass.definition.properties[property] // regular property
|
||||||
|| (modelClass._scopeMeta && modelClass._scopeMeta[property]) // relation/scope
|
|| (modelClass._scopeMeta && modelClass._scopeMeta[property]) // relation/scope
|
||||||
|| modelClass[property] // static method
|
|| modelClass[property] // static method
|
||||||
|| modelClass.prototype[property]); // prototype method
|
|| modelClass.prototype[property]); // prototype method
|
||||||
if(prop && prop.acls) {
|
if (prop && prop.acls) {
|
||||||
prop.acls.forEach(function(acl) {
|
prop.acls.forEach(function (acl) {
|
||||||
staticACLs.push({
|
staticACLs.push({
|
||||||
model: model,
|
model: modelClass.modelName,
|
||||||
property: property,
|
property: property,
|
||||||
principalType: acl.principalType,
|
principalType: acl.principalType,
|
||||||
principalId: acl.principalId,
|
principalId: acl.principalId,
|
||||||
accessType: acl.accessType,
|
accessType: acl.accessType,
|
||||||
permission: acl.permission
|
permission: acl.permission
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultPermission = {principalType: principalType, principalId: principalId,
|
var defaultPermission = {principalType: principalType, principalId: principalId,
|
||||||
model: model, property: ACL.ALL, accessType: accessType, permission: ACL.ALLOW};
|
model: model, property: ACL.ALL, accessType: accessType, permission: ACL.ALLOW};
|
||||||
|
|
||||||
defaultPermission = resolvePermission(staticACLs, defaultPermission);
|
defaultPermission = resolvePermission(staticACLs, defaultPermission);
|
||||||
|
return defaultPermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given principal is allowed to access the model/property
|
||||||
|
* @param {String} principalType The principal type
|
||||||
|
* @param {String} principalId The principal id
|
||||||
|
* @param {String} model The model name
|
||||||
|
* @param {String} property The property/method/relation name
|
||||||
|
* @param {String} accessType The access type
|
||||||
|
* @param {Function} callback The callback function
|
||||||
|
*
|
||||||
|
* @callback callback
|
||||||
|
* @param {String|Error} err The error object
|
||||||
|
* @param {Object} the access permission
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
var accessTypeQuery = (accessType === ACL.ALL) ? undefined : {inq: [accessType, ACL.ALL]};
|
||||||
|
var defaultPermission = getStaticPermission(principalType, principalId, model, property, accessType);
|
||||||
if(defaultPermission.permission === ACL.DENY) {
|
if(defaultPermission.permission === ACL.DENY) {
|
||||||
// Fail fast
|
// Fail fast
|
||||||
callback && callback(null, defaultPermission);
|
process.nextTick(function() {
|
||||||
|
callback && callback(null, defaultPermission);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,17 +236,25 @@ ACL.checkPermission = function (principalType, principalId, model, property, acc
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var resolvedPermission = resolvePermission(acls, defaultPermission);
|
var resolvedPermission = resolvePermission(acls, defaultPermission);
|
||||||
|
if(resolvedPermission.permission === ACL.DEFAULT) {
|
||||||
|
var modelClass = loopback.getModel(model);
|
||||||
|
resolvedPermission.permission = (modelClass && modelClass.settings.defaultPermission) || ACL.ALLOW;
|
||||||
|
}
|
||||||
callback && callback(null, resolvedPermission);
|
callback && callback(null, resolvedPermission);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the given scope is allowed to access the model/property
|
* Check if the given scope is allowed to access the model/property
|
||||||
* @param scope
|
* @param {String} scope The scope name
|
||||||
* @param model
|
* @param {String} model The model name
|
||||||
* @param property
|
* @param {String} property The property/method/relation name
|
||||||
* @param accessType
|
* @param {String} accessType The access type
|
||||||
* @param callback
|
* @param {Function} callback The callback function
|
||||||
|
*
|
||||||
|
* @callback callback
|
||||||
|
* @param {String|Error} err The error object
|
||||||
|
* @param {Object} the access permission
|
||||||
*/
|
*/
|
||||||
Scope.checkPermission = function (scope, model, property, accessType, callback) {
|
Scope.checkPermission = function (scope, model, property, accessType, callback) {
|
||||||
Scope.findOne({where: {name: scope}}, function (err, scope) {
|
Scope.findOne({where: {name: scope}}, function (err, scope) {
|
||||||
|
@ -233,6 +266,113 @@ Scope.checkPermission = function (scope, model, property, accessType, callback)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the request has the permission to access
|
||||||
|
* @param {Object} context
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
ACL.checkAccess = function (context, callback) {
|
||||||
|
context = context || {};
|
||||||
|
var principals = context.principals || [];
|
||||||
|
var model = context.model;
|
||||||
|
model = ('string' === typeof model) ? loopback.getModel(model) : model;
|
||||||
|
var id = context.id;
|
||||||
|
var property = context.property;
|
||||||
|
var accessType = context.accessType;
|
||||||
|
|
||||||
|
property = property || ACL.ALL;
|
||||||
|
var propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]};
|
||||||
|
accessType = accessType || ACL.ALL;
|
||||||
|
var accessTypeQuery = (accessType === ACL.ALL) ? undefined : {inq: [accessType, ACL.ALL]};
|
||||||
|
|
||||||
|
var defaultPermission = {principalType: null, principalId: null,
|
||||||
|
model: model.modelName, property: ACL.ALL, accessType: accessType, permission: ACL.ALLOW};
|
||||||
|
|
||||||
|
// Check the LDL ACLs
|
||||||
|
principals.forEach(function(p) {
|
||||||
|
var perm = getStaticPermission(p.principalType, p.principalId, model.modelName, property, accessType);
|
||||||
|
defaultPermission = resolvePermission([perm], defaultPermission);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(defaultPermission.permission === ACL.DENY) {
|
||||||
|
// Fail fast
|
||||||
|
process.nextTick(function() {
|
||||||
|
callback && callback(null, defaultPermission);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ACL.find({where: {model: model.modelName, property: propertyQuery, accessType: accessTypeQuery}}, function (err, acls) {
|
||||||
|
if (err) {
|
||||||
|
callback && callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var effectiveACLs = [];
|
||||||
|
var inRoleTasks = [];
|
||||||
|
acls.forEach(function (acl) {
|
||||||
|
principals.forEach(function (principal) {
|
||||||
|
if (principal.principalType === acl.pricipalType && principal.principalId === acl.principalId) {
|
||||||
|
effectiveACLs.push(acl);
|
||||||
|
} else if (acl.principalType === ACL.ROLE) {
|
||||||
|
inRoleTasks.push(function (done) {
|
||||||
|
Role.isInRole(acl.principalId,
|
||||||
|
{principalType: principal.principalType, principalId: acl.principalId, model: model, id: id, property: property},
|
||||||
|
function (err, inRole) {
|
||||||
|
if(!err) {
|
||||||
|
effectiveACLs.push(acl);
|
||||||
|
}
|
||||||
|
done(err, acl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async.parallel(inRoleTasks, function(err, results) {
|
||||||
|
defaultPermission = resolvePermission(effectiveACLs, defaultPermission);
|
||||||
|
callback && callback(null, defaultPermission);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given access token can invoke the method
|
||||||
|
* @param {AccessToken} token The access token
|
||||||
|
* @param {String} model The model name
|
||||||
|
* @param {*} modelId The model id
|
||||||
|
* @param {String} method The method name
|
||||||
|
* @param callback The callback function
|
||||||
|
*
|
||||||
|
* @callback callback
|
||||||
|
* @param {String|Error} err The error object
|
||||||
|
* @param {Boolean} allowed is the request allowed
|
||||||
|
*/
|
||||||
|
ACL.checkAccessForToken = function(token, model, modelId, method, callback) {
|
||||||
|
assert(token, 'Access token is required');
|
||||||
|
var principals = [];
|
||||||
|
if(token.userId) {
|
||||||
|
principals.push({principalType: ACL.USER, principalId: token.userId});
|
||||||
|
}
|
||||||
|
if(token.appId) {
|
||||||
|
principals.push({principalType: ACL.APPLICATION, principalId: token.appId});
|
||||||
|
}
|
||||||
|
var context = {
|
||||||
|
principals: principals,
|
||||||
|
model: model,
|
||||||
|
property: method,
|
||||||
|
accessType: ACL.EXECUTE,
|
||||||
|
id: modelId
|
||||||
|
};
|
||||||
|
ACL.checkAccess(context, function(err, access) {
|
||||||
|
if(err) {
|
||||||
|
callback && callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback && callback(access.permission !== ACL.DENY);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ACL: ACL,
|
ACL: ACL,
|
||||||
|
|
|
@ -79,7 +79,7 @@ Model.setup = function () {
|
||||||
self.beforeRemote.apply(self, args);
|
self.beforeRemote.apply(self, args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// after remote hook
|
// after remote hook
|
||||||
ModelCtor.afterRemote = function (name, fn) {
|
ModelCtor.afterRemote = function (name, fn) {
|
||||||
|
@ -95,7 +95,7 @@ Model.setup = function () {
|
||||||
self.afterRemote.apply(self, args);
|
self.afterRemote.apply(self, args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Map the prototype method to /:id with data in the body
|
// Map the prototype method to /:id with data in the body
|
||||||
ModelCtor.sharedCtor.accepts = [
|
ModelCtor.sharedCtor.accepts = [
|
||||||
|
@ -110,7 +110,34 @@ Model.setup = function () {
|
||||||
ModelCtor.sharedCtor.returns = {root: true};
|
ModelCtor.sharedCtor.returns = {root: true};
|
||||||
|
|
||||||
return ModelCtor;
|
return ModelCtor;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Get the reference to ACL in a lazy fashion to avoid race condition in require
|
||||||
|
*/
|
||||||
|
var ACL = null;
|
||||||
|
function getACL() {
|
||||||
|
return ACL || (ACL = require('./acl').ACL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given access token can invoke the method
|
||||||
|
*
|
||||||
|
* @param {AccessToken} token The access token
|
||||||
|
* @param {*} modelId The model id
|
||||||
|
* @param {String} method The method name
|
||||||
|
* @param callback The callback function
|
||||||
|
*
|
||||||
|
* @callback callback
|
||||||
|
* @param {String|Error} err The error object
|
||||||
|
* @param {Boolean} allowed is the request allowed
|
||||||
|
*/
|
||||||
|
Model.checkAccess = function(token, modelId, method, callback) {
|
||||||
|
var ACL = getACL();
|
||||||
|
var methodName = 'string' === typeof method? method: method && method.name;
|
||||||
|
ACL.checkAccessForToken(token, this.modelName, modelId, methodName, callback);
|
||||||
|
};
|
||||||
|
|
||||||
// setup the initial model
|
// setup the initial model
|
||||||
Model.setup();
|
Model.setup();
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,7 @@ Role.once('dataSourceAttached', function () {
|
||||||
Role.OWNER = '$owner'; // owner of the object
|
Role.OWNER = '$owner'; // owner of the object
|
||||||
Role.RELATED = "$related"; // any User with a relationship to the object
|
Role.RELATED = "$related"; // any User with a relationship to the object
|
||||||
Role.AUTHENTICATED = "$authenticated"; // authenticated user
|
Role.AUTHENTICATED = "$authenticated"; // authenticated user
|
||||||
|
Role.UNAUTHENTICATED = "$unauthenticated"; // authenticated user
|
||||||
Role.EVERYONE = "$everyone"; // everyone
|
Role.EVERYONE = "$everyone"; // everyone
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -142,21 +143,104 @@ Role.EVERYONE = "$everyone"; // everyone
|
||||||
* @param role
|
* @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
|
||||||
*
|
*
|
||||||
* isInRole(role, context, callback)
|
* function(role, context, callback)
|
||||||
*/
|
*/
|
||||||
Role.registerResolver = function(role, resolver) {
|
Role.registerResolver = function(role, resolver) {
|
||||||
|
if(!Role.resolvers) {
|
||||||
|
Role.resolvers = {};
|
||||||
|
}
|
||||||
Role.resolvers[role] = resolver;
|
Role.resolvers[role] = resolver;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Role.registerResolver(Role.OWNER, function(role, context, callback) {
|
||||||
|
if(!context || !context.model || !context.id) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
callback && callback(null, false);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var modelClass = context.model;
|
||||||
|
var id = context.id;
|
||||||
|
var userId = context.principalId;
|
||||||
|
isOwner(modelClass, id, userId, callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
function isOwner(modelClass, id, userId, callback) {
|
||||||
|
modelClass.findById(id, function(err, inst) {
|
||||||
|
if(err) {
|
||||||
|
callback && callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(inst.userId || inst.owner) {
|
||||||
|
callback && callback(null, (inst.userId || inst.owner) === userId);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback && callback(null, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Role.registerResolver(Role.AUTHENTICATED, function(role, context, callback) {
|
||||||
|
if(!context) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
callback && callback(null, false);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var userId = context.principalId;
|
||||||
|
isAuthenticated(userId, callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
function isAuthenticated(userId, callback) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
callback && callback(null, !!userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Role.registerResolver(Role.UNAUTHENTICATED, function(role, context, callback) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
callback && callback(null, !context || !context.principalId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Role.registerResolver(Role.EVERYONE, function (role, context, callback) {
|
||||||
|
process.nextTick(function () {
|
||||||
|
callback && callback(null, true); // Always true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a given principal is in the role
|
* Check if a given principal is in the role
|
||||||
*
|
*
|
||||||
* @param role
|
* @param {String} role The role name
|
||||||
* @param principalType
|
* @param {Object} context The context object
|
||||||
* @param principalId
|
* @param {Function} callback
|
||||||
* @param callback
|
|
||||||
*/
|
*/
|
||||||
Role.isInRole = function (role, principalType, principalId, callback) {
|
Role.isInRole = function (role, context, callback) {
|
||||||
|
var resolver = Role.resolvers[role];
|
||||||
|
if(resolver) {
|
||||||
|
resolver(role, context, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var principalType = context.principalType;
|
||||||
|
var principalId = context.principalId;
|
||||||
|
|
||||||
|
// Check if it's the same role
|
||||||
|
if(principalType === RoleMapping.ROLE && principalId === role) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
callback && callback(null, true);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Role.findOne({where: {name: role}}, function (err, result) {
|
Role.findOne({where: {name: role}}, function (err, result) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback && callback(err);
|
callback && callback(err);
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
"bcryptjs": "~0.7.10",
|
"bcryptjs": "~0.7.10",
|
||||||
"underscore.string": "~2.3.3",
|
"underscore.string": "~2.3.3",
|
||||||
"underscore": "~1.5.2",
|
"underscore": "~1.5.2",
|
||||||
"uid2": "0.0.3"
|
"uid2": "0.0.3",
|
||||||
|
"async": "~0.2.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"mocha": "~1.14.0",
|
"mocha": "~1.14.0",
|
||||||
|
|
112
test/acl.test.js
112
test/acl.test.js
|
@ -3,7 +3,9 @@ var loopback = require('../index');
|
||||||
var acl = require('../lib/models/acl');
|
var acl = require('../lib/models/acl');
|
||||||
var Scope = acl.Scope;
|
var Scope = acl.Scope;
|
||||||
var ACL = acl.ACL;
|
var ACL = acl.ACL;
|
||||||
var ScopeACL = acl.ScopeACL;
|
var role = require('../lib/models/role');
|
||||||
|
var Role = role.Role;
|
||||||
|
var RoleMapping = role.RoleMapping;
|
||||||
var User = loopback.User;
|
var User = loopback.User;
|
||||||
|
|
||||||
function checkResult(err, result) {
|
function checkResult(err, result) {
|
||||||
|
@ -81,6 +83,39 @@ describe('security ACLs', function () {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should honor defaultPermission from the model", function () {
|
||||||
|
var ds = loopback.createDataSource({connector: loopback.Memory});
|
||||||
|
ACL.attachTo(ds);
|
||||||
|
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.defaultPermission = ACL.DENY;
|
||||||
|
|
||||||
|
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, 'u002', 'Customer', 'name', ACL.WRITE, function (err, perm) {
|
||||||
|
assert(perm.permission === ACL.DENY);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
it("should honor static ACLs from the model", function () {
|
it("should honor static ACLs from the model", function () {
|
||||||
var ds = loopback.createDataSource({connector: loopback.Memory});
|
var ds = loopback.createDataSource({connector: loopback.Memory});
|
||||||
var Customer = ds.createModel('Customer', {
|
var Customer = ds.createModel('Customer', {
|
||||||
|
@ -117,6 +152,81 @@ describe('security ACLs', function () {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should check access against LDL, ACL, and Role", function () {
|
||||||
|
var ds = loopback.createDataSource({connector: loopback.Memory});
|
||||||
|
ACL.attachTo(ds);
|
||||||
|
Role.attachTo(ds);
|
||||||
|
RoleMapping.attachTo(ds);
|
||||||
|
User.attachTo(ds);
|
||||||
|
|
||||||
|
// var log = console.log;
|
||||||
|
var log = function() {};
|
||||||
|
|
||||||
|
// Create
|
||||||
|
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) {
|
||||||
|
|
||||||
|
log('User: ', user.toObject());
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
acls: [
|
||||||
|
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'Customer', property: ACL.ALL,
|
||||||
|
accessType: ACL.ALL, permission: ACL.ALLOW}, function (err, acl) {
|
||||||
|
|
||||||
|
log('ACL 1: ', acl.toObject());
|
||||||
|
|
||||||
|
Role.create({name: 'MyRole'}, function (err, myRole) {
|
||||||
|
log('Role: ', myRole.toObject());
|
||||||
|
|
||||||
|
myRole.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function (err, p) {
|
||||||
|
|
||||||
|
log('Principal added to role: ', p.toObject());
|
||||||
|
|
||||||
|
ACL.create({principalType: ACL.ROLE, principalId: myRole.id, 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'}
|
||||||
|
],
|
||||||
|
model: 'Customer',
|
||||||
|
property: 'name',
|
||||||
|
accessType: ACL.READ
|
||||||
|
}, function(err, access) {
|
||||||
|
assert(!err && access.permission === ACL.ALLOW);
|
||||||
|
});
|
||||||
|
|
||||||
|
ACL.checkAccess({
|
||||||
|
principals: [
|
||||||
|
{principalType: ACL.USER, principalId: 'u001'}
|
||||||
|
],
|
||||||
|
model: 'Customer',
|
||||||
|
accessType: ACL.READ
|
||||||
|
}, function(err, access) {
|
||||||
|
assert(!err && access.permission === ACL.DENY);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ var role = require('../lib/models/role');
|
||||||
var Role = role.Role;
|
var Role = role.Role;
|
||||||
var RoleMapping = role.RoleMapping;
|
var RoleMapping = role.RoleMapping;
|
||||||
var User = loopback.User;
|
var User = loopback.User;
|
||||||
|
var ACL = require('../lib/models/acl');
|
||||||
|
|
||||||
function checkResult(err, result) {
|
function checkResult(err, result) {
|
||||||
// console.log(err, result);
|
// console.log(err, result);
|
||||||
|
@ -41,19 +42,19 @@ describe('role model', function () {
|
||||||
// console.log('User: ', user.id);
|
// console.log('User: ', user.id);
|
||||||
Role.create({name: 'userRole'}, function (err, role) {
|
Role.create({name: 'userRole'}, function (err, role) {
|
||||||
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function (err, p) {
|
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function (err, p) {
|
||||||
Role.find(function(err, roles) {
|
Role.find(function (err, roles) {
|
||||||
assert(!err);
|
assert(!err);
|
||||||
assert.equal(roles.length, 1);
|
assert.equal(roles.length, 1);
|
||||||
assert.equal(roles[0].name, 'userRole');
|
assert.equal(roles[0].name, 'userRole');
|
||||||
});
|
});
|
||||||
role.principals(function(err, principals) {
|
role.principals(function (err, principals) {
|
||||||
assert(!err);
|
assert(!err);
|
||||||
// console.log(principals);
|
// console.log(principals);
|
||||||
assert.equal(principals.length, 1);
|
assert.equal(principals.length, 1);
|
||||||
assert.equal(principals[0].principalType, RoleMapping.USER);
|
assert.equal(principals[0].principalType, RoleMapping.USER);
|
||||||
assert.equal(principals[0].principalId, user.id);
|
assert.equal(principals[0].principalId, user.id);
|
||||||
});
|
});
|
||||||
role.users(function(err, users) {
|
role.users(function (err, users) {
|
||||||
assert(!err);
|
assert(!err);
|
||||||
assert.equal(users.length, 1);
|
assert.equal(users.length, 1);
|
||||||
assert.equal(users[0].principalType, RoleMapping.USER);
|
assert.equal(users[0].principalType, RoleMapping.USER);
|
||||||
|
@ -72,26 +73,26 @@ describe('role model', function () {
|
||||||
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function (err, p) {
|
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function (err, p) {
|
||||||
// Role.find(console.log);
|
// Role.find(console.log);
|
||||||
// role.principals(console.log);
|
// role.principals(console.log);
|
||||||
Role.isInRole('userRole', RoleMapping.USER, user.id, function(err, exists) {
|
Role.isInRole('userRole', {principalType: RoleMapping.USER, principalId: user.id}, function (err, exists) {
|
||||||
assert(!err && exists === true);
|
assert(!err && exists === true);
|
||||||
});
|
});
|
||||||
|
|
||||||
Role.isInRole('userRole', RoleMapping.APP, user.id, function(err, exists) {
|
Role.isInRole('userRole', {principalType: RoleMapping.APP, principalId: user.id}, function (err, exists) {
|
||||||
assert(!err && exists === false);
|
assert(!err && exists === false);
|
||||||
});
|
});
|
||||||
|
|
||||||
Role.isInRole('userRole', RoleMapping.USER, 100, function(err, exists) {
|
Role.isInRole('userRole', {principalType: RoleMapping.USER, principalId: 100}, function (err, exists) {
|
||||||
assert(!err && exists === false);
|
assert(!err && exists === false);
|
||||||
});
|
});
|
||||||
|
|
||||||
Role.getRoles(RoleMapping.USER, user.id, function(err, roles) {
|
Role.getRoles(RoleMapping.USER, user.id, function (err, roles) {
|
||||||
assert.equal(roles.length, 1);
|
assert.equal(roles.length, 1);
|
||||||
assert.equal(roles[0], role.id);
|
assert.equal(roles[0], role.id);
|
||||||
});
|
});
|
||||||
Role.getRoles(RoleMapping.APP, user.id, function(err, roles) {
|
Role.getRoles(RoleMapping.APP, user.id, function (err, roles) {
|
||||||
assert.equal(roles.length, 0);
|
assert.equal(roles.length, 0);
|
||||||
});
|
});
|
||||||
Role.getRoles(RoleMapping.USER, 100, function(err, roles) {
|
Role.getRoles(RoleMapping.USER, 100, function (err, roles) {
|
||||||
assert.equal(roles.length, 0);
|
assert.equal(roles.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -101,6 +102,63 @@ describe('role model', function () {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should support owner role resolver", function () {
|
||||||
|
var ds = loopback.createDataSource({connector: 'memory'});
|
||||||
|
User.attachTo(ds);
|
||||||
|
Role.attachTo(ds);
|
||||||
|
RoleMapping.attachTo(ds);
|
||||||
|
|
||||||
|
var Album = ds.createModel('Album', {
|
||||||
|
name: String,
|
||||||
|
userId: Number
|
||||||
|
}, {
|
||||||
|
relations: {
|
||||||
|
user: {
|
||||||
|
type: 'belongsTo',
|
||||||
|
model: 'User',
|
||||||
|
foreignKey: 'userId'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) {
|
||||||
|
Role.isInRole(Role.AUTHENTICATED, {principalType: ACL.USER, principalId: user.id}, function (err, yes) {
|
||||||
|
assert(!err && yes);
|
||||||
|
});
|
||||||
|
Role.isInRole(Role.AUTHENTICATED, {principalType: ACL.USER, principalId: null}, function (err, yes) {
|
||||||
|
assert(!err && !yes);
|
||||||
|
});
|
||||||
|
|
||||||
|
Role.isInRole(Role.UNAUTHENTICATED, {principalType: ACL.USER, principalId: user.id}, function (err, yes) {
|
||||||
|
assert(!err && !yes);
|
||||||
|
});
|
||||||
|
Role.isInRole(Role.UNAUTHENTICATED, {principalType: ACL.USER, principalId: null}, function (err, yes) {
|
||||||
|
assert(!err && yes);
|
||||||
|
});
|
||||||
|
|
||||||
|
Role.isInRole(Role.EVERYONE, {principalType: ACL.USER, principalId: user.id}, function (err, yes) {
|
||||||
|
assert(!err && yes);
|
||||||
|
});
|
||||||
|
|
||||||
|
Role.isInRole(Role.EVERYONE, {principalType: ACL.USER, principalId: null}, function (err, yes) {
|
||||||
|
assert(!err && yes);
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log('User: ', user.id);
|
||||||
|
Album.create({name: 'Album 1', userId: user.id}, function (err, album1) {
|
||||||
|
Role.isInRole(Role.OWNER, {principalType: ACL.USER, principalId: user.id, model: Album, id: album1.id}, function (err, yes) {
|
||||||
|
assert(!err && yes);
|
||||||
|
});
|
||||||
|
Album.create({name: 'Album 2'}, function (err, album2) {
|
||||||
|
Role.isInRole(Role.OWNER, {principalType: ACL.USER, principalId: user.id, model: Album, id: album2.id}, function (err, yes) {
|
||||||
|
assert(!err && !yes);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue