mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Permissions: code cleanup & basic unit tests (#9037)
refs #9043 - Split public-related and context code into logical components - Split tests up to match - Ensure we have 100% unit test coverage - General cleanup
This commit is contained in:
parent
e8f85fd3da
commit
9da7b956d5
10 changed files with 1240 additions and 615 deletions
|
@ -1,13 +1,13 @@
|
||||||
var _ = require('lodash'),
|
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;
|
||||||
|
|
||||||
effective = {
|
effective = {
|
||||||
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) {
|
||||||
// CASE: {context: {user: id}} where the id is not in our database
|
// CASE: {context: {user: id}} where the id is not in our database
|
||||||
if (!foundUser) {
|
if (!foundUser) {
|
||||||
|
@ -37,12 +37,15 @@ effective = {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// @TODO fix this!
|
||||||
|
// Permissions is an array of models
|
||||||
|
// Roles is a JSON array
|
||||||
return {permissions: allPerms, roles: user.roles};
|
return {permissions: allPerms, roles: user.roles};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
app: function (appName) {
|
app: function (appName) {
|
||||||
return Models.App.findOne({name: appName}, {withRelated: ['permissions']})
|
return models.App.findOne({name: appName}, {withRelated: ['permissions']})
|
||||||
.then(function (foundApp) {
|
.then(function (foundApp) {
|
||||||
if (!foundApp) {
|
if (!foundApp) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
|
|
||||||
var _ = require('lodash'),
|
var _ = require('lodash'),
|
||||||
Promise = require('bluebird'),
|
Promise = require('bluebird'),
|
||||||
|
models = require('../models'),
|
||||||
errors = require('../errors'),
|
errors = require('../errors'),
|
||||||
Models = require('../models'),
|
|
||||||
effectivePerms = require('./effective'),
|
|
||||||
i18n = require('../i18n'),
|
i18n = require('../i18n'),
|
||||||
|
effectivePerms = require('./effective'),
|
||||||
|
parseContext = require('./parse-context'),
|
||||||
init,
|
init,
|
||||||
refresh,
|
|
||||||
canThis,
|
canThis,
|
||||||
CanThisResult,
|
CanThisResult,
|
||||||
exported;
|
exported;
|
||||||
|
@ -22,94 +22,6 @@ function hasActionsMap() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseContext(context) {
|
|
||||||
// Parse what's passed to canThis.beginCheck for standard user and app scopes
|
|
||||||
var parsed = {
|
|
||||||
internal: false,
|
|
||||||
external: false,
|
|
||||||
user: null,
|
|
||||||
app: null,
|
|
||||||
public: true
|
|
||||||
};
|
|
||||||
|
|
||||||
if (context && (context === 'external' || context.external)) {
|
|
||||||
parsed.external = true;
|
|
||||||
parsed.public = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context && (context === 'internal' || context.internal)) {
|
|
||||||
parsed.internal = true;
|
|
||||||
parsed.public = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context && context.user) {
|
|
||||||
parsed.user = context.user;
|
|
||||||
parsed.public = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context && context.app) {
|
|
||||||
parsed.app = context.app;
|
|
||||||
parsed.public = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyStatusRules(docName, method, opts) {
|
|
||||||
var err = new errors.NoPermissionError({message: i18n.t('errors.permissions.applyStatusRules.error', {docName: docName})});
|
|
||||||
|
|
||||||
// Enforce status 'active' for users
|
|
||||||
if (docName === 'users') {
|
|
||||||
if (!opts.status) {
|
|
||||||
return 'all';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enforce status 'published' for posts
|
|
||||||
if (docName === 'posts') {
|
|
||||||
if (!opts.status) {
|
|
||||||
return 'published';
|
|
||||||
} else if (
|
|
||||||
method === 'read'
|
|
||||||
&& (opts.status === 'draft' || opts.status === 'all')
|
|
||||||
&& _.isString(opts.uuid) && _.isUndefined(opts.id) && _.isUndefined(opts.slug)
|
|
||||||
) {
|
|
||||||
// public read requests can retrieve a draft, but only by UUID
|
|
||||||
return opts.status;
|
|
||||||
} else if (opts.status !== 'published') {
|
|
||||||
// any other parameter would make this a permissions error
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return opts.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API Public Permission Rules
|
|
||||||
* This method enforces the rules for public requests
|
|
||||||
* @param {String} docName
|
|
||||||
* @param {String} method (read || browse)
|
|
||||||
* @param {Object} options
|
|
||||||
* @returns {Object} options
|
|
||||||
*/
|
|
||||||
function applyPublicRules(docName, method, options) {
|
|
||||||
try {
|
|
||||||
// If this is a public context
|
|
||||||
if (parseContext(options.context).public === true) {
|
|
||||||
if (method === 'browse') {
|
|
||||||
options.status = applyStatusRules(docName, method, options);
|
|
||||||
} else if (method === 'read') {
|
|
||||||
options.data.status = applyStatusRules(docName, method, options.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(options);
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base class for canThis call results
|
// Base class for canThis call results
|
||||||
CanThisResult = function () {
|
CanThisResult = function () {
|
||||||
return;
|
return;
|
||||||
|
@ -117,13 +29,13 @@ CanThisResult = function () {
|
||||||
|
|
||||||
CanThisResult.prototype.buildObjectTypeHandlers = function (objTypes, actType, context, permissionLoad) {
|
CanThisResult.prototype.buildObjectTypeHandlers = function (objTypes, actType, context, permissionLoad) {
|
||||||
var objectTypeModelMap = {
|
var objectTypeModelMap = {
|
||||||
post: Models.Post,
|
post: models.Post,
|
||||||
role: Models.Role,
|
role: models.Role,
|
||||||
user: Models.User,
|
user: models.User,
|
||||||
permission: Models.Permission,
|
permission: models.Permission,
|
||||||
setting: Models.Settings,
|
setting: models.Settings,
|
||||||
subscriber: Models.Subscriber,
|
subscriber: models.Subscriber,
|
||||||
invite: Models.Invite
|
invite: models.Invite
|
||||||
};
|
};
|
||||||
|
|
||||||
// Iterate through the object types, i.e. ['post', 'tag', 'user']
|
// Iterate through the object types, i.e. ['post', 'tag', 'user']
|
||||||
|
@ -272,11 +184,11 @@ canThis = function (context) {
|
||||||
return result.beginCheck(context);
|
return result.beginCheck(context);
|
||||||
};
|
};
|
||||||
|
|
||||||
init = refresh = function (options) {
|
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).then(function (perms) {
|
||||||
var seenActions = {};
|
var seenActions = {};
|
||||||
|
|
||||||
exported.actionsMap = {};
|
exported.actionsMap = {};
|
||||||
|
@ -311,9 +223,9 @@ init = refresh = function (options) {
|
||||||
|
|
||||||
module.exports = exported = {
|
module.exports = exported = {
|
||||||
init: init,
|
init: init,
|
||||||
refresh: refresh,
|
|
||||||
canThis: canThis,
|
canThis: canThis,
|
||||||
|
// @TODO: Make it so that we don't need to export these
|
||||||
parseContext: parseContext,
|
parseContext: parseContext,
|
||||||
applyPublicRules: applyPublicRules,
|
applyPublicRules: require('./public'),
|
||||||
actionsMap: {}
|
actionsMap: {}
|
||||||
};
|
};
|
||||||
|
|
39
core/server/permissions/parse-context.js
Normal file
39
core/server/permissions/parse-context.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/**
|
||||||
|
* Parse Context
|
||||||
|
*
|
||||||
|
* Utility function, to expand strings out into objects.
|
||||||
|
* @param {Object|String} context
|
||||||
|
* @return {{internal: boolean, external: boolean, user: integer|null, app: integer|null, public: boolean}}
|
||||||
|
*/
|
||||||
|
module.exports = function parseContext(context) {
|
||||||
|
// Parse what's passed to canThis.beginCheck for standard user and app scopes
|
||||||
|
var parsed = {
|
||||||
|
internal: false,
|
||||||
|
external: false,
|
||||||
|
user: null,
|
||||||
|
app: null,
|
||||||
|
public: true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (context && (context === 'external' || context.external)) {
|
||||||
|
parsed.external = true;
|
||||||
|
parsed.public = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context && (context === 'internal' || context.internal)) {
|
||||||
|
parsed.internal = true;
|
||||||
|
parsed.public = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context && context.user) {
|
||||||
|
parsed.user = context.user;
|
||||||
|
parsed.public = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context && context.app) {
|
||||||
|
parsed.app = context.app;
|
||||||
|
parsed.public = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
};
|
61
core/server/permissions/public.js
Normal file
61
core/server/permissions/public.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
var _ = require('lodash'),
|
||||||
|
Promise = require('bluebird'),
|
||||||
|
errors = require('../errors'),
|
||||||
|
i18n = require('../i18n'),
|
||||||
|
parseContext = require('./parse-context'),
|
||||||
|
_private = {};
|
||||||
|
|
||||||
|
_private.applyStatusRules = function applyStatusRules(docName, method, opts) {
|
||||||
|
var err = new errors.NoPermissionError({message: i18n.t('errors.permissions.applyStatusRules.error', {docName: docName})});
|
||||||
|
|
||||||
|
// Enforce status 'active' for users
|
||||||
|
if (docName === 'users') {
|
||||||
|
if (!opts.status) {
|
||||||
|
return 'all';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce status 'published' for posts
|
||||||
|
if (docName === 'posts') {
|
||||||
|
if (!opts.status) {
|
||||||
|
return 'published';
|
||||||
|
} else if (
|
||||||
|
method === 'read'
|
||||||
|
&& (opts.status === 'draft' || opts.status === 'all')
|
||||||
|
&& _.isString(opts.uuid) && _.isUndefined(opts.id) && _.isUndefined(opts.slug)
|
||||||
|
) {
|
||||||
|
// public read requests can retrieve a draft, but only by UUID
|
||||||
|
return opts.status;
|
||||||
|
} else if (opts.status !== 'published') {
|
||||||
|
// any other parameter would make this a permissions error
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts.status;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Public Permission Rules
|
||||||
|
* This method enforces the rules for public requests
|
||||||
|
* @param {String} docName
|
||||||
|
* @param {String} method (read || browse)
|
||||||
|
* @param {Object} options
|
||||||
|
* @returns {Object} options
|
||||||
|
*/
|
||||||
|
module.exports = function applyPublicRules(docName, method, options) {
|
||||||
|
try {
|
||||||
|
// If this is a public context
|
||||||
|
if (parseContext(options.context).public === true) {
|
||||||
|
if (method === 'browse') {
|
||||||
|
options.status = _private.applyStatusRules(docName, method, options);
|
||||||
|
} else if (method === 'read') {
|
||||||
|
options.data.status = _private.applyStatusRules(docName, method, options.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(options);
|
||||||
|
} catch (err) {
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
var should = require('should'),
|
var should = require('should'),
|
||||||
sinon = require('sinon'),
|
sinon = require('sinon'),
|
||||||
rewire = require('rewire'),
|
rewire = require('rewire'),
|
||||||
|
_ = require('lodash'),
|
||||||
|
|
||||||
// Thing we're testing
|
// Thing we're testing
|
||||||
filter = rewire('../../../server/models/plugins/filter'),
|
filter = rewire('../../../server/models/plugins/filter'),
|
||||||
|
@ -12,7 +13,7 @@ var should = require('should'),
|
||||||
describe('Filter', function () {
|
describe('Filter', function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
models.init();
|
models.init();
|
||||||
ghostBookshelf = models.Base;
|
ghostBookshelf = _.cloneDeep(models.Base);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
|
231
core/test/unit/permissions/effective_spec.js
Normal file
231
core/test/unit/permissions/effective_spec.js
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
var should = require('should'),
|
||||||
|
sinon = require('sinon'),
|
||||||
|
testUtils = require('../../utils'),
|
||||||
|
Promise = require('bluebird'),
|
||||||
|
models = require('../../../server/models'),
|
||||||
|
effective = require('../../../server/permissions/effective'),
|
||||||
|
|
||||||
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
|
describe('Effective Permissions', function () {
|
||||||
|
before(function () {
|
||||||
|
models.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('User', function () {
|
||||||
|
it('errors if user cannot be found', function (done) {
|
||||||
|
var findUserSpy = sandbox.stub(models.User, 'findOne', function () {
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
effective.user(1)
|
||||||
|
.then(function () {
|
||||||
|
done(new Error('Should have thrown a user not found error'));
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
findUserSpy.callCount.should.eql(1);
|
||||||
|
err.errorType.should.eql('NotFoundError');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can load user with role, and permissions', function (done) {
|
||||||
|
// This test requires quite a lot of unique setup work
|
||||||
|
var findUserSpy = sandbox.stub(models.User, 'findOne', function () {
|
||||||
|
// Create a fake model
|
||||||
|
var fakeUser = models.User.forge(testUtils.DataGenerator.Content.users[0]),
|
||||||
|
// Roles & Permissions need to be collections
|
||||||
|
fakeAdminRole = models.Roles.forge(testUtils.DataGenerator.Content.roles[0]),
|
||||||
|
fakeAdminRolePermissions = models.Permissions.forge(testUtils.DataGenerator.Content.permissions);
|
||||||
|
|
||||||
|
// ## Fake the relations
|
||||||
|
// User is related to roles & permissions
|
||||||
|
fakeUser.relations = {
|
||||||
|
roles: fakeAdminRole,
|
||||||
|
permissions: fakeAdminRolePermissions
|
||||||
|
};
|
||||||
|
// We use this inside toJSON.
|
||||||
|
fakeUser.include = ['roles', 'permissions', 'roles.permissions'];
|
||||||
|
|
||||||
|
return Promise.resolve(fakeUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get effective permissions for the user
|
||||||
|
effective.user(1)
|
||||||
|
.then(function (res) {
|
||||||
|
findUserSpy.callCount.should.eql(1);
|
||||||
|
|
||||||
|
res.should.be.an.Object().with.properties('permissions', 'roles');
|
||||||
|
|
||||||
|
res.permissions.should.be.an.Array().with.lengthOf(10);
|
||||||
|
res.roles.should.be.an.Array().with.lengthOf(1);
|
||||||
|
|
||||||
|
// @TODO fix this!
|
||||||
|
// Permissions is an array of models
|
||||||
|
// Roles is a JSON array
|
||||||
|
res.permissions[0].should.be.an.Object().with.properties('attributes', 'id');
|
||||||
|
res.roles[0].should.be.an.Object().with.properties('id', 'name', 'description');
|
||||||
|
res.permissions[0].should.be.instanceOf(models.Base.Model);
|
||||||
|
res.roles[0].should.not.be.instanceOf(models.Base.Model);
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can load user with role, and role.permissions', function (done) {
|
||||||
|
// This test requires quite a lot of unique setup work
|
||||||
|
var findUserSpy = sandbox.stub(models.User, 'findOne', function () {
|
||||||
|
// Create a fake model
|
||||||
|
var fakeUser = models.User.forge(testUtils.DataGenerator.Content.users[0]),
|
||||||
|
// Roles & Permissions need to be collections
|
||||||
|
fakeAdminRole = models.Roles.forge(testUtils.DataGenerator.Content.roles[0]),
|
||||||
|
fakeAdminRolePermissions = models.Permissions.forge(testUtils.DataGenerator.Content.permissions);
|
||||||
|
|
||||||
|
// ## Fake the relations
|
||||||
|
// Roles are related to permissions
|
||||||
|
fakeAdminRole.models[0].relations = {
|
||||||
|
permissions: fakeAdminRolePermissions
|
||||||
|
};
|
||||||
|
// User is related to roles
|
||||||
|
fakeUser.relations = {
|
||||||
|
roles: fakeAdminRole
|
||||||
|
};
|
||||||
|
// We use this inside toJSON.
|
||||||
|
fakeUser.include = ['roles', 'permissions', 'roles.permissions'];
|
||||||
|
|
||||||
|
return Promise.resolve(fakeUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get effective permissions for the user
|
||||||
|
effective.user(1)
|
||||||
|
.then(function (res) {
|
||||||
|
findUserSpy.callCount.should.eql(1);
|
||||||
|
|
||||||
|
res.should.be.an.Object().with.properties('permissions', 'roles');
|
||||||
|
|
||||||
|
res.permissions.should.be.an.Array().with.lengthOf(10);
|
||||||
|
res.roles.should.be.an.Array().with.lengthOf(1);
|
||||||
|
|
||||||
|
// @TODO fix this!
|
||||||
|
// Permissions is an array of models
|
||||||
|
// Roles is a JSON array
|
||||||
|
res.permissions[0].should.be.an.Object().with.properties('attributes', 'id');
|
||||||
|
res.roles[0].should.be.an.Object().with.properties('id', 'name', 'description');
|
||||||
|
res.permissions[0].should.be.instanceOf(models.Base.Model);
|
||||||
|
res.roles[0].should.not.be.instanceOf(models.Base.Model);
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can load user with role, permissions and role.permissions and deduplicate them', function (done) {
|
||||||
|
// This test requires quite a lot of unique setup work
|
||||||
|
var findUserSpy = sandbox.stub(models.User, 'findOne', function () {
|
||||||
|
// Create a fake model
|
||||||
|
var fakeUser = models.User.forge(testUtils.DataGenerator.Content.users[0]),
|
||||||
|
// Roles & Permissions need to be collections
|
||||||
|
fakeAdminRole = models.Roles.forge(testUtils.DataGenerator.Content.roles[0]),
|
||||||
|
fakeAdminRolePermissions = models.Permissions.forge(testUtils.DataGenerator.Content.permissions);
|
||||||
|
|
||||||
|
// ## Fake the relations
|
||||||
|
// Roles are related to permissions
|
||||||
|
fakeAdminRole.models[0].relations = {
|
||||||
|
permissions: fakeAdminRolePermissions
|
||||||
|
};
|
||||||
|
// User is related to roles and permissions
|
||||||
|
fakeUser.relations = {
|
||||||
|
roles: fakeAdminRole,
|
||||||
|
permissions: fakeAdminRolePermissions
|
||||||
|
};
|
||||||
|
// We use this inside toJSON.
|
||||||
|
fakeUser.include = ['roles', 'permissions', 'roles.permissions'];
|
||||||
|
|
||||||
|
return Promise.resolve(fakeUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get effective permissions for the user
|
||||||
|
effective.user(1)
|
||||||
|
.then(function (res) {
|
||||||
|
findUserSpy.callCount.should.eql(1);
|
||||||
|
|
||||||
|
res.should.be.an.Object().with.properties('permissions', 'roles');
|
||||||
|
|
||||||
|
res.permissions.should.be.an.Array().with.lengthOf(10);
|
||||||
|
res.roles.should.be.an.Array().with.lengthOf(1);
|
||||||
|
|
||||||
|
// @TODO fix this!
|
||||||
|
// Permissions is an array of models
|
||||||
|
// Roles is a JSON array
|
||||||
|
res.permissions[0].should.be.an.Object().with.properties('attributes', 'id');
|
||||||
|
res.roles[0].should.be.an.Object().with.properties('id', 'name', 'description');
|
||||||
|
res.permissions[0].should.be.instanceOf(models.Base.Model);
|
||||||
|
res.roles[0].should.not.be.instanceOf(models.Base.Model);
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('App', function () {
|
||||||
|
// @TODO make this consistent or sane or something!
|
||||||
|
// Why is this an empty array, when the success is an object?
|
||||||
|
// Also why is this an empty array when for users we error?!
|
||||||
|
it('returns empty array if app cannot be found!', function (done) {
|
||||||
|
var findAppSpy = sandbox.stub(models.App, 'findOne', function () {
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
effective.app('test')
|
||||||
|
.then(function (res) {
|
||||||
|
findAppSpy.callCount.should.eql(1);
|
||||||
|
res.should.be.an.Array().with.lengthOf(0);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can load user with role, and permissions', function (done) {
|
||||||
|
// This test requires quite a lot of unique setup work
|
||||||
|
var findAppSpy = sandbox.stub(models.App, 'findOne', function () {
|
||||||
|
var fakeApp = models.App.forge(testUtils.DataGenerator.Content.apps[0]),
|
||||||
|
fakePermissions = models.Permissions.forge(testUtils.DataGenerator.Content.permissions);
|
||||||
|
|
||||||
|
// ## Fake the relations
|
||||||
|
fakeApp.relations = {
|
||||||
|
permissions: fakePermissions
|
||||||
|
};
|
||||||
|
fakeApp.include = ['permissions'];
|
||||||
|
|
||||||
|
return Promise.resolve(fakeApp);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get effective permissions for the app
|
||||||
|
effective.app('kudos')
|
||||||
|
.then(function (res) {
|
||||||
|
findAppSpy.callCount.should.eql(1);
|
||||||
|
|
||||||
|
res.should.be.an.Object().with.properties('permissions');
|
||||||
|
|
||||||
|
res.permissions.should.be.an.Array().with.lengthOf(10);
|
||||||
|
should.not.exist(res.roles);
|
||||||
|
|
||||||
|
// @TODO fix this!
|
||||||
|
// Permissions is an array of models
|
||||||
|
// Roles is a JSON array
|
||||||
|
res.permissions[0].should.be.an.Object().with.properties('attributes', 'id');
|
||||||
|
res.permissions[0].should.be.instanceOf(models.Base.Model);
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
628
core/test/unit/permissions/index_spec.js
Normal file
628
core/test/unit/permissions/index_spec.js
Normal file
|
@ -0,0 +1,628 @@
|
||||||
|
var should = require('should'),
|
||||||
|
sinon = require('sinon'),
|
||||||
|
testUtils = require('../../utils'),
|
||||||
|
Promise = require('bluebird'),
|
||||||
|
_ = require('lodash'),
|
||||||
|
models = require('../../../server/models'),
|
||||||
|
permissions = require('../../../server/permissions'),
|
||||||
|
effective = require('../../../server/permissions/effective'),
|
||||||
|
|
||||||
|
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 = 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('No init (no action map)', function () {
|
||||||
|
it('throws an error without init', function () {
|
||||||
|
permissions.canThis.should.throw(/No actions map found/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Init (build actions map)', function () {
|
||||||
|
it('can load an actions map from existing permissions', function (done) {
|
||||||
|
fakePermissions = loadFakePermissions();
|
||||||
|
|
||||||
|
permissions.init().then(function (actionsMap) {
|
||||||
|
should.exist(actionsMap);
|
||||||
|
|
||||||
|
_.keys(actionsMap).should.eql(['browse', 'edit', 'add', 'destroy']);
|
||||||
|
|
||||||
|
actionsMap.browse.should.eql(['post']);
|
||||||
|
actionsMap.edit.should.eql(['post', 'tag', 'user', 'page']);
|
||||||
|
actionsMap.add.should.eql(['post', 'user', 'page']);
|
||||||
|
actionsMap.destroy.should.eql(['post', 'user']);
|
||||||
|
|
||||||
|
actionsMap.should.equal(permissions.actionsMap);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can load an actions map from existing permissions, and deduplicate', function (done) {
|
||||||
|
fakePermissions = loadFakePermissions({extra: true});
|
||||||
|
|
||||||
|
permissions.init().then(function (actionsMap) {
|
||||||
|
should.exist(actionsMap);
|
||||||
|
|
||||||
|
_.keys(actionsMap).should.eql(['browse', 'edit', 'add', 'destroy']);
|
||||||
|
|
||||||
|
actionsMap.browse.should.eql(['post']);
|
||||||
|
actionsMap.edit.should.eql(['post', 'tag', 'user', 'page']);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
effectiveUserStub.callCount.should.eql(1);
|
||||||
|
should.not.exist(res);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
96
core/test/unit/permissions/parse-context_spec.js
Normal file
96
core/test/unit/permissions/parse-context_spec.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
var should = require('should'), // jshint ignore:line
|
||||||
|
parseContext = require('../../../server/permissions/parse-context');
|
||||||
|
|
||||||
|
describe('Permissions', function () {
|
||||||
|
describe('parseContext', function () {
|
||||||
|
it('should return public for no context', function () {
|
||||||
|
parseContext().should.eql({
|
||||||
|
internal: false,
|
||||||
|
external: false,
|
||||||
|
user: null,
|
||||||
|
app: null,
|
||||||
|
public: true
|
||||||
|
});
|
||||||
|
parseContext({}).should.eql({
|
||||||
|
internal: false,
|
||||||
|
external: false,
|
||||||
|
user: null,
|
||||||
|
app: null,
|
||||||
|
public: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return public for random context', function () {
|
||||||
|
parseContext('public').should.eql({
|
||||||
|
internal: false,
|
||||||
|
external: false,
|
||||||
|
user: null,
|
||||||
|
app: null,
|
||||||
|
public: true
|
||||||
|
});
|
||||||
|
parseContext({client: 'thing'}).should.eql({
|
||||||
|
internal: false,
|
||||||
|
external: false,
|
||||||
|
user: null,
|
||||||
|
app: null,
|
||||||
|
public: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return user if user populated', function () {
|
||||||
|
parseContext({user: 1}).should.eql({
|
||||||
|
internal: false,
|
||||||
|
external: false,
|
||||||
|
user: 1,
|
||||||
|
app: null,
|
||||||
|
public: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return app if app populated', function () {
|
||||||
|
parseContext({app: 5}).should.eql({
|
||||||
|
internal: false,
|
||||||
|
external: false,
|
||||||
|
user: null,
|
||||||
|
app: 5,
|
||||||
|
public: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return internal if internal provided', function () {
|
||||||
|
parseContext({internal: true}).should.eql({
|
||||||
|
internal: true,
|
||||||
|
external: false,
|
||||||
|
user: null,
|
||||||
|
app: null,
|
||||||
|
public: false
|
||||||
|
});
|
||||||
|
|
||||||
|
parseContext('internal').should.eql({
|
||||||
|
internal: true,
|
||||||
|
external: false,
|
||||||
|
user: null,
|
||||||
|
app: null,
|
||||||
|
public: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return external if external provided', function () {
|
||||||
|
parseContext({external: true}).should.eql({
|
||||||
|
internal: false,
|
||||||
|
external: true,
|
||||||
|
user: null,
|
||||||
|
app: null,
|
||||||
|
public: false
|
||||||
|
});
|
||||||
|
|
||||||
|
parseContext('external').should.eql({
|
||||||
|
internal: false,
|
||||||
|
external: true,
|
||||||
|
user: null,
|
||||||
|
app: null,
|
||||||
|
public: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
158
core/test/unit/permissions/public_spec.js
Normal file
158
core/test/unit/permissions/public_spec.js
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
var should = require('should'), // jshint ignore:line
|
||||||
|
_ = require('lodash'),
|
||||||
|
errors = require('../../../server/errors'),
|
||||||
|
applyPublicRules = require('../../../server/permissions/public');
|
||||||
|
|
||||||
|
describe('Permissions', function () {
|
||||||
|
describe('applyPublicRules', function () {
|
||||||
|
it('should return empty object for docName with no rules', function (done) {
|
||||||
|
applyPublicRules('test', 'test', {}).then(function (result) {
|
||||||
|
result.should.eql({});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return unchanged object for non-public context', function (done) {
|
||||||
|
var internal = {context: 'internal'},
|
||||||
|
user = {context: {user: 1}},
|
||||||
|
app = {context: {app: 1}};
|
||||||
|
|
||||||
|
applyPublicRules('posts', 'browse', _.cloneDeep(internal)).then(function (result) {
|
||||||
|
result.should.eql(internal);
|
||||||
|
|
||||||
|
return applyPublicRules('posts', 'browse', _.cloneDeep(user));
|
||||||
|
}).then(function (result) {
|
||||||
|
result.should.eql(user);
|
||||||
|
|
||||||
|
return applyPublicRules('posts', 'browse', _.cloneDeep(app));
|
||||||
|
}).then(function (result) {
|
||||||
|
result.should.eql(app);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return unchanged object for post with public context', function (done) {
|
||||||
|
var publicContext = {context: {}};
|
||||||
|
|
||||||
|
applyPublicRules('posts', 'browse', _.cloneDeep(publicContext)).then(function (result) {
|
||||||
|
result.should.not.eql(publicContext);
|
||||||
|
result.should.eql({
|
||||||
|
context: {},
|
||||||
|
status: 'published'
|
||||||
|
});
|
||||||
|
|
||||||
|
return applyPublicRules('posts', 'browse', _.extend({}, _.cloneDeep(publicContext), {status: 'published'}));
|
||||||
|
}).then(function (result) {
|
||||||
|
result.should.eql({
|
||||||
|
context: {},
|
||||||
|
status: 'published'
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error for draft post without uuid (read)', function (done) {
|
||||||
|
var draft = {context: {}, data: {status: 'draft'}};
|
||||||
|
|
||||||
|
applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function () {
|
||||||
|
done('Did not throw an error for draft');
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.NoPermissionError).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error for draft post (browse)', function (done) {
|
||||||
|
var draft = {context: {}, status: 'draft'};
|
||||||
|
|
||||||
|
applyPublicRules('posts', 'browse', _.cloneDeep(draft)).then(function () {
|
||||||
|
done('Did not throw an error for draft');
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.NoPermissionError).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should permit post draft status with uuid (read)', function (done) {
|
||||||
|
var draft = {context: {}, data: {status: 'draft', uuid: '1234-abcd'}};
|
||||||
|
|
||||||
|
applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function (result) {
|
||||||
|
result.should.eql(draft);
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should permit post all status with uuid (read)', function (done) {
|
||||||
|
var draft = {context: {}, data: {status: 'all', uuid: '1234-abcd'}};
|
||||||
|
|
||||||
|
applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function (result) {
|
||||||
|
result.should.eql(draft);
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT permit post draft status with uuid (browse)', function (done) {
|
||||||
|
var draft = {context: {}, status: 'draft', uuid: '1234-abcd'};
|
||||||
|
|
||||||
|
applyPublicRules('posts', 'browse', _.cloneDeep(draft)).then(function () {
|
||||||
|
done('Did not throw an error for draft');
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.NoPermissionError).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT permit post all status with uuid (browse)', function (done) {
|
||||||
|
var draft = {context: {}, status: 'all', uuid: '1234-abcd'};
|
||||||
|
|
||||||
|
applyPublicRules('posts', 'browse', _.cloneDeep(draft)).then(function () {
|
||||||
|
done('Did not throw an error for draft');
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.NoPermissionError).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error for draft post with uuid and id or slug (read)', function (done) {
|
||||||
|
var draft = {context: {}, data: {status: 'draft', uuid: '1234-abcd', id: 1}};
|
||||||
|
|
||||||
|
applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function () {
|
||||||
|
done('Did not throw an error for draft');
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.NoPermissionError).should.eql(true);
|
||||||
|
|
||||||
|
draft = {context: {}, data: {status: 'draft', uuid: '1234-abcd', slug: 'abcd'}};
|
||||||
|
|
||||||
|
return applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function () {
|
||||||
|
done('Did not throw an error for draft');
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.NoPermissionError).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return unchanged object for user with public context', function (done) {
|
||||||
|
var publicContext = {context: {}};
|
||||||
|
|
||||||
|
applyPublicRules('users', 'browse', _.cloneDeep(publicContext)).then(function (result) {
|
||||||
|
result.should.not.eql(publicContext);
|
||||||
|
result.should.eql({
|
||||||
|
context: {},
|
||||||
|
status: 'all'
|
||||||
|
});
|
||||||
|
|
||||||
|
return applyPublicRules('users', 'browse', _.extend({}, _.cloneDeep(publicContext), {status: 'active'}));
|
||||||
|
}).then(function (result) {
|
||||||
|
result.should.eql({
|
||||||
|
context: {},
|
||||||
|
status: 'active'
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,504 +0,0 @@
|
||||||
var should = require('should'),
|
|
||||||
sinon = require('sinon'),
|
|
||||||
testUtils = require('../utils'),
|
|
||||||
Promise = require('bluebird'),
|
|
||||||
_ = require('lodash'),
|
|
||||||
Models = require('../../server/models'),
|
|
||||||
errors = require('../../server/errors'),
|
|
||||||
permissions = require('../../server/permissions'),
|
|
||||||
|
|
||||||
sandbox = sinon.sandbox.create();
|
|
||||||
|
|
||||||
describe('Permissions', function () {
|
|
||||||
afterEach(function () {
|
|
||||||
sandbox.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('actions map', function () {
|
|
||||||
before(function () {
|
|
||||||
Models.init();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
var permissions = _.map(testUtils.DataGenerator.Content.permissions, function (testPerm) {
|
|
||||||
return testUtils.DataGenerator.forKnex.createPermission(testPerm);
|
|
||||||
});
|
|
||||||
|
|
||||||
sandbox.stub(Models.Permission, 'findAll', function () {
|
|
||||||
return Promise.resolve(Models.Permissions.forge(permissions));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can load an actions map from existing permissions', function (done) {
|
|
||||||
permissions.init().then(function (actionsMap) {
|
|
||||||
should.exist(actionsMap);
|
|
||||||
|
|
||||||
actionsMap.edit.sort().should.eql(['post', 'tag', 'user', 'page'].sort());
|
|
||||||
|
|
||||||
actionsMap.should.equal(permissions.actionsMap);
|
|
||||||
|
|
||||||
done();
|
|
||||||
}).catch(done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('parseContext', function () {
|
|
||||||
it('should return public for no context', function () {
|
|
||||||
permissions.parseContext().should.eql({
|
|
||||||
internal: false,
|
|
||||||
external: false,
|
|
||||||
user: null,
|
|
||||||
app: null,
|
|
||||||
public: true
|
|
||||||
});
|
|
||||||
permissions.parseContext({}).should.eql({
|
|
||||||
internal: false,
|
|
||||||
external: false,
|
|
||||||
user: null,
|
|
||||||
app: null,
|
|
||||||
public: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return public for random context', function () {
|
|
||||||
permissions.parseContext('public').should.eql({
|
|
||||||
internal: false,
|
|
||||||
external: false,
|
|
||||||
user: null,
|
|
||||||
app: null,
|
|
||||||
public: true
|
|
||||||
});
|
|
||||||
permissions.parseContext({client: 'thing'}).should.eql({
|
|
||||||
internal: false,
|
|
||||||
external: false,
|
|
||||||
user: null,
|
|
||||||
app: null,
|
|
||||||
public: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return user if user populated', function () {
|
|
||||||
permissions.parseContext({user: 1}).should.eql({
|
|
||||||
internal: false,
|
|
||||||
external: false,
|
|
||||||
user: 1,
|
|
||||||
app: null,
|
|
||||||
public: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return app if app populated', function () {
|
|
||||||
permissions.parseContext({app: 5}).should.eql({
|
|
||||||
internal: false,
|
|
||||||
external: false,
|
|
||||||
user: null,
|
|
||||||
app: 5,
|
|
||||||
public: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return internal if internal provided', function () {
|
|
||||||
permissions.parseContext({internal: true}).should.eql({
|
|
||||||
internal: true,
|
|
||||||
external: false,
|
|
||||||
user: null,
|
|
||||||
app: null,
|
|
||||||
public: false
|
|
||||||
});
|
|
||||||
|
|
||||||
permissions.parseContext('internal').should.eql({
|
|
||||||
internal: true,
|
|
||||||
external: false,
|
|
||||||
user: null,
|
|
||||||
app: null,
|
|
||||||
public: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('applyPublicRules', function () {
|
|
||||||
it('should return empty object for docName with no rules', function (done) {
|
|
||||||
permissions.applyPublicRules('test', 'test', {}).then(function (result) {
|
|
||||||
result.should.eql({});
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return unchanged object for non-public context', function (done) {
|
|
||||||
var internal = {context: 'internal'},
|
|
||||||
user = {context: {user: 1}},
|
|
||||||
app = {context: {app: 1}};
|
|
||||||
|
|
||||||
permissions.applyPublicRules('posts', 'browse', _.cloneDeep(internal)).then(function (result) {
|
|
||||||
result.should.eql(internal);
|
|
||||||
|
|
||||||
return permissions.applyPublicRules('posts', 'browse', _.cloneDeep(user));
|
|
||||||
}).then(function (result) {
|
|
||||||
result.should.eql(user);
|
|
||||||
|
|
||||||
return permissions.applyPublicRules('posts', 'browse', _.cloneDeep(app));
|
|
||||||
}).then(function (result) {
|
|
||||||
result.should.eql(app);
|
|
||||||
|
|
||||||
done();
|
|
||||||
}).catch(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return unchanged object for post with public context', function (done) {
|
|
||||||
var publicContext = {context: {}};
|
|
||||||
|
|
||||||
permissions.applyPublicRules('posts', 'browse', _.cloneDeep(publicContext)).then(function (result) {
|
|
||||||
result.should.not.eql(publicContext);
|
|
||||||
result.should.eql({
|
|
||||||
context: {},
|
|
||||||
status: 'published'
|
|
||||||
});
|
|
||||||
|
|
||||||
return permissions.applyPublicRules('posts', 'browse', _.extend({}, _.cloneDeep(publicContext), {status: 'published'}));
|
|
||||||
}).then(function (result) {
|
|
||||||
result.should.eql({
|
|
||||||
context: {},
|
|
||||||
status: 'published'
|
|
||||||
});
|
|
||||||
|
|
||||||
done();
|
|
||||||
}).catch(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error for draft post without uuid (read)', function (done) {
|
|
||||||
var draft = {context: {}, data: {status: 'draft'}};
|
|
||||||
|
|
||||||
permissions.applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function () {
|
|
||||||
done('Did not throw an error for draft');
|
|
||||||
}).catch(function (err) {
|
|
||||||
(err instanceof errors.NoPermissionError).should.eql(true);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error for draft post (browse)', function (done) {
|
|
||||||
var draft = {context: {}, status: 'draft'};
|
|
||||||
|
|
||||||
permissions.applyPublicRules('posts', 'browse', _.cloneDeep(draft)).then(function () {
|
|
||||||
done('Did not throw an error for draft');
|
|
||||||
}).catch(function (err) {
|
|
||||||
(err instanceof errors.NoPermissionError).should.eql(true);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should permit post draft status with uuid (read)', function (done) {
|
|
||||||
var draft = {context: {}, data: {status: 'draft', uuid: '1234-abcd'}};
|
|
||||||
|
|
||||||
permissions.applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function (result) {
|
|
||||||
result.should.eql(draft);
|
|
||||||
done();
|
|
||||||
}).catch(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should permit post all status with uuid (read)', function (done) {
|
|
||||||
var draft = {context: {}, data: {status: 'all', uuid: '1234-abcd'}};
|
|
||||||
|
|
||||||
permissions.applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function (result) {
|
|
||||||
result.should.eql(draft);
|
|
||||||
done();
|
|
||||||
}).catch(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should NOT permit post draft status with uuid (browse)', function (done) {
|
|
||||||
var draft = {context: {}, status: 'draft', uuid: '1234-abcd'};
|
|
||||||
|
|
||||||
permissions.applyPublicRules('posts', 'browse', _.cloneDeep(draft)).then(function () {
|
|
||||||
done('Did not throw an error for draft');
|
|
||||||
}).catch(function (err) {
|
|
||||||
(err instanceof errors.NoPermissionError).should.eql(true);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should NOT permit post all status with uuid (browse)', function (done) {
|
|
||||||
var draft = {context: {}, status: 'all', uuid: '1234-abcd'};
|
|
||||||
|
|
||||||
permissions.applyPublicRules('posts', 'browse', _.cloneDeep(draft)).then(function () {
|
|
||||||
done('Did not throw an error for draft');
|
|
||||||
}).catch(function (err) {
|
|
||||||
(err instanceof errors.NoPermissionError).should.eql(true);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error for draft post with uuid and id or slug (read)', function (done) {
|
|
||||||
var draft = {context: {}, data: {status: 'draft', uuid: '1234-abcd', id: 1}};
|
|
||||||
|
|
||||||
permissions.applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function () {
|
|
||||||
done('Did not throw an error for draft');
|
|
||||||
}).catch(function (err) {
|
|
||||||
(err instanceof errors.NoPermissionError).should.eql(true);
|
|
||||||
|
|
||||||
draft = {context: {}, data: {status: 'draft', uuid: '1234-abcd', slug: 'abcd'}};
|
|
||||||
|
|
||||||
return permissions.applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function () {
|
|
||||||
done('Did not throw an error for draft');
|
|
||||||
}).catch(function (err) {
|
|
||||||
(err instanceof errors.NoPermissionError).should.eql(true);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return unchanged object for user with public context', function (done) {
|
|
||||||
var publicContext = {context: {}};
|
|
||||||
|
|
||||||
permissions.applyPublicRules('users', 'browse', _.cloneDeep(publicContext)).then(function (result) {
|
|
||||||
result.should.not.eql(publicContext);
|
|
||||||
result.should.eql({
|
|
||||||
context: {},
|
|
||||||
status: 'all'
|
|
||||||
});
|
|
||||||
|
|
||||||
return permissions.applyPublicRules('users', 'browse', _.extend({}, _.cloneDeep(publicContext), {status: 'active'}));
|
|
||||||
}).then(function (result) {
|
|
||||||
result.should.eql({
|
|
||||||
context: {},
|
|
||||||
status: 'active'
|
|
||||||
});
|
|
||||||
|
|
||||||
done();
|
|
||||||
}).catch(done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// @TODO: move to integrations or stub
|
|
||||||
// it('does not allow edit post without permission', function (done) {
|
|
||||||
// var fakePage = {
|
|
||||||
// id: 1
|
|
||||||
// };
|
|
||||||
|
|
||||||
// permissions.init()
|
|
||||||
// .then(function () {
|
|
||||||
// var canThisResult = permissions.canThis({id: 1});
|
|
||||||
|
|
||||||
// should.exist(canThisResult.edit);
|
|
||||||
// should.exist(canThisResult.edit.post);
|
|
||||||
|
|
||||||
// return canThisResult.edit.page(fakePage);
|
|
||||||
// })
|
|
||||||
// .then(function () {
|
|
||||||
// done(new Error('was able to edit post without permission'));
|
|
||||||
// }).catch(done);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('allows edit post with permission', function (done) {
|
|
||||||
// var fakePost = {
|
|
||||||
// id: '1'
|
|
||||||
// };
|
|
||||||
|
|
||||||
// permissions.init()
|
|
||||||
// .then(function () {
|
|
||||||
// return Models.User.findOne({id: 1});
|
|
||||||
// })
|
|
||||||
// .then(function (foundUser) {
|
|
||||||
// var newPerm = new Models.Permission({
|
|
||||||
// name: 'test3 edit post',
|
|
||||||
// action_type: 'edit',
|
|
||||||
// object_type: 'post'
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return newPerm.save(null, context).then(function () {
|
|
||||||
// return foundUser.permissions().attach(newPerm);
|
|
||||||
// });
|
|
||||||
// })
|
|
||||||
// .then(function () {
|
|
||||||
// return Models.User.findOne({id: 1}, { withRelated: ['permissions']});
|
|
||||||
// })
|
|
||||||
// .then(function (updatedUser) {
|
|
||||||
|
|
||||||
// // TODO: Verify updatedUser.related('permissions') has the permission?
|
|
||||||
// var canThisResult = permissions.canThis(updatedUser.id);
|
|
||||||
|
|
||||||
// should.exist(canThisResult.edit);
|
|
||||||
// should.exist(canThisResult.edit.post);
|
|
||||||
|
|
||||||
// return canThisResult.edit.post(fakePost);
|
|
||||||
// })
|
|
||||||
// .then(function () {
|
|
||||||
// done();
|
|
||||||
// }).catch(done);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('can use permissible function on Model to allow something', function (done) {
|
|
||||||
// var testUser,
|
|
||||||
// permissibleStub = sandbox.stub(Models.Post, 'permissible', function () {
|
|
||||||
// return Promise.resolve();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// testUtils.insertAuthorUser()
|
|
||||||
// .then(function () {
|
|
||||||
// return Models.User.findAll();
|
|
||||||
// })
|
|
||||||
// .then(function (foundUser) {
|
|
||||||
// testUser = foundUser.models[1];
|
|
||||||
|
|
||||||
// return permissions.canThis({user: testUser.id}).edit.post(123);
|
|
||||||
// })
|
|
||||||
// .then(function () {
|
|
||||||
// permissibleStub.restore();
|
|
||||||
// permissibleStub.calledWith(123, { user: testUser.id, app: null, internal: false })
|
|
||||||
// .should.equal(true);
|
|
||||||
|
|
||||||
// done();
|
|
||||||
// })
|
|
||||||
// .catch(function () {
|
|
||||||
// permissibleStub.restore();
|
|
||||||
|
|
||||||
// done(new Error('did not allow testUser'));
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('can use permissible function on Model to forbid something', function (done) {
|
|
||||||
// var testUser,
|
|
||||||
// permissibleStub = sandbox.stub(Models.Post, 'permissible', function () {
|
|
||||||
// return Promise.reject();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// testUtils.insertAuthorUser()
|
|
||||||
// .then(function () {
|
|
||||||
// return Models.User.findAll();
|
|
||||||
// })
|
|
||||||
// .then(function (foundUser) {
|
|
||||||
// testUser = foundUser.models[1];
|
|
||||||
|
|
||||||
// return permissions.canThis({user: testUser.id}).edit.post(123);
|
|
||||||
// })
|
|
||||||
// .then(function () {
|
|
||||||
|
|
||||||
// permissibleStub.restore();
|
|
||||||
// done(new Error('Allowed testUser to edit post'));
|
|
||||||
// })
|
|
||||||
// .catch(function () {
|
|
||||||
// permissibleStub.calledWith(123, { user: testUser.id, app: null, internal: false })
|
|
||||||
// .should.equal(true);
|
|
||||||
// permissibleStub.restore();
|
|
||||||
// done();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('can get effective user permissions', function (done) {
|
|
||||||
// effectivePerms.user(1).then(function (effectivePermissions) {
|
|
||||||
// should.exist(effectivePermissions);
|
|
||||||
|
|
||||||
// effectivePermissions.length.should.be.above(0);
|
|
||||||
|
|
||||||
// done();
|
|
||||||
// }).catch(done);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('can check an apps effective permissions', function (done) {
|
|
||||||
// effectivePerms.app('Kudos')
|
|
||||||
// .then(function (effectivePermissions) {
|
|
||||||
// should.exist(effectivePermissions);
|
|
||||||
|
|
||||||
// effectivePermissions.length.should.be.above(0);
|
|
||||||
|
|
||||||
// done();
|
|
||||||
// })
|
|
||||||
// .catch(done);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('does not allow an app to edit a post without permission', function (done) {
|
|
||||||
// // Change the author of the post so the author override doesn't affect the test
|
|
||||||
// Models.Post.edit({'author_id': 2}, _.extend(context, {id: 1}))
|
|
||||||
// .then(function (updatedPost) {
|
|
||||||
// // Add user permissions
|
|
||||||
// return Models.User.findOne({id: 1})
|
|
||||||
// .then(function (foundUser) {
|
|
||||||
// var newPerm = new Models.Permission({
|
|
||||||
// name: 'app test edit post',
|
|
||||||
// action_type: 'edit',
|
|
||||||
// object_type: 'post'
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return newPerm.save(null, context).then(function () {
|
|
||||||
// return foundUser.permissions().attach(newPerm).then(function () {
|
|
||||||
// return Promise.all([updatedPost, foundUser]);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// })
|
|
||||||
// .then(function (results) {
|
|
||||||
// var updatedPost = results[0],
|
|
||||||
// updatedUser = results[1];
|
|
||||||
|
|
||||||
// return permissions.canThis({ user: updatedUser.id })
|
|
||||||
// .edit
|
|
||||||
// .post(updatedPost.id)
|
|
||||||
// .then(function () {
|
|
||||||
// return results;
|
|
||||||
// })
|
|
||||||
// .catch(function (err) {
|
|
||||||
// /*jshint unused:false */
|
|
||||||
// done(new Error('Did not allow user 1 to edit post 1'));
|
|
||||||
// });
|
|
||||||
// })
|
|
||||||
// .then(function (results) {
|
|
||||||
// var updatedPost = results[0],
|
|
||||||
// updatedUser = results[1];
|
|
||||||
|
|
||||||
// // Confirm app cannot edit it.
|
|
||||||
// return permissions.canThis({ app: 'Hemingway', user: updatedUser.id })
|
|
||||||
// .edit
|
|
||||||
// .post(updatedPost.id)
|
|
||||||
// .then(function () {
|
|
||||||
// done(new Error('Allowed an edit of post 1'));
|
|
||||||
// }).catch(done);
|
|
||||||
// }).catch(done);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('allows an app to edit a post with permission', function (done) {
|
|
||||||
// permissions.canThis({ app: 'Kudos', user: 1 })
|
|
||||||
// .edit
|
|
||||||
// .post(1)
|
|
||||||
// .then(function () {
|
|
||||||
// done();
|
|
||||||
// })
|
|
||||||
// .catch(function () {
|
|
||||||
// done(new Error('Did not allow an edit of post 1'));
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('checks for null context passed and rejects', function (done) {
|
|
||||||
// permissions.canThis(undefined)
|
|
||||||
// .edit
|
|
||||||
// .post(1)
|
|
||||||
// .then(function () {
|
|
||||||
// done(new Error('Should not allow editing post'));
|
|
||||||
// })
|
|
||||||
// .catch(done);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('allows \'internal\' to be passed for internal requests', function (done) {
|
|
||||||
// // Using tag here because post implements the custom permissible interface
|
|
||||||
// permissions.canThis('internal')
|
|
||||||
// .edit
|
|
||||||
// .tag(1)
|
|
||||||
// .then(function () {
|
|
||||||
// done();
|
|
||||||
// })
|
|
||||||
// .catch(function () {
|
|
||||||
// done(new Error('Should allow editing post with "internal"'));
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('allows { internal: true } to be passed for internal requests', function (done) {
|
|
||||||
// // Using tag here because post implements the custom permissible interface
|
|
||||||
// permissions.canThis({internal: true})
|
|
||||||
// .edit
|
|
||||||
// .tag(1)
|
|
||||||
// .then(function () {
|
|
||||||
// done();
|
|
||||||
// })
|
|
||||||
// .catch(function () {
|
|
||||||
// done(new Error('Should allow editing post with { internal: true }'));
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
});
|
|
Loading…
Add table
Reference in a new issue