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.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: {}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
|
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'),
|
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Reference in a new issue