diff --git a/lib/loopback.js b/lib/loopback.js index 423a3f99..328f2ffa 100644 --- a/lib/loopback.js +++ b/lib/loopback.js @@ -90,7 +90,7 @@ loopback.errorHandler.title = 'Loopback'; */ loopback.createDataSource = function (name, options) { - var ds = new DataSource(name, options); + var ds = new DataSource(name, options, loopback.Model.dataSource); ds.createModel = function (name, properties, settings) { var ModelCtor = loopback.createModel(name, properties, settings); ModelCtor.attachTo(ds); diff --git a/lib/models/acl.js b/lib/models/acl.js index c304e362..754bb8f5 100644 --- a/lib/models/acl.js +++ b/lib/models/acl.js @@ -1,14 +1,14 @@ /** -Schema ACL options + Schema ACL options -Object level permissions, for example, an album owned by a user + Object level permissions, for example, an album owned by a user -Factors to be authorized against: + Factors to be authorized against: -* model name: Album -* model instance properties: userId of the album, friends, shared -* methods -* app and/or user ids/roles + * model name: Album + * model instance properties: userId of the album, friends, shared + * methods + * app and/or user ids/roles ** loggedIn ** roles ** userId @@ -17,35 +17,166 @@ Factors to be authorized against: ** everyone ** relations: owner/friend/granted -Class level permissions, for example, Album + Class level permissions, for example, Album * model name: Album * methods -URL/Route level permissions + URL/Route level permissions * url pattern * application id * ip addresses * http headers -Map to oAuth 2.0 scopes + Map to oAuth 2.0 scopes -*/ + */ +var loopback = require('../loopback'); + +/** + * Schema for Scope which represents the permissions that are granted to client applications by the resource owner + */ +var ScopeSchema = { + name: {type: String, required: true}, + description: String +}; + + +/** + * Resource owner grants/delegates permissions to client applications + * + * For a protected resource, does the client application have the authorization from the resource owner (user or system)? + * + * Scope has many resource access entries + * @type {createModel|*} + */ +var Scope = loopback.createModel('Scope', ScopeSchema); + +/** + * System grants permissions to principals (users/applications, can be grouped into roles). + * + * Protected resource: the model data and operations (model/property/method/relation/…) + * + * For a given principal, such as client application and/or user, is it allowed to access (read/write/execute) + * the protected resource? + * + */ var ACLSchema = { - model: String, // The model name - properties: [String], // A list of property names - methods: [String], // A list of methods - roles: [String], // A list of roles - permission: {type: String, enum: ['Allow', 'Deny']}, // Allow/Deny - status: String, // Enabled/disabled - created: Date, - modified: Date + model: String, // The name of the model + property: String, // The name of the property, method, scope, or relation + + /** + * Name of the access type - READ/WRITE/EXEC + */ + accessType: String, + + /** + * ALARM - Generate an alarm, in a system dependent way, the access specified in the permissions component of the ACL entry. + * ALLOW - Explicitly grants access to the resource. + * AUDIT - Log, in a system dependent way, the access specified in the permissions component of the ACL entry. + * DENY - Explicitly denies access to the resource. + */ + permission: String, + /** + * Type of the principal - Application/User/Role + */ + principalType: String, + /** + * Id of the principal - such as appId, userId or roleId + */ + principalId: String +}; + +var ACL = loopback.createModel('ACL', ACLSchema); + +ACL.ALL = '*'; + +ACL.ALLOW = 'ALLOW'; +ACL.ALARM = 'ALARM'; +ACL.AUDIT = 'AUDIT'; +ACL.DENY = 'DENY'; + +ACL.READ = 'READ'; +ACL.WRITE = 'WRITE'; +ACL.EXECUTE = 'EXECUTE'; + +ACL.USER = 'USER'; +ACL.APP = ACL.APPLICATION = 'APP'; +ACL.ROLE = 'ROLE'; +ACL.SCOPE = 'SCOPE'; + +var permissionOrder = { + ALLOW: 1, + ALARM: 2, + AUDIT: 3, + DENY: 4 +}; + +function overridePermission(p1, p2) { + p1 = permissionOrder[p1] ? p1 : ACL.ALLOW; + p2 = permissionOrder[p2] ? p2 : ACL.ALLOW; + var i1 = permissionOrder[p1]; + var i2 = permissionOrder[p2]; + return i1 > i2 ? p1 : p2; } -// readAccess, writeAccess --> public, userId, role +/** + * Check if the given principal is allowed to access the model/property + * @param principalType + * @param principalId + * @param model + * @param property + * @param accessType + * @param callback + */ +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]}; -module.exports = function(dataSource) { - dataSource = dataSource || new require('loopback-datasource-juggler').ModelBuilder(); - var ACL = dataSource.define('ACL', ACLSchema); - return ACL; -} \ No newline at end of file + ACL.find({where: {principalType: principalType, principalId: principalId, + model: model, property: propertyQuery, accessType: accessTypeQuery}}, + function (err, acls) { + if (err) { + callback && callback(err); + return; + } + var resolvedPermission = acls.reduce(function (previousValue, currentValue, index, array) { + // If the property is the same or the previous one is ACL.ALL (ALL) + if (previousValue.property === currentValue.property || (previousValue.property === ACL.ALL && currentValue.property)) { + previousValue.property = currentValue.property; + if (previousValue.accessType === currentValue.accessType || (previousValue.accessType === ACL.ALL && currentValue.accessType)) { + previousValue.accessType = currentValue.accessType; + } + previousValue.permission = overridePermission(previousValue.permission, currentValue.permission); + } + return previousValue; + }, {principalType: principalType, principalId: principalId, model: model, property: ACL.ALL, accessType: ACL.ALL, permission: ACL.ALLOW}); + callback && callback(null, resolvedPermission); + }); +}; + +/** + * Check if the given scope is allowed to access the model/property + * @param scope + * @param model + * @param property + * @param accessType + * @param callback + */ +Scope.checkPermission = function (scope, model, property, accessType, callback) { + Scope.findOne({where: {name: scope}}, function (err, scope) { + if (err) { + callback && callback(err); + } else { + ACL.checkPermission(ACL.SCOPE, scope.id, model, property, accessType, callback); + } + }); +}; + + +module.exports = { + ACL: ACL, + Scope: Scope +}; diff --git a/lib/models/oauth2.js b/lib/models/oauth2.js new file mode 100644 index 00000000..ba82a6e3 --- /dev/null +++ b/lib/models/oauth2.js @@ -0,0 +1,222 @@ +var loopback = require('../loopback'); + +// "OAuth token" +var OAuthToken = loopback.createModel({ + // "access token" + accessToken: { + type: String, + index: { + unique: true + } + }, // key, The string token + clientId: { + type: String, + index: true + }, // The client id + resourceOwner: { + type: String, + index: true + }, // The resource owner (user) id + realm: { + type: String, + index: true + }, // The resource owner realm + issuedAt: { + type: Date, + index: true + }, // The timestamp when the token is issued + expiresIn: Number, // Expiration time in seconds + expiredAt: { + type: Date, + index: { + expires: "1d" + } + }, // The timestamp when the token is expired + scopes: [ String ], // oAuth scopes + parameters: [ + { + name: String, + value: String + } + ], + + authorizationCode: { + type: String, + index: true + }, // The corresponding authorization code that is used to request the + // access token + refreshToken: { + type: String, + index: true + }, // The corresponding refresh token if issued + + tokenType: { + type: String, + enum: [ "Bearer", "MAC" ] + }, // The token type, such as Bearer: + // http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-16 + // or MAC: http://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05 + authenticationScheme: String, // HTTP authenticationScheme + hash: String // The SHA-1 hash for +// client-secret/resource-owner-secret-key +}); + +// "OAuth authorization code" +var OAuthAuthorizationCode = loopback.createModel({ + code: { + type: String, + index: { + unique: true + } + }, // key // The string code + clientId: { + type: String, + index: true + }, // The client id + resourceOwner: { + type: String, + index: true + }, // The resource owner (user) id + realm: { + type: String, + index: true + }, // The resource owner realm + + issuedAt: { + type: Date, + index: true + }, // The timestamp when the token is issued + expiresIn: Number, // Expiration time in seconds + expiredAt: { + type: Date, + index: { + expires: "1d" + } + }, // The timestamp when the token is expired + + scopes: [ String ], // oAuth scopes + parameters: [ + { + name: String, + value: String + } + ], + + used: Boolean, // Is it ever used + redirectURI: String, // The redirectURI from the request, we need to + // check if it's identical to the one used for + // access token + hash: String // The SHA-1 hash for +// client-secret/resource-owner-secret-key +}); + +// "OAuth client registration record" +var ClientRegistration = loopback.createModel({ + id: { + type: String, + index: { + unique: true + } + }, + clientId: { + type: String, + index: { + unique: true + } + }, // key; // The client id + clientSecret: String, // The generated client secret + + defaultTokenType: String, + accessLevel: Number, // The access level to scopes, -1: disabled, 0: + // basic, 1..N + disabled: Boolean, + + name: { + type: String, + index: true + }, + email: String, + description: String, + url: String, + iconURL: String, + redirectURIs: [ String ], + type: { + type: String, + enum: [ "CONFIDENTIAL", "PUBLIC" ] + }, + + userId: { + type: String, + index: true + } // The registered developer + +}); + +// "OAuth permission" +var OAuthPermission = loopback.createModel({ + clientId: { + type: String, + index: true + }, // The client id + resourceOwner: { + type: String, + index: true + }, // The resource owner (user) id + realm: { + type: String, + index: true + }, // The resource owner realm + + issuedAt: { + type: Date, + index: true + }, // The timestamp when the permission is issued + expiresIn: Number, // Expiration time in seconds + expiredAt: { + type: Date, + index: { + expires: "1d" + } + }, // The timestamp when the permission is expired + + scopes: [ String ] +}); + +// "OAuth scope" +var OAuthScope = loopback.createModel({ + scope: { + type: String, + index: { + unique: true + } + }, // key; // The scope name + description: String, // Description of the scope + iconURL: String, // The icon to be displayed on the "Request Permission" + // dialog + expiresIn: Number, // The default maximum lifetime of access token that + // carries the scope + requiredAccessLevel: Number, // The minimum access level required + resourceOwnerAuthorizationRequired: Boolean +// The scope requires authorization from the resource owner +}); + +// "OAuth protected resource" +var OAuthResource = loopback.createModel({ + operations: [ + { + type: String, + enum: [ "ALL", "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH" ] + } + ], // A list of operations, by default ALL + path: String, // The resource URI path + scopes: [ String ] +// Allowd scopes +}); + +// Use the schema to register a model +exports.OAuthToken = OAuthToken; +exports.OAuthAuthorizationCode = OAuthAuthorizationCode; +exports.ClientRegistration = ClientRegistration; +exports.OAuthPermission = OAuthPermission; +exports.OAuthScope = OAuthScope; +exports.OAuthResource = OAuthResource; diff --git a/lib/models/role.js b/lib/models/role.js index 9a18e418..5522340b 100644 --- a/lib/models/role.js +++ b/lib/models/role.js @@ -1,18 +1,208 @@ +var loopback = require('../loopback'); + // Role model var RoleSchema = { - id: {type: String, required: true}, // Id - name: {type: String, required: true}, // The name of a role - description: String, // Description - roles: [String], // A role can be an aggregate of other roles - users: [String], // A role contains a list of user ids + id: {type: String, id: 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} -} + // Timestamps + created: {type: Date, default: Date}, + modified: {type: Date, default: Date} +}; + +/** + * 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' + } + } +}); + +// 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) { + loopback.Application.findById(this.principalId, callback); + } else { + process.nextTick(function () { + callback && callback(null, null); + }); + } +}; + +/** + * Get the user principal + * @param callback + */ +RoleMapping.prototype.user = function (callback) { + if (this.principalType === RoleMapping.USER) { + loopback.User.findById(this.principalId, callback); + } else { + process.nextTick(function () { + callback && callback(null, null); + }); + } +}; + +/** + * Get the child role principal + * @param callback + */ +RoleMapping.prototype.childRole = function (callback) { + if (this.principalType === RoleMapping.ROLE) { + loopback.User.findById(this.principalId, callback); + } else { + process.nextTick(function () { + callback && callback(null, null); + }); + } +}; + +/** + * Define the Role model with `hasMany` relation to RoleMapping + */ +var Role = loopback.createModel('Role', RoleSchema, { + relations: { + principals: { + type: 'hasMany', + model: 'RoleMapping', + foreignKey: 'roleId' + } + } +}); + +// 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) { + callback && callback(err); + return; + } + return mappings.map(function (m) { + return m.principalId; + }); + }); + }; + + Role.prototype.applications = function (callback) { + RoleMapping.find({where: {roleId: this.id, principalType: RoleMapping.APPLICATION}}, function (err, mappings) { + if (err) { + callback && callback(err); + return; + } + return mappings.map(function (m) { + return m.principalId; + }); + }); + }; + + Role.prototype.roles = function (callback) { + RoleMapping.find({where: {roleId: this.id, principalType: RoleMapping.ROLE}}, function (err, mappings) { + if (err) { + callback && callback(err); + return; + } + return mappings.map(function (m) { + return m.principalId; + }); + }); + }; + +}); + +// Special roles +Role.OWNER = '$owner'; // owner of the object +Role.RELATED = "$related"; // any User with a relationship to the object +Role.AUTHENTICATED = "$authenticated"; // authenticated user +Role.EVERYONE = "$everyone"; // everyone + +/** + * Add custom handler for roles + * @param role + * @param resolver The resolver function decides if a principal is in the role dynamically + * + * isInRole(role, context, callback) + */ +Role.registerResolver = function(role, resolver) { + Role.resolvers[role] = resolver; +}; + +/** + * Check if a given principal is in the role + * + * @param role + * @param principalType + * @param principalId + * @param callback + */ +Role.isInRole = function (role, principalType, principalId, callback) { + Role.findOne({where: {name: role}}, function (err, result) { + if (err) { + callback && callback(err); + return; + } + 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); + }); + }); +}; + +/** + * List roles for a given principal + * @param {String} principalType + * @param {String|Number} principalId + * @param {Function} callback + * + * @callback callback + * @param err + * @param {String[]} An array of role ids + */ +Role.getRoles = function (principalType, principalId, callback) { + RoleMapping.find({where: {principalType: principalType, principalId: principalId}}, function (err, mappings) { + if (err) { + callback && callback(err); + return; + } + var roles = []; + mappings.forEach(function (m) { + roles.push(m.roleId); + }); + callback && callback(null, roles); + }); +}; + +module.exports = { + Role: Role, + RoleMapping: RoleMapping +}; -module.exports = function(dataSource) { - dataSource = dataSource || new require('loopback-datasource-juggler').ModelBuilder(); - var Role = dataSource.define('Role', RoleSchema); - return Role; -} diff --git a/package.json b/package.json index 7883e8de..4ed8d6b0 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,11 @@ "debug": "~0.7.2", "express": "~3.4.0", "loopback-datasource-juggler": "~1.2.0", - "strong-remoting": "~1.0.0", + "strong-remoting": "~1.1.0", "inflection": "~1.2.5", "passport": "~0.1.17", "passport-local": "~0.1.6", - "nodemailer": "~0.4.4", + "nodemailer": "~0.5.5", "ejs": "~0.8.4", "bcryptjs": "~0.7.10", "underscore.string": "~2.3.3", @@ -34,9 +34,9 @@ }, "devDependencies": { "blanket": "~1.1.5", - "mocha": "~1.12.1", + "mocha": "~1.14.0", "strong-task-emitter": "0.0.x", - "supertest": "~0.7.1" + "supertest": "~0.8.1" }, "repository": { "type": "git", diff --git a/test/acl.test.js b/test/acl.test.js new file mode 100644 index 00000000..1c5c4d91 --- /dev/null +++ b/test/acl.test.js @@ -0,0 +1,102 @@ +var assert = require('assert'); +var loopback = require('../index'); +var acl = require('../lib/models/acl'); +var Scope = acl.Scope; +var ACL = acl.ACL; +var ScopeACL = acl.ScopeACL; +var User = loopback.User; + +function checkResult(err, result) { + // console.log(err, result); + assert(!err); +} + +describe('security scopes', function () { + + it("should allow access to models for the given scope by wildcard", function () { + var ds = loopback.createDataSource({connector: loopback.Memory}); + Scope.attachTo(ds); + ACL.attachTo(ds); + + // console.log(Scope.relations); + + Scope.create({name: 'user', description: 'access user information'}, function (err, scope) { + // console.log(scope); + ACL.create({principalType: ACL.SCOPE, principalId: scope.id, model: 'user', property: ACL.ALL, + accessType: ACL.ALL, permission: ACL.ALLOW}, + function (err, resource) { + // console.log(resource); + Scope.checkPermission('user', 'user', ACL.ALL, ACL.ALL, checkResult); + Scope.checkPermission('user', 'user', 'name', ACL.ALL, checkResult); + Scope.checkPermission('user', 'user', 'name', ACL.READ, checkResult); + }); + }); + + }); + + it("should allow access to models for the given scope", function () { + var ds = loopback.createDataSource({connector: loopback.Memory}); + Scope.attachTo(ds); + ACL.attachTo(ds); + + // console.log(Scope.relations); + + Scope.create({name: 'user', description: 'access user information'}, function (err, scope) { + // console.log(scope); + ACL.create({principalType: ACL.SCOPE, principalId: scope.id, + model: 'user', property: 'name', accessType: ACL.READ, permission: ACL.ALLOW}, + function (err, resource) { + ACL.create({principalType: ACL.SCOPE, principalId: scope.id, + model: 'user', property: 'name', accessType: ACL.WRITE, permission: ACL.DENY}, + function (err, resource) { + // console.log(resource); + Scope.checkPermission('user', 'user', ACL.ALL, ACL.ALL, function (err, perm) { + assert(perm.permission === ACL.DENY); // because name.WRITE == DENY + }); + Scope.checkPermission('user', 'user', 'name', ACL.ALL, function (err, perm) { + assert(perm.permission === ACL.DENY); // because name.WRITE == DENY + }); + Scope.checkPermission('user', 'user', 'name', ACL.READ, function (err, perm) { + assert(perm.permission === ACL.ALLOW); + }); + Scope.checkPermission('user', 'user', 'name', ACL.WRITE, function (err, perm) { + assert(perm.permission === ACL.DENY); + }); + }); + }); + }); + + }); + +}); + +describe('security ACLs', function () { + + it("should allow access to models for the given principal by wildcard", function () { + var ds = loopback.createDataSource({connector: loopback.Memory}); + ACL.attachTo(ds); + + ACL.create({principalType: 'user', principalId: 'u001', model: 'user', property: ACL.ALL, + accessType: ACL.ALL, permission: ACL.ALLOW}, function (err, acl) { + + ACL.create({principalType: 'user', principalId: 'u001', model: 'user', property: ACL.ALL, + accessType: ACL.READ, permission: ACL.DENY}, function (err, acl) { + + ACL.checkPermission('user', 'u001', 'user', 'name', ACL.READ, function (err, perm) { + assert(perm.permission === ACL.DENY); + }); + + ACL.checkPermission('user', 'u001', 'user', 'name', ACL.ALL, function (err, perm) { + assert(perm.permission === ACL.DENY); + }); + + }); + + }); + + }); + +}); + + + diff --git a/test/model.test.js b/test/model.test.js index 1ac72441..d0686fee 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -512,6 +512,39 @@ describe('Model', function() { }); }); + describe('Model.extend() events', function() { + it('create isolated emitters for subclasses', function() { + var User1 = loopback.createModel('User1', { + 'first': String, + 'last': String + }); + + var User2 = loopback.createModel('User2', { + 'name': String + }); + + var user1Triggered = false; + User1.once('x', function(event) { + user1Triggered = true; + }); + + + var user2Triggered = false; + User2.once('x', function(event) { + user2Triggered = true; + }); + + assert(User1.once !== User2.once); + assert(User1.once !== loopback.Model.once); + + User1.emit('x', User1); + + assert(user1Triggered); + assert(!user2Triggered); + }); + + }); + // describe('Model.hasAndBelongsToMany()', function() { // it("TODO: implement / document", function(done) { // /* example - diff --git a/test/role.test.js b/test/role.test.js new file mode 100644 index 00000000..acd561d9 --- /dev/null +++ b/test/role.test.js @@ -0,0 +1,122 @@ +var assert = require('assert'); +var loopback = require('../index'); +var role = require('../lib/models/role'); +var Role = role.Role; +var RoleMapping = role.RoleMapping; +var User = loopback.User; + +function checkResult(err, result) { + // console.log(err, result); + assert(!err); +} + +describe('role model', function () { + + it("should define role/role relations", function () { + var ds = loopback.createDataSource({connector: 'memory'}); + Role.attachTo(ds); + RoleMapping.attachTo(ds); + + Role.create({name: 'user'}, function (err, userRole) { + Role.create({name: 'admin'}, function (err, adminRole) { + userRole.principals.create({principalType: RoleMapping.ROLE, principalId: adminRole.id}, function (err, mapping) { + Role.find(function (err, roles) { + assert.equal(roles.length, 2); + }); + RoleMapping.find(function (err, mappings) { + assert.equal(mappings.length, 1); + assert.equal(mappings[0].principalType, RoleMapping.ROLE); + assert.equal(mappings[0].principalId, adminRole.id); + }); + userRole.principals(function (err, principals) { + assert.equal(principals.length, 1); + }); + userRole.roles(function (err, roles) { + assert.equal(roles.length, 1); + }); + }); + }); + }); + + }); + + it("should define role/user relations", function () { + var ds = loopback.createDataSource({connector: 'memory'}); + User.attachTo(ds); + Role.attachTo(ds); + RoleMapping.attachTo(ds); + + User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) { + // console.log('User: ', user.id); + Role.create({name: 'userRole'}, function (err, role) { + role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function (err, p) { + Role.find(function(err, roles) { + assert(!err); + assert.equal(roles.length, 1); + assert.equal(roles[0].name, 'userRole'); + }); + role.principals(function(err, principals) { + assert(!err); + // console.log(principals); + assert.equal(principals.length, 1); + assert.equal(principals[0].principalType, RoleMapping.USER); + assert.equal(principals[0].principalId, user.id); + }); + role.users(function(err, users) { + assert(!err); + assert.equal(users.length, 1); + assert.equal(users[0].principalType, RoleMapping.USER); + assert.equal(users[0].principalId, user.id); + }); + }); + }); + }); + + }); + + it("should support getRoles() and isInRole()", function () { + var ds = loopback.createDataSource({connector: 'memory'}); + User.attachTo(ds); + Role.attachTo(ds); + RoleMapping.attachTo(ds); + + User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) { + // console.log('User: ', user.id); + Role.create({name: 'userRole'}, function (err, role) { + role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function (err, p) { + // Role.find(console.log); + // role.principals(console.log); + Role.isInRole('userRole', RoleMapping.USER, user.id, function(err, exists) { + assert(!err && exists === true); + }); + + Role.isInRole('userRole', RoleMapping.APP, user.id, function(err, exists) { + assert(!err && exists === false); + }); + + Role.isInRole('userRole', RoleMapping.USER, 100, function(err, exists) { + assert(!err && exists === false); + }); + + Role.getRoles(RoleMapping.USER, user.id, function(err, roles) { + assert.equal(roles.length, 1); + assert.equal(roles[0], role.id); + }); + Role.getRoles(RoleMapping.APP, user.id, function(err, roles) { + assert.equal(roles.length, 0); + }); + Role.getRoles(RoleMapping.USER, 100, function(err, roles) { + assert.equal(roles.length, 0); + }); + + }); + }); + }); + + }); + +}); + + + + diff --git a/test/user.test.js b/test/user.test.js index 74ddab1a..bc2ea76f 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -264,7 +264,7 @@ describe('User', function(){ var lines = result.email.message.split('\n'); - assert(lines[4].indexOf('To: bar@bat.com') === 0); + assert(lines[3].indexOf('To: bar@bat.com') === 0); done(); }); });