Skip to content

Commit

Permalink
Add util methods to ACL and clean up related model resolutions
Browse files Browse the repository at this point in the history
  • Loading branch information
Raymond Feng committed Aug 13, 2015
1 parent 3c18d38 commit 3eb8dd5
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 36 deletions.
72 changes: 72 additions & 0 deletions common/models/acl.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,4 +496,76 @@ module.exports = function(ACL) {
if (callback) callback(null, access.permission !== ACL.DENY);
});
};

ACL.resolveRelatedModels = function() {
if (!this.roleModel) {
var reg = this.registry;
this.roleModel = reg.getModelByType(loopback.Role);
this.roleMappingModel = reg.getModelByType(loopback.RoleMapping);
this.userModel = reg.getModelByType(loopback.User);
this.applicationModel = reg.getModelByType(loopback.Application);
}
};

/**
* Resolve a principal by type/id
* @param {String} type Principal type - ROLE/APP/USER
* @param {String|Number} id Principal id or name
* @param {Function} cb Callback function
*/
ACL.resolvePrincipal = function(type, id, cb) {
type = type || ACL.ROLE;
this.resolveRelatedModels();
switch (type) {
case ACL.ROLE:
this.roleModel.findOne({where: {or: [{name: id}, {id: id}]}}, cb);
break;
case ACL.USER:
this.userModel.findOne(
{where: {or: [{username: id}, {email: id}, {id: id}]}}, cb);
break;
case ACL.APP:
this.applicationModel.findOne(
{where: {or: [{name: id}, {email: id}, {id: id}]}}, cb);
break;
default:
process.nextTick(function() {
var err = new Error('Invalid principal type: ' + type);
err.statusCode = 400;
cb(err);
});
}
};

/**
* Check if the given principal is mapped to the role
* @param {String} principalType Principal type
* @param {String|*} principalId Principal id/name
* @param {String|*} role Role id/name
* @param {Function} cb Callback function
*/
ACL.isMappedToRole = function(principalType, principalId, role, cb) {
var self = this;
this.resolvePrincipal(principalType, principalId,
function(err, principal) {
if (err) return cb(err);
if (principal != null) {
principalId = principal.id;
}
principalType = principalType || 'ROLE';
self.resolvePrincipal('ROLE', role, function(err, role) {
if (err || !role) return cb(err, role);
self.roleMappingModel.findOne({
where: {
roleId: role.id,
principalType: principalType,
principalId: String(principalId)
}
}, function(err, result) {
if (err) return cb(err);
return cb(null, !!result);
});
});
});
};
};
24 changes: 15 additions & 9 deletions common/models/role-mapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,26 @@ module.exports = function(RoleMapping) {
RoleMapping.APP = RoleMapping.APPLICATION = 'APP';
RoleMapping.ROLE = 'ROLE';

RoleMapping.resolveRelatedModels = function() {
if (!this.userModel) {
var reg = this.registry;
this.roleModel = reg.getModelByType(loopback.Role);
this.userModel = reg.getModelByType(loopback.User);
this.applicationModel = reg.getModelByType(loopback.Application);
}
};

/**
* Get the application principal
* @callback {Function} callback
* @param {Error} err
* @param {Application} application
*/
RoleMapping.prototype.application = function(callback) {
var registry = this.constructor.registry;
this.constructor.resolveRelatedModels();

if (this.principalType === RoleMapping.APPLICATION) {
var applicationModel = this.constructor.Application ||
registry.getModelByType('Application');
var applicationModel = this.constructor.applicationModel;
applicationModel.findById(this.principalId, callback);
} else {
process.nextTick(function() {
Expand All @@ -44,10 +52,9 @@ module.exports = function(RoleMapping) {
* @param {User} user
*/
RoleMapping.prototype.user = function(callback) {
var RoleMapping = this.constructor;
this.constructor.resolveRelatedModels();
if (this.principalType === RoleMapping.USER) {
var userModel = RoleMapping.User ||
RoleMapping.registry.getModelByType('User');
var userModel = this.constructor.userModel;
userModel.findById(this.principalId, callback);
} else {
process.nextTick(function() {
Expand All @@ -63,11 +70,10 @@ module.exports = function(RoleMapping) {
* @param {User} childUser
*/
RoleMapping.prototype.childRole = function(callback) {
var registry = this.constructor.registry;
this.constructor.resolveRelatedModels();

if (this.principalType === RoleMapping.ROLE) {
var roleModel = this.constructor.Role ||
registry.getModelByType(loopback.Role);
var roleModel = this.constructor.roleModel;
roleModel.findById(this.principalId, callback);
} else {
process.nextTick(function() {
Expand Down
56 changes: 34 additions & 22 deletions common/models/role.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ var async = require('async');
var AccessContext = require('../../lib/access-context').AccessContext;

var RoleMapping = loopback.RoleMapping;
var Role = loopback.Role;
var User = loopback.User;
var Application = loopback.Application;

assert(RoleMapping, 'RoleMapping model must be defined before Role model');

Expand All @@ -31,19 +28,19 @@ module.exports = function(Role) {
return new Date();
};

Role.resolveRelatedModels = function() {
if (!this.userModel) {
var reg = this.registry;
this.roleMappingModel = reg.getModelByType(loopback.RoleMapping);
this.userModel = reg.getModelByType(loopback.User);
this.applicationModel = reg.getModelByType(loopback.Application);
}
};

// Set up the connection to users/applications/roles once the model
Role.once('dataSourceAttached', function() {
var registry = Role.registry;
var roleMappingModel = this.RoleMapping || registry.getModelByType(RoleMapping);
var principalTypesToModels = {};

principalTypesToModels[RoleMapping.USER] = User;
principalTypesToModels[RoleMapping.APPLICATION] = Application;
principalTypesToModels[RoleMapping.ROLE] = Role;

Object.keys(principalTypesToModels).forEach(function(principalType) {
var model = principalTypesToModels[principalType];
var pluralName = model.pluralModelName.toLowerCase();
Role.once('dataSourceAttached', function(roleModel) {

['users', 'applications', 'roles'].forEach(function(rel) {
/**
* Fetch all users assigned to this role
* @function Role.prototype#users
Expand All @@ -62,8 +59,23 @@ module.exports = function(Role) {
* @param {object} [query] query object passed to model find call
* @param {Function} [callback]
*/
Role.prototype[pluralName] = function(query, callback) {
listByPrincipalType(model, principalType, query, callback);
Role.prototype[rel] = function(query, callback) {
roleModel.resolveRelatedModels();
var relsToModels = {
users: roleModel.userModel,
applications: roleModel.applicationModel,
roles: roleModel
};

var ACL = loopback.ACL;
var relsToTypes = {
users: ACL.USER,
applications: ACL.APP,
roles: ACL.ROLE
};

var model = relsToModels[rel];
listByPrincipalType(model, relsToTypes[rel], query, callback);
};
});

Expand All @@ -81,7 +93,7 @@ module.exports = function(Role) {
query = {};
}

roleMappingModel.find({
roleModel.roleMappingModel.find({
where: {roleId: this.id, principalType: principalType}
}, function(err, mappings) {
var ids;
Expand Down Expand Up @@ -272,7 +284,7 @@ module.exports = function(Role) {
context = new AccessContext(context);
}

var registry = this.registry;
this.resolveRelatedModels();

debug('isInRole(): %s', role);
context.debug();
Expand Down Expand Up @@ -309,7 +321,7 @@ module.exports = function(Role) {
return;
}

var roleMappingModel = this.RoleMapping || registry.getModelByType(RoleMapping);
var roleMappingModel = this.roleMappingModel;
this.findOne({where: {name: role}}, function(err, result) {
if (err) {
if (callback) callback(err);
Expand Down Expand Up @@ -364,7 +376,7 @@ module.exports = function(Role) {
context = new AccessContext(context);
}
var roles = [];
var registry = this.registry;
this.resolveRelatedModels();

var addRole = function(role) {
if (role && roles.indexOf(role) === -1) {
Expand All @@ -391,7 +403,7 @@ module.exports = function(Role) {
});
});

var roleMappingModel = this.RoleMapping || registry.getModelByType(RoleMapping);
var roleMappingModel = this.roleMappingModel;
context.principals.forEach(function(p) {
// Check against the role mappings
var principalType = p.type || undefined;
Expand Down
17 changes: 12 additions & 5 deletions common/models/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ var loopback = require('../../lib/loopback');
*/

module.exports = function(Scope) {
Scope.resolveRelatedModels = function() {
if (!this.aclModel) {
var reg = this.registry;
this.aclModel = reg.getModelByType(loopback.ACL);
}
};

/**
* Check if the given scope is allowed to access the model/property
* @param {String} scope The scope name
Expand All @@ -24,17 +31,17 @@ module.exports = function(Scope) {
* @param {AccessRequest} result The access permission
*/
Scope.checkPermission = function(scope, model, property, accessType, callback) {
var ACL = loopback.ACL;
var registry = this.registry;
assert(ACL,
this.resolveRelatedModels();
var aclModel = this.aclModel;
assert(aclModel,
'ACL model must be defined before Scope.checkPermission is called');

this.findOne({where: {name: scope}}, function(err, scope) {
if (err) {
if (callback) callback(err);
} else {
var aclModel = registry.getModelByType(ACL);
aclModel.checkPermission(ACL.SCOPE, scope.id, model, property, accessType, callback);
aclModel.checkPermission(
aclModel.SCOPE, scope.id, model, property, accessType, callback);
}
});
};
Expand Down
Loading

0 comments on commit 3eb8dd5

Please sign in to comment.