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:
parent
c50415d9d6
commit
6760ccc8ec
7 changed files with 853 additions and 775 deletions
43
core/server/permissions/actions-map-cache.js
Normal file
43
core/server/permissions/actions-map-cache.js
Normal 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;
|
||||
}
|
||||
};
|
175
core/server/permissions/can-this.js
Normal file
175
core/server/permissions/can-this.js
Normal 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;
|
|
@ -1,232 +1,24 @@
|
|||
// canThis(someUser).edit.posts([id]|[[ids]])
|
||||
// canThis(someUser).edit.post(somePost|somePostId)
|
||||
|
||||
var _ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
models = require('../models'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
effectivePerms = require('./effective'),
|
||||
parseContext = require('./parse-context'),
|
||||
init,
|
||||
canThis,
|
||||
CanThisResult,
|
||||
exported;
|
||||
var models = require('../models'),
|
||||
actionsMap = require('./actions-map-cache'),
|
||||
init;
|
||||
|
||||
function hasActionsMap() {
|
||||
// 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) {
|
||||
init = function init(options) {
|
||||
options = options || {};
|
||||
|
||||
// Load all the permissions
|
||||
return models.Permission.findAll(options).then(function (perms) {
|
||||
var seenActions = {};
|
||||
|
||||
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;
|
||||
return models.Permission.findAll(options)
|
||||
.then(function (permissionsCollection) {
|
||||
return actionsMap.init(permissionsCollection);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = exported = {
|
||||
module.exports = {
|
||||
init: init,
|
||||
canThis: canThis,
|
||||
canThis: require('./can-this'),
|
||||
// @TODO: Make it so that we don't need to export these
|
||||
parseContext: parseContext,
|
||||
applyPublicRules: require('./public'),
|
||||
actionsMap: {}
|
||||
parseContext: require('./parse-context'),
|
||||
applyPublicRules: require('./public')
|
||||
};
|
||||
|
|
|
@ -2,10 +2,9 @@ var _ = require('lodash'),
|
|||
Promise = require('bluebird'),
|
||||
models = require('../models'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
effective;
|
||||
i18n = require('../i18n');
|
||||
|
||||
effective = {
|
||||
module.exports = {
|
||||
user: function (id) {
|
||||
return models.User.findOne({id: id, status: 'all'}, {include: ['permissions', 'roles', 'roles.permissions']})
|
||||
.then(function (foundUser) {
|
||||
|
@ -55,5 +54,3 @@ effective = {
|
|||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = effective;
|
601
core/test/unit/permissions/can-this_spec.js
Normal file
601
core/test/unit/permissions/can-this_spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,8 +4,8 @@ var should = require('should'), // jshint ignore:line
|
|||
Promise = require('bluebird'),
|
||||
_ = require('lodash'),
|
||||
models = require('../../../server/models'),
|
||||
actionsMap = require('../../../server/permissions/actions-map-cache'),
|
||||
permissions = require('../../../server/permissions'),
|
||||
effective = require('../../../server/permissions/effective'),
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
|
@ -51,7 +51,7 @@ describe('Permissions', function () {
|
|||
function loadFakePermissions(options) {
|
||||
options = options || {};
|
||||
|
||||
var fixturePermissions = testUtils.DataGenerator.Content.permissions,
|
||||
var fixturePermissions = _.cloneDeep(testUtils.DataGenerator.Content.permissions),
|
||||
extraPerm = {
|
||||
name: 'test',
|
||||
action_type: 'edit',
|
||||
|
@ -68,7 +68,9 @@ describe('Permissions', 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/);
|
||||
});
|
||||
});
|
||||
|
@ -80,6 +82,8 @@ describe('Permissions', function () {
|
|||
permissions.init().then(function (actionsMap) {
|
||||
should.exist(actionsMap);
|
||||
|
||||
permissions.canThis.should.not.throwError();
|
||||
|
||||
_.keys(actionsMap).should.eql(['browse', 'edit', 'add', 'destroy']);
|
||||
|
||||
actionsMap.browse.should.eql(['post']);
|
||||
|
@ -87,8 +91,6 @@ describe('Permissions', function () {
|
|||
actionsMap.add.should.eql(['post', 'user', 'page']);
|
||||
actionsMap.destroy.should.eql(['post', 'user']);
|
||||
|
||||
actionsMap.should.equal(permissions.actionsMap);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
@ -99,6 +101,8 @@ describe('Permissions', function () {
|
|||
permissions.init().then(function (actionsMap) {
|
||||
should.exist(actionsMap);
|
||||
|
||||
permissions.canThis.should.not.throwError();
|
||||
|
||||
_.keys(actionsMap).should.eql(['browse', 'edit', 'add', 'destroy']);
|
||||
|
||||
actionsMap.browse.should.eql(['post']);
|
||||
|
@ -106,542 +110,8 @@ describe('Permissions', function () {
|
|||
actionsMap.add.should.eql(['post', 'user', 'page']);
|
||||
actionsMap.destroy.should.eql(['post', 'user']);
|
||||
|
||||
actionsMap.should.equal(permissions.actionsMap);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,11 +3,11 @@ var should = require('should'),
|
|||
testUtils = require('../../utils'),
|
||||
Promise = require('bluebird'),
|
||||
models = require('../../../server/models'),
|
||||
effective = require('../../../server/permissions/effective'),
|
||||
providers = require('../../../server/permissions/providers'),
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('Effective Permissions', function () {
|
||||
describe('Permission Providers', function () {
|
||||
before(function () {
|
||||
models.init();
|
||||
});
|
||||
|
@ -22,7 +22,7 @@ describe('Effective Permissions', function () {
|
|||
return Promise.resolve();
|
||||
});
|
||||
|
||||
effective.user(1)
|
||||
providers.user(1)
|
||||
.then(function () {
|
||||
done(new Error('Should have thrown a user not found error'));
|
||||
})
|
||||
|
@ -54,8 +54,8 @@ describe('Effective Permissions', function () {
|
|||
return Promise.resolve(fakeUser);
|
||||
});
|
||||
|
||||
// Get effective permissions for the user
|
||||
effective.user(1)
|
||||
// Get permissions for the user
|
||||
providers.user(1)
|
||||
.then(function (res) {
|
||||
findUserSpy.callCount.should.eql(1);
|
||||
|
||||
|
@ -101,8 +101,8 @@ describe('Effective Permissions', function () {
|
|||
return Promise.resolve(fakeUser);
|
||||
});
|
||||
|
||||
// Get effective permissions for the user
|
||||
effective.user(1)
|
||||
// Get permissions for the user
|
||||
providers.user(1)
|
||||
.then(function (res) {
|
||||
findUserSpy.callCount.should.eql(1);
|
||||
|
||||
|
@ -149,8 +149,8 @@ describe('Effective Permissions', function () {
|
|||
return Promise.resolve(fakeUser);
|
||||
});
|
||||
|
||||
// Get effective permissions for the user
|
||||
effective.user(1)
|
||||
// Get permissions for the user
|
||||
providers.user(1)
|
||||
.then(function (res) {
|
||||
findUserSpy.callCount.should.eql(1);
|
||||
|
||||
|
@ -182,7 +182,7 @@ describe('Effective Permissions', function () {
|
|||
return Promise.resolve();
|
||||
});
|
||||
|
||||
effective.app('test')
|
||||
providers.app('test')
|
||||
.then(function (res) {
|
||||
findAppSpy.callCount.should.eql(1);
|
||||
res.should.be.an.Array().with.lengthOf(0);
|
||||
|
@ -206,8 +206,8 @@ describe('Effective Permissions', function () {
|
|||
return Promise.resolve(fakeApp);
|
||||
});
|
||||
|
||||
// Get effective permissions for the app
|
||||
effective.app('kudos')
|
||||
// Get permissions for the app
|
||||
providers.app('kudos')
|
||||
.then(function (res) {
|
||||
findAppSpy.callCount.should.eql(1);
|
||||
|
Loading…
Add table
Reference in a new issue