loopback/lib/models/role.js

334 lines
8.4 KiB
JavaScript
Raw Normal View History

2013-11-10 06:22:16 +00:00
var loopback = require('../loopback');
var debug = require('debug')('role');
2013-11-04 21:19:02 +00:00
// Role model
var RoleSchema = {
2013-11-10 06:22:16 +00:00
id: {type: String, id: true}, // Id
name: {type: String, required: true}, // The name of a role
description: String, // Description
2013-07-09 22:06:42 +00:00
2013-11-10 06:22:16 +00:00
// Timestamps
created: {type: Date, default: Date},
modified: {type: Date, default: Date}
2013-11-04 21:19:02 +00:00
};
2013-11-12 06:16:51 +00:00
/**
* Map principals to roles
*/
var RoleMappingSchema = {
id: {type: String, id: true}, // Id
roleId: String, // The role id
principalType: String, // The principal type, such as user, application, or role
principalId: String // The principal id
};
var RoleMapping = loopback.createModel('RoleMapping', RoleMappingSchema, {
relations: {
role: {
type: 'belongsTo',
model: 'Role',
foreignKey: 'roleId'
}
}
});
2013-11-12 18:10:32 +00:00
// Principal types
RoleMapping.USER = 'USER';
RoleMapping.APP = RoleMapping.APPLICATION = 'APP';
RoleMapping.ROLE = 'ROLE';
/**
* Get the application principal
* @param callback
*/
RoleMapping.prototype.application = function (callback) {
if (this.principalType === RoleMapping.APPLICATION) {
2013-11-12 06:16:51 +00:00
loopback.Application.findById(this.principalId, callback);
} else {
2013-11-12 18:10:32 +00:00
process.nextTick(function () {
2013-11-12 06:16:51 +00:00
callback && callback(null, null);
});
}
};
2013-11-12 18:10:32 +00:00
/**
* Get the user principal
* @param callback
*/
RoleMapping.prototype.user = function (callback) {
if (this.principalType === RoleMapping.USER) {
2013-11-12 06:16:51 +00:00
loopback.User.findById(this.principalId, callback);
} else {
2013-11-12 18:10:32 +00:00
process.nextTick(function () {
2013-11-12 06:16:51 +00:00
callback && callback(null, null);
});
}
};
2013-11-12 18:10:32 +00:00
/**
* Get the child role principal
* @param callback
*/
RoleMapping.prototype.childRole = function (callback) {
if (this.principalType === RoleMapping.ROLE) {
2013-11-12 06:16:51 +00:00
loopback.User.findById(this.principalId, callback);
} else {
2013-11-12 18:10:32 +00:00
process.nextTick(function () {
2013-11-12 06:16:51 +00:00
callback && callback(null, null);
});
}
};
2013-11-12 18:10:32 +00:00
/**
* Define the Role model with `hasMany` relation to RoleMapping
*/
2013-11-10 06:22:16 +00:00
var Role = loopback.createModel('Role', RoleSchema, {
relations: {
2013-11-12 06:16:51 +00:00
principals: {
2013-11-10 06:22:16 +00:00
type: 'hasMany',
2013-11-12 06:16:51 +00:00
model: 'RoleMapping',
foreignKey: 'roleId'
2013-11-10 06:22:16 +00:00
}
}
});
2013-11-04 21:19:02 +00:00
2013-11-12 18:10:32 +00:00
// Set up the connection to users/applications/roles once the model
Role.once('dataSourceAttached', function () {
Role.prototype.users = function (callback) {
RoleMapping.find({where: {roleId: this.id,
principalType: RoleMapping.USER}}, function (err, mappings) {
2013-11-12 18:10:32 +00:00
if (err) {
2013-11-12 06:16:51 +00:00
callback && callback(err);
return;
}
2013-11-12 18:10:32 +00:00
return mappings.map(function (m) {
2013-11-12 06:16:51 +00:00
return m.principalId;
});
});
};
2013-11-12 18:10:32 +00:00
Role.prototype.applications = function (callback) {
RoleMapping.find({where: {roleId: this.id,
principalType: RoleMapping.APPLICATION}}, function (err, mappings) {
2013-11-12 18:10:32 +00:00
if (err) {
2013-11-12 06:16:51 +00:00
callback && callback(err);
return;
}
2013-11-12 18:10:32 +00:00
return mappings.map(function (m) {
2013-11-12 06:16:51 +00:00
return m.principalId;
});
});
};
2013-11-12 18:10:32 +00:00
Role.prototype.roles = function (callback) {
RoleMapping.find({where: {roleId: this.id,
principalType: RoleMapping.ROLE}}, function (err, mappings) {
2013-11-12 18:10:32 +00:00
if (err) {
2013-11-12 06:16:51 +00:00
callback && callback(err);
return;
}
2013-11-12 18:10:32 +00:00
return mappings.map(function (m) {
2013-11-12 06:16:51 +00:00
return m.principalId;
});
});
};
});
2013-11-10 06:22:16 +00:00
// Special roles
Role.OWNER = '$owner'; // owner of the object
2013-11-04 21:19:02 +00:00
Role.RELATED = "$related"; // any User with a relationship to the object
Role.AUTHENTICATED = "$authenticated"; // authenticated user
2013-11-20 21:38:14 +00:00
Role.UNAUTHENTICATED = "$unauthenticated"; // authenticated user
2013-11-04 21:19:02 +00:00
Role.EVERYONE = "$everyone"; // everyone
2013-07-09 22:06:42 +00:00
2013-11-13 18:29:33 +00:00
/**
* Add custom handler for roles
* @param role
* @param resolver The resolver function decides if a principal is in the role
* dynamically
2013-11-13 18:29:33 +00:00
*
* function(role, context, callback)
2013-11-13 18:29:33 +00:00
*/
Role.registerResolver = function(role, resolver) {
if(!Role.resolvers) {
Role.resolvers = {};
}
2013-11-13 18:29:33 +00:00
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.userId || context.principalId;
isOwner(modelClass, id, userId, callback);
});
function isUserClass(modelClass) {
return modelClass === loopback.User ||
modelClass.prototype instanceof loopback.User;
}
function isOwner(modelClass, id, userId, callback) {
debug('isOwner(): %s %s %s', modelClass && modelClass.modelName, id, userId);
// No userId is present
2013-12-11 05:49:18 +00:00
if(!userId) {
process.nextTick(function() {
callback(null, false);
});
return;
}
// Is the modelClass User or a subclass of User?
if(isUserClass(modelClass)) {
2013-12-11 05:49:18 +00:00
process.nextTick(function() {
callback(null, id === userId);
});
return;
}
modelClass.findById(id, function(err, inst) {
if(err || !inst) {
callback && callback(err, false);
return;
}
debug('Model found: %j', inst);
if(inst.userId || inst.owner) {
callback && callback(null, (inst.userId || inst.owner) === userId);
return;
} else {
// Try to follow belongsTo
for(var r in modelClass.relations) {
var rel = modelClass.relations[r];
if(rel.type === 'belongsTo' && isUserClass(rel.modelTo)) {
debug('Checking relation %s to %s: %j', r, rel.modelTo.modelName, rel);
inst[r](function(err, user) {
if(!err && user) {
debug('User found: %j', user.id);
callback && callback(null, user.id === userId);
} else {
callback && callback(err, false);
}
});
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);
});
}
2013-11-20 21:38:14 +00:00
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
});
});
2013-11-12 06:16:51 +00:00
/**
* Check if a given principal is in the role
*
* @param {String} role The role name
* @param {Object} context The context object
* @param {Function} callback
2013-11-12 06:16:51 +00:00
*/
Role.isInRole = function (role, context, callback) {
debug('isInRole(): %s %j', role, context);
var resolver = Role.resolvers[role];
if(resolver) {
debug('Custom resolver found for role %s', role);
resolver(role, context, callback);
return;
}
var principalType = context.principalType;
var principalId = context.principalId;
2013-11-20 21:31:30 +00:00
// Check if it's the same role
if(principalType === RoleMapping.ROLE && principalId === role) {
process.nextTick(function() {
callback && callback(null, true);
});
return;
}
2013-11-12 18:47:59 +00:00
Role.findOne({where: {name: role}}, function (err, result) {
2013-11-12 18:10:32 +00:00
if (err) {
2013-11-12 06:16:51 +00:00
callback && callback(err);
return;
}
2013-11-12 18:47:59 +00:00
if(!result) {
callback && callback(null, false);
return;
}
debug('Role found: %j', result);
2013-11-12 18:47:59 +00:00
RoleMapping.findOne({where: {roleId: result.id, principalType: principalType, principalId: principalId}},
function (err, result) {
if (err) {
callback && callback(err);
return;
}
debug('Role mapping found: %j', result);
2013-11-12 18:47:59 +00:00
callback && callback(null, !!result);
});
2013-11-12 06:16:51 +00:00
});
};
/**
* List roles for a given principal
2013-11-12 18:10:32 +00:00
* @param {String} principalType
* @param {String|Number} principalId
* @param {Function} callback
*
* @callback callback
* @param err
* @param {String[]} An array of role ids
2013-11-12 06:16:51 +00:00
*/
2013-11-12 18:10:32 +00:00
Role.getRoles = function (principalType, principalId, callback) {
RoleMapping.find({where: {principalType: principalType, principalId: principalId}}, function (err, mappings) {
if (err) {
2013-11-12 06:16:51 +00:00
callback && callback(err);
return;
}
var roles = [];
2013-11-12 18:10:32 +00:00
mappings.forEach(function (m) {
2013-11-12 06:16:51 +00:00
roles.push(m.roleId);
});
callback && callback(null, roles);
});
};
module.exports = {
Role: Role,
RoleMapping: RoleMapping
};