0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-24 23:48:13 -05:00

Permissions: minor refactors (#9104)

refs #9043

- Cleanups / refactors to make the code more manageable
- Move remaining code out of index.js 
   - Only "init" function is left. Actions map cache and init function is based heavily on the settings cache module
- refactor the odd way of exporting
   - This was cleaned up naturally by moving the actionsMap object out
- rename "effective" -> "providers"
  - "Providers" provide permissions for different things that can have permissions (users, apps, in future clients).
This commit is contained in:
Hannah Wolfe 2017-10-05 20:01:34 +01:00 committed by Katharina Irrgang
parent c50415d9d6
commit 6760ccc8ec
7 changed files with 853 additions and 775 deletions

View file

@ -0,0 +1,43 @@
// Based heavily on the settings cache
var _ = require('lodash'),
actionsMap = {};
module.exports = {
getAll: function getAll() {
return _.cloneDeep(actionsMap);
},
init: function init(perms) {
var seenActions = {};
actionsMap = {};
// Build a hash map of the actions on objects, i.e
/*
{
'edit': ['post', 'tag', 'user', 'page'],
'delete': ['post', 'user'],
'create': ['post', 'user', 'page']
}
*/
_.each(perms.models, function (perm) {
var actionType = perm.get('action_type'),
objectType = perm.get('object_type');
actionsMap[actionType] = actionsMap[actionType] || [];
seenActions[actionType] = seenActions[actionType] || {};
// Check if we've already seen this action -> object combo
if (seenActions[actionType][objectType]) {
return;
}
actionsMap[actionType].push(objectType);
seenActions[actionType][objectType] = true;
});
return actionsMap;
},
empty: function empty() {
return _.size(actionsMap) === 0;
}
};

View file

@ -0,0 +1,175 @@
var _ = require('lodash'),
Promise = require('bluebird'),
models = require('../models'),
errors = require('../errors'),
i18n = require('../i18n'),
providers = require('./providers'),
parseContext = require('./parse-context'),
actionsMap = require('./actions-map-cache'),
canThis,
CanThisResult;
// Base class for canThis call results
CanThisResult = function () {
return;
};
CanThisResult.prototype.buildObjectTypeHandlers = function (objTypes, actType, context, permissionLoad) {
var objectTypeModelMap = {
post: models.Post,
role: models.Role,
user: models.User,
permission: models.Permission,
setting: models.Settings,
subscriber: models.Subscriber,
invite: models.Invite
};
// Iterate through the object types, i.e. ['post', 'tag', 'user']
return _.reduce(objTypes, function (objTypeHandlers, objType) {
// Grab the TargetModel through the objectTypeModelMap
var TargetModel = objectTypeModelMap[objType];
// Create the 'handler' for the object type;
// the '.post()' in canThis(user).edit.post()
objTypeHandlers[objType] = function (modelOrId, unsafeAttrs) {
var modelId;
unsafeAttrs = unsafeAttrs || {};
// If it's an internal request, resolve immediately
if (context.internal) {
return Promise.resolve();
}
if (_.isNumber(modelOrId) || _.isString(modelOrId)) {
// It's an id already, do nothing
modelId = modelOrId;
} else if (modelOrId) {
// It's a model, get the id
modelId = modelOrId.id;
}
// Wait for the user loading to finish
return permissionLoad.then(function (loadedPermissions) {
// Iterate through the user permissions looking for an affirmation
var userPermissions = loadedPermissions.user ? loadedPermissions.user.permissions : null,
appPermissions = loadedPermissions.app ? loadedPermissions.app.permissions : null,
hasUserPermission,
hasAppPermission,
checkPermission = function (perm) {
var permObjId;
// Look for a matching action type and object type first
if (perm.get('action_type') !== actType || perm.get('object_type') !== objType) {
return false;
}
// Grab the object id (if specified, could be null)
permObjId = perm.get('object_id');
// If we didn't specify a model (any thing)
// or the permission didn't have an id scope set
// then the "thing" has permission
if (!modelId || !permObjId) {
return true;
}
// Otherwise, check if the id's match
// TODO: String vs Int comparison possibility here?
return modelId === permObjId;
};
if (loadedPermissions.user && _.some(loadedPermissions.user.roles, {name: 'Owner'})) {
hasUserPermission = true;
} else if (!_.isEmpty(userPermissions)) {
hasUserPermission = _.some(userPermissions, checkPermission);
}
// Check app permissions if they were passed
hasAppPermission = true;
if (!_.isNull(appPermissions)) {
hasAppPermission = _.some(appPermissions, checkPermission);
}
// Offer a chance for the TargetModel to override the results
if (TargetModel && _.isFunction(TargetModel.permissible)) {
return TargetModel.permissible(
modelId, actType, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasAppPermission
);
}
if (hasUserPermission && hasAppPermission) {
return;
}
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.permissions.noPermissionToAction')}));
});
};
return objTypeHandlers;
}, {});
};
CanThisResult.prototype.beginCheck = function (context) {
var self = this,
userPermissionLoad,
appPermissionLoad,
permissionsLoad;
// Get context.user and context.app
context = parseContext(context);
if (actionsMap.empty()) {
throw new Error(i18n.t('errors.permissions.noActionsMapFound.error'));
}
// Kick off loading of user permissions if necessary
if (context.user) {
userPermissionLoad = providers.user(context.user);
} else {
// Resolve null if no context.user to prevent db call
userPermissionLoad = Promise.resolve(null);
}
// Kick off loading of app permissions if necessary
if (context.app) {
appPermissionLoad = providers.app(context.app);
} else {
// Resolve null if no context.app
appPermissionLoad = Promise.resolve(null);
}
// Wait for both user and app permissions to load
permissionsLoad = Promise.all([userPermissionLoad, appPermissionLoad]).then(function (result) {
return {
user: result[0],
app: result[1]
};
});
// Iterate through the actions and their related object types
_.each(actionsMap.getAll(), function (objTypes, actType) {
// Build up the object type handlers;
// the '.post()' parts in canThis(user).edit.post()
var objTypeHandlers = self.buildObjectTypeHandlers(objTypes, actType, context, permissionsLoad);
// Define a property for the action on the result;
// the '.edit' in canThis(user).edit.post()
Object.defineProperty(self, actType, {
writable: false,
enumerable: false,
configurable: false,
value: objTypeHandlers
});
});
// Return this for chaining
return this;
};
canThis = function (context) {
var result = new CanThisResult();
return result.beginCheck(context);
};
module.exports = canThis;

View file

@ -1,232 +1,24 @@
// canThis(someUser).edit.posts([id]|[[ids]]) // canThis(someUser).edit.posts([id]|[[ids]])
// canThis(someUser).edit.post(somePost|somePostId) // canThis(someUser).edit.post(somePost|somePostId)
var _ = require('lodash'), var models = require('../models'),
Promise = require('bluebird'), actionsMap = require('./actions-map-cache'),
models = require('../models'), init;
errors = require('../errors'),
i18n = require('../i18n'),
effectivePerms = require('./effective'),
parseContext = require('./parse-context'),
init,
canThis,
CanThisResult,
exported;
function hasActionsMap() { init = function init(options) {
// Just need to find one key in the actionsMap
return _.some(exported.actionsMap, function (val, key) {
/*jslint unparam:true*/
return Object.hasOwnProperty.call(exported.actionsMap, key);
});
}
// Base class for canThis call results
CanThisResult = function () {
return;
};
CanThisResult.prototype.buildObjectTypeHandlers = function (objTypes, actType, context, permissionLoad) {
var objectTypeModelMap = {
post: models.Post,
role: models.Role,
user: models.User,
permission: models.Permission,
setting: models.Settings,
subscriber: models.Subscriber,
invite: models.Invite
};
// Iterate through the object types, i.e. ['post', 'tag', 'user']
return _.reduce(objTypes, function (objTypeHandlers, objType) {
// Grab the TargetModel through the objectTypeModelMap
var TargetModel = objectTypeModelMap[objType];
// Create the 'handler' for the object type;
// the '.post()' in canThis(user).edit.post()
objTypeHandlers[objType] = function (modelOrId, unsafeAttrs) {
var modelId;
unsafeAttrs = unsafeAttrs || {};
// If it's an internal request, resolve immediately
if (context.internal) {
return Promise.resolve();
}
if (_.isNumber(modelOrId) || _.isString(modelOrId)) {
// It's an id already, do nothing
modelId = modelOrId;
} else if (modelOrId) {
// It's a model, get the id
modelId = modelOrId.id;
}
// Wait for the user loading to finish
return permissionLoad.then(function (loadedPermissions) {
// Iterate through the user permissions looking for an affirmation
var userPermissions = loadedPermissions.user ? loadedPermissions.user.permissions : null,
appPermissions = loadedPermissions.app ? loadedPermissions.app.permissions : null,
hasUserPermission,
hasAppPermission,
checkPermission = function (perm) {
var permObjId;
// Look for a matching action type and object type first
if (perm.get('action_type') !== actType || perm.get('object_type') !== objType) {
return false;
}
// Grab the object id (if specified, could be null)
permObjId = perm.get('object_id');
// If we didn't specify a model (any thing)
// or the permission didn't have an id scope set
// then the "thing" has permission
if (!modelId || !permObjId) {
return true;
}
// Otherwise, check if the id's match
// TODO: String vs Int comparison possibility here?
return modelId === permObjId;
};
if (loadedPermissions.user && _.some(loadedPermissions.user.roles, {name: 'Owner'})) {
hasUserPermission = true;
} else if (!_.isEmpty(userPermissions)) {
hasUserPermission = _.some(userPermissions, checkPermission);
}
// Check app permissions if they were passed
hasAppPermission = true;
if (!_.isNull(appPermissions)) {
hasAppPermission = _.some(appPermissions, checkPermission);
}
// Offer a chance for the TargetModel to override the results
if (TargetModel && _.isFunction(TargetModel.permissible)) {
return TargetModel.permissible(
modelId, actType, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasAppPermission
);
}
if (hasUserPermission && hasAppPermission) {
return;
}
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.permissions.noPermissionToAction')}));
});
};
return objTypeHandlers;
}, {});
};
CanThisResult.prototype.beginCheck = function (context) {
var self = this,
userPermissionLoad,
appPermissionLoad,
permissionsLoad;
// Get context.user and context.app
context = parseContext(context);
if (!hasActionsMap()) {
throw new Error(i18n.t('errors.permissions.noActionsMapFound.error'));
}
// Kick off loading of effective user permissions if necessary
if (context.user) {
userPermissionLoad = effectivePerms.user(context.user);
} else {
// Resolve null if no context.user to prevent db call
userPermissionLoad = Promise.resolve(null);
}
// Kick off loading of effective app permissions if necessary
if (context.app) {
appPermissionLoad = effectivePerms.app(context.app);
} else {
// Resolve null if no context.app
appPermissionLoad = Promise.resolve(null);
}
// Wait for both user and app permissions to load
permissionsLoad = Promise.all([userPermissionLoad, appPermissionLoad]).then(function (result) {
return {
user: result[0],
app: result[1]
};
});
// Iterate through the actions and their related object types
_.each(exported.actionsMap, function (objTypes, actType) {
// Build up the object type handlers;
// the '.post()' parts in canThis(user).edit.post()
var objTypeHandlers = self.buildObjectTypeHandlers(objTypes, actType, context, permissionsLoad);
// Define a property for the action on the result;
// the '.edit' in canThis(user).edit.post()
Object.defineProperty(self, actType, {
writable: false,
enumerable: false,
configurable: false,
value: objTypeHandlers
});
});
// Return this for chaining
return this;
};
canThis = function (context) {
var result = new CanThisResult();
return result.beginCheck(context);
};
init = function (options) {
options = options || {}; options = options || {};
// Load all the permissions // Load all the permissions
return models.Permission.findAll(options).then(function (perms) { return models.Permission.findAll(options)
var seenActions = {}; .then(function (permissionsCollection) {
return actionsMap.init(permissionsCollection);
exported.actionsMap = {};
// Build a hash map of the actions on objects, i.e
/*
{
'edit': ['post', 'tag', 'user', 'page'],
'delete': ['post', 'user'],
'create': ['post', 'user', 'page']
}
*/
_.each(perms.models, function (perm) {
var actionType = perm.get('action_type'),
objectType = perm.get('object_type');
exported.actionsMap[actionType] = exported.actionsMap[actionType] || [];
seenActions[actionType] = seenActions[actionType] || {};
// Check if we've already seen this action -> object combo
if (seenActions[actionType][objectType]) {
return;
}
exported.actionsMap[actionType].push(objectType);
seenActions[actionType][objectType] = true;
});
return exported.actionsMap;
}); });
}; };
module.exports = exported = { module.exports = {
init: init, init: init,
canThis: canThis, canThis: require('./can-this'),
// @TODO: Make it so that we don't need to export these // @TODO: Make it so that we don't need to export these
parseContext: parseContext, parseContext: require('./parse-context'),
applyPublicRules: require('./public'), applyPublicRules: require('./public')
actionsMap: {}
}; };

View file

@ -2,10 +2,9 @@ var _ = require('lodash'),
Promise = require('bluebird'), Promise = require('bluebird'),
models = require('../models'), models = require('../models'),
errors = require('../errors'), errors = require('../errors'),
i18n = require('../i18n'), i18n = require('../i18n');
effective;
effective = { module.exports = {
user: function (id) { user: function (id) {
return models.User.findOne({id: id, status: 'all'}, {include: ['permissions', 'roles', 'roles.permissions']}) return models.User.findOne({id: id, status: 'all'}, {include: ['permissions', 'roles', 'roles.permissions']})
.then(function (foundUser) { .then(function (foundUser) {
@ -55,5 +54,3 @@ effective = {
}); });
} }
}; };
module.exports = effective;

View file

@ -0,0 +1,601 @@
var should = require('should'), // jshint ignore:line
sinon = require('sinon'),
testUtils = require('../../utils'),
Promise = require('bluebird'),
_ = require('lodash'),
models = require('../../../server/models'),
permissions = require('../../../server/permissions'),
providers = require('../../../server/permissions/providers'),
sandbox = sinon.sandbox.create();
describe('Permissions', function () {
var fakePermissions = [],
findPostSpy,
findTagSpy;
before(function () {
models.init();
});
beforeEach(function () {
sandbox.stub(models.Permission, 'findAll', function () {
return Promise.resolve(models.Permissions.forge(fakePermissions));
});
findPostSpy = sandbox.stub(models.Post, 'findOne', function () {
return Promise.resolve(models.Post.forge(testUtils.DataGenerator.Content.posts[0]));
});
findTagSpy = sandbox.stub(models.Tag, 'findOne', function () {
return Promise.resolve({});
});
});
afterEach(function () {
sandbox.restore();
});
/**
* Default test actionMap looks like this:
* {
* browse: [ 'post' ],
* edit: [ 'post', 'tag', 'user', 'page' ],
* add: [ 'post', 'user', 'page' ],
* destroy: [ 'post', 'user' ]
* }
*
* @param {object} options
* @return {Array|*}
*/
function loadFakePermissions(options) {
options = options || {};
var fixturePermissions = _.cloneDeep(testUtils.DataGenerator.Content.permissions),
extraPerm = {
name: 'test',
action_type: 'edit',
object_type: 'post'
};
if (options.extra) {
fixturePermissions.push(extraPerm);
}
return _.map(fixturePermissions, function (testPerm) {
return testUtils.DataGenerator.forKnex.createPermission(testPerm);
});
}
describe('CanThis', function () {
beforeEach(function () {
fakePermissions = loadFakePermissions();
return permissions.init();
});
it('canThisResult gets build properly', function () {
var canThisResult = permissions.canThis();
canThisResult.browse.should.be.an.Object();
canThisResult.browse.post.should.be.a.Function();
canThisResult.edit.should.be.an.Object();
canThisResult.edit.post.should.be.a.Function();
canThisResult.edit.tag.should.be.a.Function();
canThisResult.edit.user.should.be.a.Function();
canThisResult.edit.page.should.be.a.Function();
canThisResult.add.should.be.an.Object();
canThisResult.add.post.should.be.a.Function();
canThisResult.add.user.should.be.a.Function();
canThisResult.add.page.should.be.a.Function();
canThisResult.destroy.should.be.an.Object();
canThisResult.destroy.post.should.be.a.Function();
canThisResult.destroy.user.should.be.a.Function();
});
describe('Non user/app permissions', function () {
// TODO change to using fake models in tests!
// Permissions need to be NOT fundamentally baked into Ghost, but a separate module, at some point
// It can depend on bookshelf, but should NOT use hard coded model knowledge.
describe('with permissible calls (post model)', function () {
it('No context: does not allow edit post (no model)', function (done) {
permissions
.canThis() // no context
.edit
.post() // post id
.then(function () {
done(new Error('was able to edit post without permission'));
})
.catch(function (err) {
err.errorType.should.eql('NoPermissionError');
findPostSpy.callCount.should.eql(0);
done();
});
});
it('No context: does not allow edit post (model syntax)', function (done) {
permissions
.canThis() // no context
.edit
.post({id: 1}) // post id in model syntax
.then(function () {
done(new Error('was able to edit post without permission'));
})
.catch(function (err) {
err.errorType.should.eql('NoPermissionError');
findPostSpy.callCount.should.eql(1);
findPostSpy.firstCall.args[0].should.eql({id: 1, status: 'all'});
done();
});
});
it('No context: does not allow edit post (model ID syntax)', function (done) {
permissions
.canThis({}) // no context
.edit
.post(1) // post id using number syntax
.then(function () {
done(new Error('was able to edit post without permission'));
})
.catch(function (err) {
err.errorType.should.eql('NoPermissionError');
findPostSpy.callCount.should.eql(1);
findPostSpy.firstCall.args[0].should.eql({id: 1, status: 'all'});
done();
});
});
it('Internal context: instantly grants permissions', function (done) {
permissions
.canThis({internal: true}) // internal context
.edit
.post({id: 1}) // post id
.then(function () {
// We don't get this far, permissions are instantly granted for internal
findPostSpy.callCount.should.eql(0);
done();
})
.catch(function () {
done(new Error('Should allow editing post with { internal: true }'));
});
});
it('External context: does not grant permissions', function (done) {
permissions
.canThis({external: true}) // internal context
.edit
.post({id: 1}) // post id
.then(function () {
done(new Error('was able to edit post without permission'));
})
.catch(function (err) {
err.errorType.should.eql('NoPermissionError');
findPostSpy.callCount.should.eql(1);
findPostSpy.firstCall.args[0].should.eql({id: 1, status: 'all'});
done();
});
});
});
describe('without permissible (tag model)', function () {
it('No context: does not allow edit tag (model syntax)', function (done) {
permissions
.canThis() // no context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function () {
done(new Error('was able to edit tag without permission'));
})
.catch(function (err) {
err.errorType.should.eql('NoPermissionError');
// We don't look up tags
findTagSpy.callCount.should.eql(0);
done();
});
});
it('Internal context: instantly grants permissions', function (done) {
permissions
.canThis({internal: true}) // internal context
.edit
.tag({id: 1}) // tag id
.then(function () {
// We don't look up tags
findTagSpy.callCount.should.eql(0);
done();
})
.catch(function () {
done(new Error('Should allow editing post with { internal: true }'));
});
});
it('External context: does not grant permissions', function (done) {
permissions
.canThis({external: true}) // external context
.edit
.tag({id: 1}) // tag id
.then(function () {
done(new Error('was able to edit tag without permission'));
})
.catch(function (err) {
err.errorType.should.eql('NoPermissionError');
findTagSpy.callCount.should.eql(0);
done();
});
});
});
});
describe('User-based permissions', function () {
// TODO change to using fake models in tests!
// Permissions need to be NOT fundamentally baked into Ghost, but a separate module, at some point
// It can depend on bookshelf, but should NOT use hard coded model knowledge.
// We use the tag model here because it doesn't have permissible, once that changes, these tests must also change
it('No permissions: cannot edit tag (no permissible function on model)', function (done) {
var userProviderStub = sandbox.stub(providers, 'user', function () {
// Fake the response from providers.user, which contains permissions and roles
return Promise.resolve({
permissions: [],
roles: undefined
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function () {
done(new Error('was able to edit tag without permission'));
})
.catch(function (err) {
userProviderStub.callCount.should.eql(1);
err.errorType.should.eql('NoPermissionError');
done();
});
});
it('With permissions: can edit specific tag (no permissible function on model)', function (done) {
var userProviderStub = sandbox.stub(providers, 'user', function () {
// Fake the response from providers.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
roles: undefined
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function (res) {
userProviderStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
it('With permissions: can edit non-specific tag (no permissible function on model)', function (done) {
var userProviderStub = sandbox.stub(providers, 'user', function () {
// Fake the response from providers.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
roles: undefined
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag() // tag id in model syntax
.then(function (res) {
userProviderStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
it('Specific permissions: can edit correct specific tag (no permissible function on model)', function (done) {
var userProviderStub = sandbox.stub(providers, 'user', function () {
// Fake the response from providers.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge([
{
id: 'abc123',
name: 'test',
action_type: 'edit',
object_type: 'tag',
object_id: 1
}
]).models,
roles: undefined
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function (res) {
userProviderStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
it('Specific permissions: cannot edit incorrect specific tag (no permissible function on model)', function (done) {
var userProviderStub = sandbox.stub(providers, 'user', function () {
// Fake the response from providers.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge([
{
id: 'abc123',
name: 'test',
action_type: 'edit',
object_type: 'tag',
object_id: 1
}
]).models,
roles: undefined
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag({id: 10}) // tag id in model syntax
.then(function () {
done(new Error('was able to edit tag without permission'));
})
.catch(function (err) {
userProviderStub.callCount.should.eql(1);
err.errorType.should.eql('NoPermissionError');
done();
});
});
// @TODO fix this case - it makes no sense?!
it('Specific permissions: CAN edit non-specific tag (no permissible function on model) @TODO fix this', function (done) {
var userProviderStub = sandbox.stub(providers, 'user', function () {
// Fake the response from providers.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge([
{
id: 'abc123',
name: 'test',
action_type: 'edit',
object_type: 'tag',
object_id: 1
}
]).models,
roles: undefined
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag() // tag id in model syntax
.then(function (res) {
userProviderStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
it('With owner role: can edit tag (no permissible function on model)', function (done) {
var userProviderStub = sandbox.stub(providers, 'user', function () {
// Fake the response from providers.user, which contains permissions and roles
return Promise.resolve({
permissions: [],
// This should be JSON, so no need to run it through the model layer. 3 === owner
roles: [testUtils.DataGenerator.Content.roles[3]]
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function (res) {
userProviderStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
});
describe('App-based permissions (requires user as well)', function () {
// @TODO: revisit this - do we really need to have USER permissions AND app permissions?
it('No permissions: cannot edit tag with app only (no permissible function on model)', function (done) {
var appProviderStub = sandbox.stub(providers, 'app', function () {
// Fake the response from providers.app, which contains an empty array for this case
return Promise.resolve([]);
});
permissions
.canThis({app: {}}) // app context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function () {
done(new Error('was able to edit tag without permission'));
})
.catch(function (err) {
appProviderStub.callCount.should.eql(1);
err.errorType.should.eql('NoPermissionError');
done();
});
});
it('No permissions: cannot edit tag (no permissible function on model)', function (done) {
var appProviderStub = sandbox.stub(providers, 'app', function () {
// Fake the response from providers.app, which contains an empty array for this case
return Promise.resolve([]);
}),
userProviderStub = sandbox.stub(providers, 'user', function () {
// Fake the response from providers.user, which contains permissions and roles
return Promise.resolve({
permissions: [],
roles: undefined
});
});
permissions
.canThis({app: {}, user: {}}) // app context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function () {
done(new Error('was able to edit tag without permission'));
})
.catch(function (err) {
appProviderStub.callCount.should.eql(1);
userProviderStub.callCount.should.eql(1);
err.errorType.should.eql('NoPermissionError');
done();
});
});
it('With permissions: can edit specific tag (no permissible function on model)', function (done) {
var appProviderStub = sandbox.stub(providers, 'app', function () {
// Fake the response from providers.app, which contains permissions only
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models
});
}),
userProviderStub = sandbox.stub(providers, 'user', function () {
// Fake the response from providers.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
roles: undefined
});
});
permissions
.canThis({app: {}, user: {}}) // app context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function (res) {
appProviderStub.callCount.should.eql(1);
userProviderStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
it('With permissions: can edit non-specific tag (no permissible function on model)', function (done) {
var appProviderStub = sandbox.stub(providers, 'app', function () {
// Fake the response from providers.app, which contains permissions only
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models
});
}),
userProviderStub = sandbox.stub(providers, 'user', function () {
// Fake the response from providers.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
roles: undefined
});
});
permissions
.canThis({app: {}, user: {}}) // app context
.edit
.tag() // tag id in model syntax
.then(function (res) {
appProviderStub.callCount.should.eql(1);
userProviderStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
});
});
describe('permissible (overridden)', function () {
it('can use permissible function on model to forbid something (post model)', function (done) {
var userProviderStub = sandbox.stub(providers, 'user', function () {
// Fake the response from providers.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
roles: undefined
});
}),
permissibleStub = sandbox.stub(models.Post, 'permissible', function () {
return Promise.reject({message: 'Hello World!'});
});
permissions
.canThis({user: {}}) // user context
.edit
.post({id: 1}) // tag id in model syntax
.then(function () {
done(new Error('was able to edit post without permission'));
})
.catch(function (err) {
permissibleStub.callCount.should.eql(1);
permissibleStub.firstCall.args.should.have.lengthOf(7);
permissibleStub.firstCall.args[0].should.eql(1);
permissibleStub.firstCall.args[1].should.eql('edit');
permissibleStub.firstCall.args[2].should.be.an.Object();
permissibleStub.firstCall.args[3].should.be.an.Object();
permissibleStub.firstCall.args[4].should.be.an.Object();
permissibleStub.firstCall.args[5].should.be.true();
permissibleStub.firstCall.args[6].should.be.true();
userProviderStub.callCount.should.eql(1);
err.message.should.eql('Hello World!');
done();
});
});
it('can use permissible function on model to allow something (post model)', function (done) {
var userProviderStub = sandbox.stub(providers, 'user', function () {
// Fake the response from providers.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
roles: undefined
});
}),
permissibleStub = sandbox.stub(models.Post, 'permissible', function () {
return Promise.resolve();
});
permissions
.canThis({user: {}}) // user context
.edit
.post({id: 1}) // tag id in model syntax
.then(function (res) {
permissibleStub.callCount.should.eql(1);
permissibleStub.firstCall.args.should.have.lengthOf(7);
permissibleStub.firstCall.args[0].should.eql(1);
permissibleStub.firstCall.args[1].should.eql('edit');
permissibleStub.firstCall.args[2].should.be.an.Object();
permissibleStub.firstCall.args[3].should.be.an.Object();
permissibleStub.firstCall.args[4].should.be.an.Object();
permissibleStub.firstCall.args[5].should.be.true();
permissibleStub.firstCall.args[6].should.be.true();
userProviderStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
});
});

View file

@ -4,8 +4,8 @@ var should = require('should'), // jshint ignore:line
Promise = require('bluebird'), Promise = require('bluebird'),
_ = require('lodash'), _ = require('lodash'),
models = require('../../../server/models'), models = require('../../../server/models'),
actionsMap = require('../../../server/permissions/actions-map-cache'),
permissions = require('../../../server/permissions'), permissions = require('../../../server/permissions'),
effective = require('../../../server/permissions/effective'),
sandbox = sinon.sandbox.create(); sandbox = sinon.sandbox.create();
@ -51,7 +51,7 @@ describe('Permissions', function () {
function loadFakePermissions(options) { function loadFakePermissions(options) {
options = options || {}; options = options || {};
var fixturePermissions = testUtils.DataGenerator.Content.permissions, var fixturePermissions = _.cloneDeep(testUtils.DataGenerator.Content.permissions),
extraPerm = { extraPerm = {
name: 'test', name: 'test',
action_type: 'edit', action_type: 'edit',
@ -68,7 +68,9 @@ describe('Permissions', function () {
} }
describe('No init (no action map)', function () { describe('No init (no action map)', function () {
it('throws an error without init', function () { it('throws an error without actionMap', function () {
sandbox.stub(actionsMap, 'empty').returns(true);
permissions.canThis.should.throw(/No actions map found/); permissions.canThis.should.throw(/No actions map found/);
}); });
}); });
@ -80,6 +82,8 @@ describe('Permissions', function () {
permissions.init().then(function (actionsMap) { permissions.init().then(function (actionsMap) {
should.exist(actionsMap); should.exist(actionsMap);
permissions.canThis.should.not.throwError();
_.keys(actionsMap).should.eql(['browse', 'edit', 'add', 'destroy']); _.keys(actionsMap).should.eql(['browse', 'edit', 'add', 'destroy']);
actionsMap.browse.should.eql(['post']); actionsMap.browse.should.eql(['post']);
@ -87,8 +91,6 @@ describe('Permissions', function () {
actionsMap.add.should.eql(['post', 'user', 'page']); actionsMap.add.should.eql(['post', 'user', 'page']);
actionsMap.destroy.should.eql(['post', 'user']); actionsMap.destroy.should.eql(['post', 'user']);
actionsMap.should.equal(permissions.actionsMap);
done(); done();
}).catch(done); }).catch(done);
}); });
@ -99,6 +101,8 @@ describe('Permissions', function () {
permissions.init().then(function (actionsMap) { permissions.init().then(function (actionsMap) {
should.exist(actionsMap); should.exist(actionsMap);
permissions.canThis.should.not.throwError();
_.keys(actionsMap).should.eql(['browse', 'edit', 'add', 'destroy']); _.keys(actionsMap).should.eql(['browse', 'edit', 'add', 'destroy']);
actionsMap.browse.should.eql(['post']); actionsMap.browse.should.eql(['post']);
@ -106,542 +110,8 @@ describe('Permissions', function () {
actionsMap.add.should.eql(['post', 'user', 'page']); actionsMap.add.should.eql(['post', 'user', 'page']);
actionsMap.destroy.should.eql(['post', 'user']); actionsMap.destroy.should.eql(['post', 'user']);
actionsMap.should.equal(permissions.actionsMap);
done(); done();
}).catch(done); }).catch(done);
}); });
}); });
describe('CanThis', function () {
beforeEach(function () {
fakePermissions = loadFakePermissions();
return permissions.init();
});
it('canThisResult gets build properly', function () {
var canThisResult = permissions.canThis();
canThisResult.browse.should.be.an.Object();
canThisResult.browse.post.should.be.a.Function();
canThisResult.edit.should.be.an.Object();
canThisResult.edit.post.should.be.a.Function();
canThisResult.edit.tag.should.be.a.Function();
canThisResult.edit.user.should.be.a.Function();
canThisResult.edit.page.should.be.a.Function();
canThisResult.add.should.be.an.Object();
canThisResult.add.post.should.be.a.Function();
canThisResult.add.user.should.be.a.Function();
canThisResult.add.page.should.be.a.Function();
canThisResult.destroy.should.be.an.Object();
canThisResult.destroy.post.should.be.a.Function();
canThisResult.destroy.user.should.be.a.Function();
});
describe('Non user/app permissions', function () {
// TODO change to using fake models in tests!
// Permissions need to be NOT fundamentally baked into Ghost, but a separate module, at some point
// It can depend on bookshelf, but should NOT use hard coded model knowledge.
describe('with permissible calls (post model)', function () {
it('No context: does not allow edit post (no model)', function (done) {
permissions
.canThis() // no context
.edit
.post() // post id
.then(function () {
done(new Error('was able to edit post without permission'));
})
.catch(function (err) {
err.errorType.should.eql('NoPermissionError');
findPostSpy.callCount.should.eql(0);
done();
});
});
it('No context: does not allow edit post (model syntax)', function (done) {
permissions
.canThis() // no context
.edit
.post({id: 1}) // post id in model syntax
.then(function () {
done(new Error('was able to edit post without permission'));
})
.catch(function (err) {
err.errorType.should.eql('NoPermissionError');
findPostSpy.callCount.should.eql(1);
findPostSpy.firstCall.args[0].should.eql({id: 1, status: 'all'});
done();
});
});
it('No context: does not allow edit post (model ID syntax)', function (done) {
permissions
.canThis({}) // no context
.edit
.post(1) // post id using number syntax
.then(function () {
done(new Error('was able to edit post without permission'));
})
.catch(function (err) {
err.errorType.should.eql('NoPermissionError');
findPostSpy.callCount.should.eql(1);
findPostSpy.firstCall.args[0].should.eql({id: 1, status: 'all'});
done();
});
});
it('Internal context: instantly grants permissions', function (done) {
permissions
.canThis({internal: true}) // internal context
.edit
.post({id: 1}) // post id
.then(function () {
// We don't get this far, permissions are instantly granted for internal
findPostSpy.callCount.should.eql(0);
done();
})
.catch(function () {
done(new Error('Should allow editing post with { internal: true }'));
});
});
it('External context: does not grant permissions', function (done) {
permissions
.canThis({external: true}) // internal context
.edit
.post({id: 1}) // post id
.then(function () {
done(new Error('was able to edit post without permission'));
})
.catch(function (err) {
err.errorType.should.eql('NoPermissionError');
findPostSpy.callCount.should.eql(1);
findPostSpy.firstCall.args[0].should.eql({id: 1, status: 'all'});
done();
});
});
});
describe('without permissible (tag model)', function () {
it('No context: does not allow edit tag (model syntax)', function (done) {
permissions
.canThis() // no context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function () {
done(new Error('was able to edit tag without permission'));
})
.catch(function (err) {
err.errorType.should.eql('NoPermissionError');
// We don't look up tags
findTagSpy.callCount.should.eql(0);
done();
});
});
it('Internal context: instantly grants permissions', function (done) {
permissions
.canThis({internal: true}) // internal context
.edit
.tag({id: 1}) // tag id
.then(function () {
// We don't look up tags
findTagSpy.callCount.should.eql(0);
done();
})
.catch(function () {
done(new Error('Should allow editing post with { internal: true }'));
});
});
it('External context: does not grant permissions', function (done) {
permissions
.canThis({external: true}) // external context
.edit
.tag({id: 1}) // tag id
.then(function () {
done(new Error('was able to edit tag without permission'));
})
.catch(function (err) {
err.errorType.should.eql('NoPermissionError');
findTagSpy.callCount.should.eql(0);
done();
});
});
});
});
describe('User-based permissions', function () {
// TODO change to using fake models in tests!
// Permissions need to be NOT fundamentally baked into Ghost, but a separate module, at some point
// It can depend on bookshelf, but should NOT use hard coded model knowledge.
// We use the tag model here because it doesn't have permissible, once that changes, these tests must also change
it('No permissions: cannot edit tag (no permissible function on model)', function (done) {
var effectiveUserStub = sandbox.stub(effective, 'user', function () {
// Fake the response from effective.user, which contains permissions and roles
return Promise.resolve({
permissions: [],
roles: undefined
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function () {
done(new Error('was able to edit tag without permission'));
})
.catch(function (err) {
effectiveUserStub.callCount.should.eql(1);
err.errorType.should.eql('NoPermissionError');
done();
});
});
it('With permissions: can edit specific tag (no permissible function on model)', function (done) {
var effectiveUserStub = sandbox.stub(effective, 'user', function () {
// Fake the response from effective.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
roles: undefined
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function (res) {
effectiveUserStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
it('With permissions: can edit non-specific tag (no permissible function on model)', function (done) {
var effectiveUserStub = sandbox.stub(effective, 'user', function () {
// Fake the response from effective.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
roles: undefined
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag() // tag id in model syntax
.then(function (res) {
effectiveUserStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
it('Specific permissions: can edit correct specific tag (no permissible function on model)', function (done) {
var effectiveUserStub = sandbox.stub(effective, 'user', function () {
// Fake the response from effective.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge([
{
id: 'abc123',
name: 'test',
action_type: 'edit',
object_type: 'tag',
object_id: 1
}
]).models,
roles: undefined
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function (res) {
effectiveUserStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
it('Specific permissions: cannot edit incorrect specific tag (no permissible function on model)', function (done) {
var effectiveUserStub = sandbox.stub(effective, 'user', function () {
// Fake the response from effective.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge([
{
id: 'abc123',
name: 'test',
action_type: 'edit',
object_type: 'tag',
object_id: 1
}
]).models,
roles: undefined
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag({id: 10}) // tag id in model syntax
.then(function () {
done(new Error('was able to edit tag without permission'));
})
.catch(function (err) {
effectiveUserStub.callCount.should.eql(1);
err.errorType.should.eql('NoPermissionError');
done();
});
});
// @TODO fix this case - it makes no sense?!
it('Specific permissions: CAN edit non-specific tag (no permissible function on model) @TODO fix this', function (done) {
var effectiveUserStub = sandbox.stub(effective, 'user', function () {
// Fake the response from effective.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge([
{
id: 'abc123',
name: 'test',
action_type: 'edit',
object_type: 'tag',
object_id: 1
}
]).models,
roles: undefined
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag() // tag id in model syntax
.then(function (res) {
effectiveUserStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
it('With owner role: can edit tag (no permissible function on model)', function (done) {
var effectiveUserStub = sandbox.stub(effective, 'user', function () {
// Fake the response from effective.user, which contains permissions and roles
return Promise.resolve({
permissions: [],
// This should be JSON, so no need to run it through the model layer. 3 === owner
roles: [testUtils.DataGenerator.Content.roles[3]]
});
});
permissions
.canThis({user: {}}) // user context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function (res) {
effectiveUserStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
});
describe('App-based permissions (requires user as well)', function () {
// @TODO: revisit this - do we really need to have USER permissions AND app permissions?
it('No permissions: cannot edit tag with app only (no permissible function on model)', function (done) {
var effectiveAppStub = sandbox.stub(effective, 'app', function () {
// Fake the response from effective.app, which contains an empty array for this case
return Promise.resolve([]);
});
permissions
.canThis({app: {}}) // app context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function () {
done(new Error('was able to edit tag without permission'));
})
.catch(function (err) {
effectiveAppStub.callCount.should.eql(1);
err.errorType.should.eql('NoPermissionError');
done();
});
});
it('No permissions: cannot edit tag (no permissible function on model)', function (done) {
var effectiveAppStub = sandbox.stub(effective, 'app', function () {
// Fake the response from effective.app, which contains an empty array for this case
return Promise.resolve([]);
}),
effectiveUserStub = sandbox.stub(effective, 'user', function () {
// Fake the response from effective.user, which contains permissions and roles
return Promise.resolve({
permissions: [],
roles: undefined
});
});
permissions
.canThis({app: {}, user: {}}) // app context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function () {
done(new Error('was able to edit tag without permission'));
})
.catch(function (err) {
effectiveAppStub.callCount.should.eql(1);
effectiveUserStub.callCount.should.eql(1);
err.errorType.should.eql('NoPermissionError');
done();
});
});
it('With permissions: can edit specific tag (no permissible function on model)', function (done) {
var effectiveAppStub = sandbox.stub(effective, 'app', function () {
// Fake the response from effective.app, which contains permissions only
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models
});
}),
effectiveUserStub = sandbox.stub(effective, 'user', function () {
// Fake the response from effective.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
roles: undefined
});
});
permissions
.canThis({app: {}, user: {}}) // app context
.edit
.tag({id: 1}) // tag id in model syntax
.then(function (res) {
effectiveAppStub.callCount.should.eql(1);
effectiveUserStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
it('With permissions: can edit non-specific tag (no permissible function on model)', function (done) {
var effectiveAppStub = sandbox.stub(effective, 'app', function () {
// Fake the response from effective.app, which contains permissions only
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models
});
}),
effectiveUserStub = sandbox.stub(effective, 'user', function () {
// Fake the response from effective.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
roles: undefined
});
});
permissions
.canThis({app: {}, user: {}}) // app context
.edit
.tag() // tag id in model syntax
.then(function (res) {
effectiveAppStub.callCount.should.eql(1);
effectiveUserStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
});
});
describe('permissible (overridden)', function () {
it('can use permissible function on model to forbid something (post model)', function (done) {
var effectiveUserStub = sandbox.stub(effective, 'user', function () {
// Fake the response from effective.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
roles: undefined
});
}),
permissibleStub = sandbox.stub(models.Post, 'permissible', function () {
return Promise.reject({message: 'Hello World!'});
});
permissions
.canThis({user: {}}) // user context
.edit
.post({id: 1}) // tag id in model syntax
.then(function () {
done(new Error('was able to edit post without permission'));
})
.catch(function (err) {
permissibleStub.callCount.should.eql(1);
permissibleStub.firstCall.args.should.have.lengthOf(7);
permissibleStub.firstCall.args[0].should.eql(1);
permissibleStub.firstCall.args[1].should.eql('edit');
permissibleStub.firstCall.args[2].should.be.an.Object();
permissibleStub.firstCall.args[3].should.be.an.Object();
permissibleStub.firstCall.args[4].should.be.an.Object();
permissibleStub.firstCall.args[5].should.be.true();
permissibleStub.firstCall.args[6].should.be.true();
effectiveUserStub.callCount.should.eql(1);
err.message.should.eql('Hello World!');
done();
});
});
it('can use permissible function on model to allow something (post model)', function (done) {
var effectiveUserStub = sandbox.stub(effective, 'user', function () {
// Fake the response from effective.user, which contains permissions and roles
return Promise.resolve({
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
roles: undefined
});
}),
permissibleStub = sandbox.stub(models.Post, 'permissible', function () {
return Promise.resolve();
});
permissions
.canThis({user: {}}) // user context
.edit
.post({id: 1}) // tag id in model syntax
.then(function (res) {
permissibleStub.callCount.should.eql(1);
permissibleStub.firstCall.args.should.have.lengthOf(7);
permissibleStub.firstCall.args[0].should.eql(1);
permissibleStub.firstCall.args[1].should.eql('edit');
permissibleStub.firstCall.args[2].should.be.an.Object();
permissibleStub.firstCall.args[3].should.be.an.Object();
permissibleStub.firstCall.args[4].should.be.an.Object();
permissibleStub.firstCall.args[5].should.be.true();
permissibleStub.firstCall.args[6].should.be.true();
effectiveUserStub.callCount.should.eql(1);
should.not.exist(res);
done();
})
.catch(done);
});
});
}); });

View file

@ -3,11 +3,11 @@ var should = require('should'),
testUtils = require('../../utils'), testUtils = require('../../utils'),
Promise = require('bluebird'), Promise = require('bluebird'),
models = require('../../../server/models'), models = require('../../../server/models'),
effective = require('../../../server/permissions/effective'), providers = require('../../../server/permissions/providers'),
sandbox = sinon.sandbox.create(); sandbox = sinon.sandbox.create();
describe('Effective Permissions', function () { describe('Permission Providers', function () {
before(function () { before(function () {
models.init(); models.init();
}); });
@ -22,7 +22,7 @@ describe('Effective Permissions', function () {
return Promise.resolve(); return Promise.resolve();
}); });
effective.user(1) providers.user(1)
.then(function () { .then(function () {
done(new Error('Should have thrown a user not found error')); done(new Error('Should have thrown a user not found error'));
}) })
@ -54,8 +54,8 @@ describe('Effective Permissions', function () {
return Promise.resolve(fakeUser); return Promise.resolve(fakeUser);
}); });
// Get effective permissions for the user // Get permissions for the user
effective.user(1) providers.user(1)
.then(function (res) { .then(function (res) {
findUserSpy.callCount.should.eql(1); findUserSpy.callCount.should.eql(1);
@ -101,8 +101,8 @@ describe('Effective Permissions', function () {
return Promise.resolve(fakeUser); return Promise.resolve(fakeUser);
}); });
// Get effective permissions for the user // Get permissions for the user
effective.user(1) providers.user(1)
.then(function (res) { .then(function (res) {
findUserSpy.callCount.should.eql(1); findUserSpy.callCount.should.eql(1);
@ -149,8 +149,8 @@ describe('Effective Permissions', function () {
return Promise.resolve(fakeUser); return Promise.resolve(fakeUser);
}); });
// Get effective permissions for the user // Get permissions for the user
effective.user(1) providers.user(1)
.then(function (res) { .then(function (res) {
findUserSpy.callCount.should.eql(1); findUserSpy.callCount.should.eql(1);
@ -182,7 +182,7 @@ describe('Effective Permissions', function () {
return Promise.resolve(); return Promise.resolve();
}); });
effective.app('test') providers.app('test')
.then(function (res) { .then(function (res) {
findAppSpy.callCount.should.eql(1); findAppSpy.callCount.should.eql(1);
res.should.be.an.Array().with.lengthOf(0); res.should.be.an.Array().with.lengthOf(0);
@ -206,8 +206,8 @@ describe('Effective Permissions', function () {
return Promise.resolve(fakeApp); return Promise.resolve(fakeApp);
}); });
// Get effective permissions for the app // Get permissions for the app
effective.app('kudos') providers.app('kudos')
.then(function (res) { .then(function (res) {
findAppSpy.callCount.should.eql(1); findAppSpy.callCount.should.eql(1);