2013-11-10 06:22:16 +00:00
|
|
|
var loopback = require('../loopback');
|
2013-11-04 21:19:02 +00:00
|
|
|
|
2013-06-26 23:25:51 +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) {
|
|
|
|
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) {
|
|
|
|
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) {
|
|
|
|
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
|
|
|
|
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-19 19:58:30 +00:00
|
|
|
* function(role, context, callback)
|
2013-11-13 18:29:33 +00:00
|
|
|
*/
|
|
|
|
Role.registerResolver = function(role, resolver) {
|
2013-11-19 19:58:30 +00:00
|
|
|
if(!Role.resolvers) {
|
|
|
|
Role.resolvers = {};
|
|
|
|
}
|
2013-11-13 18:29:33 +00:00
|
|
|
Role.resolvers[role] = resolver;
|
|
|
|
};
|
|
|
|
|
2013-11-19 19:58:30 +00:00
|
|
|
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.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
|
|
|
|
*
|
2013-11-19 19:58:30 +00:00
|
|
|
* @param {String} role The role name
|
|
|
|
* @param {Object} context The context object
|
|
|
|
* @param {Function} callback
|
2013-11-12 06:16:51 +00:00
|
|
|
*/
|
2013-11-19 19:58:30 +00:00
|
|
|
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;
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
RoleMapping.findOne({where: {roleId: result.id, principalType: principalType, principalId: principalId}},
|
|
|
|
function (err, result) {
|
|
|
|
if (err) {
|
|
|
|
callback && callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|