models: move Role LDL def into a json file

This commit is contained in:
Miroslav Bajtoš 2014-10-13 11:31:27 +02:00
parent e9c86163aa
commit 461ae92c1c
4 changed files with 376 additions and 362 deletions

View File

@ -41,8 +41,8 @@ var AccessContext = ctx.AccessContext;
var Principal = ctx.Principal; var Principal = ctx.Principal;
var AccessRequest = ctx.AccessRequest; var AccessRequest = ctx.AccessRequest;
var role = require('./role'); var Role = loopback.Role;
var Role = role.Role; assert(Role, 'Role model must be defined before ACL model');
/** /**
* A Model for access control meta data. * A Model for access control meta data.

View File

@ -5,86 +5,79 @@ var async = require('async');
var AccessContext = require('../../lib/access-context').AccessContext; var AccessContext = require('../../lib/access-context').AccessContext;
// Role model
var RoleSchema = {
id: {type: String, id: true, generated: true}, // Id
name: {type: String, required: true}, // The name of a role
description: String, // Description
// Timestamps
created: {type: Date, default: Date},
modified: {type: Date, default: Date}
};
/**
* The Role Model
* @class
*/
var Role = loopback.createModel('Role', RoleSchema, {
relations: {
principals: {
type: 'hasMany',
model: 'RoleMapping',
foreignKey: 'roleId'
}
}
});
var RoleMapping = loopback.RoleMapping; var RoleMapping = loopback.RoleMapping;
assert(RoleMapping, 'RoleMapping model must be defined before Role model'); assert(RoleMapping, 'RoleMapping model must be defined before Role model');
// Set up the connection to users/applications/roles once the model /**
Role.once('dataSourceAttached', function () { * The Role Model
* @class Role
*/
module.exports = function(Role) {
// Workaround for https://github.com/strongloop/loopback/issues/292
Role.definition.rawProperties.created.default =
Role.definition.properties.created.default = function() {
return new Date();
};
// Workaround for https://github.com/strongloop/loopback/issues/292
Role.definition.rawProperties.modified.default =
Role.definition.properties.modified.default = function() {
return new Date();
};
// Set up the connection to users/applications/roles once the model
Role.once('dataSourceAttached', function() {
var roleMappingModel = this.RoleMapping || loopback.getModelByType(RoleMapping); var roleMappingModel = this.RoleMapping || loopback.getModelByType(RoleMapping);
Role.prototype.users = function (callback) { Role.prototype.users = function(callback) {
roleMappingModel.find({where: {roleId: this.id, roleMappingModel.find({where: {roleId: this.id,
principalType: RoleMapping.USER}}, function (err, mappings) { principalType: RoleMapping.USER}}, function(err, mappings) {
if (err) { if (err) {
callback && callback(err); callback && callback(err);
return; return;
} }
return mappings.map(function (m) { return mappings.map(function(m) {
return m.principalId; return m.principalId;
}); });
}); });
}; };
Role.prototype.applications = function (callback) { Role.prototype.applications = function(callback) {
roleMappingModel.find({where: {roleId: this.id, roleMappingModel.find({where: {roleId: this.id,
principalType: RoleMapping.APPLICATION}}, function (err, mappings) { principalType: RoleMapping.APPLICATION}}, function(err, mappings) {
if (err) { if (err) {
callback && callback(err); callback && callback(err);
return; return;
} }
return mappings.map(function (m) { return mappings.map(function(m) {
return m.principalId; return m.principalId;
}); });
}); });
}; };
Role.prototype.roles = function (callback) { Role.prototype.roles = function(callback) {
roleMappingModel.find({where: {roleId: this.id, roleMappingModel.find({where: {roleId: this.id,
principalType: RoleMapping.ROLE}}, function (err, mappings) { principalType: RoleMapping.ROLE}}, function(err, mappings) {
if (err) { if (err) {
callback && callback(err); callback && callback(err);
return; return;
} }
return mappings.map(function (m) { return mappings.map(function(m) {
return m.principalId; return m.principalId;
}); });
}); });
}; };
}); });
// Special roles // Special roles
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.UNAUTHENTICATED = "$unauthenticated"; // authenticated user
Role.EVERYONE = "$everyone"; // everyone Role.EVERYONE = "$everyone"; // everyone
/** /**
* Add custom handler for roles * Add custom handler for roles
* @param role * @param role
* @param resolver The resolver function decides if a principal is in the role * @param resolver The resolver function decides if a principal is in the role
@ -92,15 +85,15 @@ Role.EVERYONE = "$everyone"; // everyone
* *
* function(role, context, callback) * function(role, context, callback)
*/ */
Role.registerResolver = function(role, resolver) { Role.registerResolver = function(role, resolver) {
if(!Role.resolvers) { if (!Role.resolvers) {
Role.resolvers = {}; Role.resolvers = {};
} }
Role.resolvers[role] = resolver; Role.resolvers[role] = resolver;
}; };
Role.registerResolver(Role.OWNER, function(role, context, callback) { Role.registerResolver(Role.OWNER, function(role, context, callback) {
if(!context || !context.model || !context.modelId) { if (!context || !context.model || !context.modelId) {
process.nextTick(function() { process.nextTick(function() {
callback && callback(null, false); callback && callback(null, false);
}); });
@ -110,40 +103,40 @@ Role.registerResolver(Role.OWNER, function(role, context, callback) {
var modelId = context.modelId; var modelId = context.modelId;
var userId = context.getUserId(); var userId = context.getUserId();
Role.isOwner(modelClass, modelId, userId, callback); Role.isOwner(modelClass, modelId, userId, callback);
}); });
function isUserClass(modelClass) { function isUserClass(modelClass) {
return modelClass === loopback.User || return modelClass === loopback.User ||
modelClass.prototype instanceof loopback.User; modelClass.prototype instanceof loopback.User;
} }
/*! /*!
* Check if two user ids matches * Check if two user ids matches
* @param {*} id1 * @param {*} id1
* @param {*} id2 * @param {*} id2
* @returns {boolean} * @returns {boolean}
*/ */
function matches(id1, id2) { function matches(id1, id2) {
if (id1 === undefined || id1 === null || id1 ==='' if (id1 === undefined || id1 === null || id1 === ''
|| id2 === undefined || id2 === null || id2 === '') { || id2 === undefined || id2 === null || id2 === '') {
return false; return false;
} }
// The id can be a MongoDB ObjectID // The id can be a MongoDB ObjectID
return id1 === id2 || id1.toString() === id2.toString(); return id1 === id2 || id1.toString() === id2.toString();
} }
/** /**
* Check if a given userId is the owner the model instance * Check if a given userId is the owner the model instance
* @param {Function} modelClass The model class * @param {Function} modelClass The model class
* @param {*} modelId The model id * @param {*} modelId The model id
* @param {*) userId The user id * @param {*) userId The user id
* @param {Function} callback * @param {Function} callback
*/ */
Role.isOwner = function isOwner(modelClass, modelId, userId, callback) { Role.isOwner = function isOwner(modelClass, modelId, userId, callback) {
assert(modelClass, 'Model class is required'); assert(modelClass, 'Model class is required');
debug('isOwner(): %s %s userId: %s', modelClass && modelClass.modelName, modelId, userId); debug('isOwner(): %s %s userId: %s', modelClass && modelClass.modelName, modelId, userId);
// No userId is present // No userId is present
if(!userId) { if (!userId) {
process.nextTick(function() { process.nextTick(function() {
callback(null, false); callback(null, false);
}); });
@ -151,7 +144,7 @@ Role.isOwner = function isOwner(modelClass, modelId, userId, callback) {
} }
// Is the modelClass User or a subclass of User? // Is the modelClass User or a subclass of User?
if(isUserClass(modelClass)) { if (isUserClass(modelClass)) {
process.nextTick(function() { process.nextTick(function() {
callback(null, matches(modelId, userId)); callback(null, matches(modelId, userId));
}); });
@ -159,24 +152,24 @@ Role.isOwner = function isOwner(modelClass, modelId, userId, callback) {
} }
modelClass.findById(modelId, function(err, inst) { modelClass.findById(modelId, function(err, inst) {
if(err || !inst) { if (err || !inst) {
debug('Model not found for id %j', modelId); debug('Model not found for id %j', modelId);
callback && callback(err, false); callback && callback(err, false);
return; return;
} }
debug('Model found: %j', inst); debug('Model found: %j', inst);
var ownerId = inst.userId || inst.owner; var ownerId = inst.userId || inst.owner;
if(ownerId) { if (ownerId) {
callback && callback(null, matches(ownerId, userId)); callback && callback(null, matches(ownerId, userId));
return; return;
} else { } else {
// Try to follow belongsTo // Try to follow belongsTo
for(var r in modelClass.relations) { for (var r in modelClass.relations) {
var rel = modelClass.relations[r]; var rel = modelClass.relations[r];
if(rel.type === 'belongsTo' && isUserClass(rel.modelTo)) { if (rel.type === 'belongsTo' && isUserClass(rel.modelTo)) {
debug('Checking relation %s to %s: %j', r, rel.modelTo.modelName, rel); debug('Checking relation %s to %s: %j', r, rel.modelTo.modelName, rel);
inst[r](function(err, user) { inst[r](function(err, user) {
if(!err && user) { if (!err && user) {
debug('User found: %j', user.id); debug('User found: %j', user.id);
callback && callback(null, matches(user.id, userId)); callback && callback(null, matches(user.id, userId));
} else { } else {
@ -190,44 +183,44 @@ Role.isOwner = function isOwner(modelClass, modelId, userId, callback) {
callback && callback(null, false); callback && callback(null, false);
} }
}); });
}; };
Role.registerResolver(Role.AUTHENTICATED, function(role, context, callback) { Role.registerResolver(Role.AUTHENTICATED, function(role, context, callback) {
if(!context) { if (!context) {
process.nextTick(function() { process.nextTick(function() {
callback && callback(null, false); callback && callback(null, false);
}); });
return; return;
} }
Role.isAuthenticated(context, callback); Role.isAuthenticated(context, callback);
}); });
/** /**
* Check if the user id is authenticated * Check if the user id is authenticated
* @param {Object} context The security context * @param {Object} context The security context
* @callback {Function} callback * @callback {Function} callback
* @param {Error} err * @param {Error} err
* @param {Boolean} isAuthenticated * @param {Boolean} isAuthenticated
*/ */
Role.isAuthenticated = function isAuthenticated(context, callback) { Role.isAuthenticated = function isAuthenticated(context, callback) {
process.nextTick(function() { process.nextTick(function() {
callback && callback(null, context.isAuthenticated()); callback && callback(null, context.isAuthenticated());
}); });
}; };
Role.registerResolver(Role.UNAUTHENTICATED, function(role, context, callback) { Role.registerResolver(Role.UNAUTHENTICATED, function(role, context, callback) {
process.nextTick(function() { process.nextTick(function() {
callback && callback(null, !context || !context.isAuthenticated()); callback && callback(null, !context || !context.isAuthenticated());
}); });
}); });
Role.registerResolver(Role.EVERYONE, function (role, context, callback) { Role.registerResolver(Role.EVERYONE, function(role, context, callback) {
process.nextTick(function () { process.nextTick(function() {
callback && callback(null, true); // Always true 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 {String} role The role name * @param {String} role The role name
@ -236,7 +229,7 @@ Role.registerResolver(Role.EVERYONE, function (role, context, callback) {
* @param {Error} err * @param {Error} err
* @param {Boolean} isInRole * @param {Boolean} isInRole
*/ */
Role.isInRole = function (role, context, callback) { Role.isInRole = function(role, context, callback) {
if (!(context instanceof AccessContext)) { if (!(context instanceof AccessContext)) {
context = new AccessContext(context); context = new AccessContext(context);
} }
@ -253,13 +246,13 @@ Role.isInRole = function (role, context, callback) {
if (context.principals.length === 0) { if (context.principals.length === 0) {
debug('isInRole() returns: false'); debug('isInRole() returns: false');
process.nextTick(function () { process.nextTick(function() {
callback && callback(null, false); callback && callback(null, false);
}); });
return; return;
} }
var inRole = context.principals.some(function (p) { var inRole = context.principals.some(function(p) {
var principalType = p.type || undefined; var principalType = p.type || undefined;
var principalId = p.id || undefined; var principalId = p.id || undefined;
@ -270,14 +263,14 @@ Role.isInRole = function (role, context, callback) {
if (inRole) { if (inRole) {
debug('isInRole() returns: %j', inRole); debug('isInRole() returns: %j', inRole);
process.nextTick(function () { process.nextTick(function() {
callback && callback(null, true); callback && callback(null, true);
}); });
return; return;
} }
var roleMappingModel = this.RoleMapping || loopback.getModelByType(RoleMapping); var roleMappingModel = this.RoleMapping || loopback.getModelByType(RoleMapping);
this.findOne({where: {name: role}}, function (err, result) { this.findOne({where: {name: role}}, function(err, result) {
if (err) { if (err) {
callback && callback(err); callback && callback(err);
return; return;
@ -289,36 +282,36 @@ Role.isInRole = function (role, context, callback) {
debug('Role found: %j', result); debug('Role found: %j', result);
// Iterate through the list of principals // Iterate through the list of principals
async.some(context.principals, function (p, done) { async.some(context.principals, function(p, done) {
var principalType = p.type || undefined; var principalType = p.type || undefined;
var principalId = p.id || undefined; var principalId = p.id || undefined;
var roleId = result.id.toString(); var roleId = result.id.toString();
if(principalId !== null && principalId !== undefined && (typeof principalId !== 'string') ) { if (principalId !== null && principalId !== undefined && (typeof principalId !== 'string')) {
principalId = principalId.toString(); principalId = principalId.toString();
} }
if (principalType && principalId) { if (principalType && principalId) {
roleMappingModel.findOne({where: {roleId: roleId, roleMappingModel.findOne({where: {roleId: roleId,
principalType: principalType, principalId: principalId}}, principalType: principalType, principalId: principalId}},
function (err, result) { function(err, result) {
debug('Role mapping found: %j', result); debug('Role mapping found: %j', result);
done(!err && result); // The only arg is the result done(!err && result); // The only arg is the result
}); });
} else { } else {
process.nextTick(function () { process.nextTick(function() {
done(false); done(false);
}); });
} }
}, function (inRole) { }, function(inRole) {
debug('isInRole() returns: %j', inRole); debug('isInRole() returns: %j', inRole);
callback && callback(null, inRole); callback && callback(null, inRole);
}); });
}); });
}; };
/** /**
* List roles for a given principal * List roles for a given principal
* @param {Object} context The security context * @param {Object} context The security context
* @param {Function} callback * @param {Function} callback
@ -327,13 +320,13 @@ Role.isInRole = function (role, context, callback) {
* @param err * @param err
* @param {String[]} An array of role ids * @param {String[]} An array of role ids
*/ */
Role.getRoles = function (context, callback) { Role.getRoles = function(context, callback) {
if(!(context instanceof AccessContext)) { if (!(context instanceof AccessContext)) {
context = new AccessContext(context); context = new AccessContext(context);
} }
var roles = []; var roles = [];
var addRole = function (role) { var addRole = function(role) {
if (role && roles.indexOf(role) === -1) { if (role && roles.indexOf(role) === -1) {
roles.push(role); roles.push(role);
} }
@ -342,10 +335,10 @@ Role.getRoles = function (context, callback) {
var self = this; var self = this;
// Check against the smart roles // Check against the smart roles
var inRoleTasks = []; var inRoleTasks = [];
Object.keys(Role.resolvers).forEach(function (role) { Object.keys(Role.resolvers).forEach(function(role) {
inRoleTasks.push(function (done) { inRoleTasks.push(function(done) {
self.isInRole(role, context, function (err, inRole) { self.isInRole(role, context, function(err, inRole) {
if(debug.enabled) { if (debug.enabled) {
debug('In role %j: %j', role, inRole); debug('In role %j: %j', role, inRole);
} }
if (!err && inRole) { if (!err && inRole) {
@ -359,7 +352,7 @@ Role.getRoles = function (context, callback) {
}); });
var roleMappingModel = this.RoleMapping || loopback.getModelByType(RoleMapping); var roleMappingModel = this.RoleMapping || loopback.getModelByType(RoleMapping);
context.principals.forEach(function (p) { context.principals.forEach(function(p) {
// Check against the role mappings // Check against the role mappings
var principalType = p.type || undefined; var principalType = p.type || undefined;
var principalId = p.id || undefined; var principalId = p.id || undefined;
@ -371,15 +364,15 @@ Role.getRoles = function (context, callback) {
if (principalType && principalId) { if (principalType && principalId) {
// Please find() treat undefined matches all values // Please find() treat undefined matches all values
inRoleTasks.push(function (done) { inRoleTasks.push(function(done) {
roleMappingModel.find({where: {principalType: principalType, roleMappingModel.find({where: {principalType: principalType,
principalId: principalId}}, function (err, mappings) { principalId: principalId}}, function(err, mappings) {
debug('Role mappings found: %s %j', err, mappings); debug('Role mappings found: %s %j', err, mappings);
if (err) { if (err) {
done && done(err); done && done(err);
return; return;
} }
mappings.forEach(function (m) { mappings.forEach(function(m) {
addRole(m.roleId); addRole(m.roleId);
}); });
done && done(); done && done();
@ -388,16 +381,9 @@ Role.getRoles = function (context, callback) {
} }
}); });
async.parallel(inRoleTasks, function (err, results) { async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles); debug('getRoles() returns: %j %j', err, roles);
callback && callback(err, roles); callback && callback(err, roles);
}); });
};
}; };
module.exports = {
Role: Role,
RoleMapping: RoleMapping
};

26
common/models/role.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "Role",
"properties": {
"id": {
"type": "string",
"id": true,
"generated": true
},
"name": {
"type": "string",
"required": true
},
"description": "string",
"created": "date",
"modified": "date"
},
"relations": {
"principals": {
"type": "hasMany",
"model": "RoleMapping",
"foreignKey": "roleId"
}
}
}

View File

@ -17,7 +17,9 @@ module.exports = function(loopback) {
require('../common/models/role-mapping.json'), require('../common/models/role-mapping.json'),
require('../common/models/role-mapping.js')); require('../common/models/role-mapping.js'));
loopback.Role = require('../common/models/role').Role; loopback.Role = createModel(
require('../common/models/role.json'),
require('../common/models/role.js'));
loopback.ACL = createModel( loopback.ACL = createModel(
require('../common/models/acl.json'), require('../common/models/acl.json'),