mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-15 03:12:54 +03:00
Merge pull request #5496 from ErisDS/api-public-perms
Add public API endpoint permission handling
This commit is contained in:
commit
cfce197159
@ -38,20 +38,6 @@ posts = {
|
||||
permittedOptions = utils.browseDefaultOptions.concat(extraOptions),
|
||||
tasks;
|
||||
|
||||
/**
|
||||
* ### Handle Permissions
|
||||
* We need to either be an authorised user, or only return published posts.
|
||||
* @param {Object} options
|
||||
* @returns {Object} options
|
||||
*/
|
||||
function handlePermissions(options) {
|
||||
if (!(options.context && options.context.user)) {
|
||||
options.status = 'published';
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* ### Model Query
|
||||
* Make the call to the Model layer
|
||||
@ -65,7 +51,7 @@ posts = {
|
||||
// Push all of our tasks into a `tasks` array in the correct order
|
||||
tasks = [
|
||||
utils.validate(docName, {opts: permittedOptions}),
|
||||
handlePermissions,
|
||||
utils.handlePublicPermissions(docName, 'browse'),
|
||||
utils.convertOptions(allowedIncludes),
|
||||
modelQuery
|
||||
];
|
||||
@ -86,19 +72,6 @@ posts = {
|
||||
var attrs = ['id', 'slug', 'status', 'uuid'],
|
||||
tasks;
|
||||
|
||||
/**
|
||||
* ### Handle Permissions
|
||||
* We need to either be an authorised user, or only return published posts.
|
||||
* @param {Object} options
|
||||
* @returns {Object} options
|
||||
*/
|
||||
function handlePermissions(options) {
|
||||
if (!options.data.uuid && !(options.context && options.context.user)) {
|
||||
options.data.status = 'published';
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* ### Model Query
|
||||
* Make the call to the Model layer
|
||||
@ -112,7 +85,7 @@ posts = {
|
||||
// Push all of our tasks into a `tasks` array in the correct order
|
||||
tasks = [
|
||||
utils.validate(docName, {attrs: attrs}),
|
||||
handlePermissions,
|
||||
utils.handlePublicPermissions(docName, 'read'),
|
||||
utils.convertOptions(allowedIncludes),
|
||||
modelQuery
|
||||
];
|
||||
|
@ -26,20 +26,6 @@ tags = {
|
||||
browse: function browse(options) {
|
||||
var tasks;
|
||||
|
||||
/**
|
||||
* ### Handle Permissions
|
||||
* We need to be an authorised user to perform this action
|
||||
* @param {Object} options
|
||||
* @returns {Object} options
|
||||
*/
|
||||
function handlePermissions(options) {
|
||||
return canThis(options.context).browse.tag().then(function permissionGranted() {
|
||||
return options;
|
||||
}).catch(function handleError(error) {
|
||||
return errors.handleAPIError(error, 'You do not have permission to browse tags.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ### Model Query
|
||||
* Make the call to the Model layer
|
||||
@ -53,7 +39,7 @@ tags = {
|
||||
// Push all of our tasks into a `tasks` array in the correct order
|
||||
tasks = [
|
||||
utils.validate(docName, {opts: utils.browseDefaultOptions}),
|
||||
handlePermissions,
|
||||
utils.handlePublicPermissions(docName, 'browse'),
|
||||
utils.convertOptions(allowedIncludes),
|
||||
doQuery
|
||||
];
|
||||
@ -71,20 +57,6 @@ tags = {
|
||||
var attrs = ['id', 'slug'],
|
||||
tasks;
|
||||
|
||||
/**
|
||||
* ### Handle Permissions
|
||||
* We need to be an authorised user to perform this action
|
||||
* @param {Object} options
|
||||
* @returns {Object} options
|
||||
*/
|
||||
function handlePermissions(options) {
|
||||
return canThis(options.context).read.tag().then(function permissionGranted() {
|
||||
return options;
|
||||
}).catch(function handleError(error) {
|
||||
return errors.handleAPIError(error, 'You do not have permission to read tags.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ### Model Query
|
||||
* Make the call to the Model layer
|
||||
@ -98,7 +70,7 @@ tags = {
|
||||
// Push all of our tasks into a `tasks` array in the correct order
|
||||
tasks = [
|
||||
utils.validate(docName, {attrs: attrs}),
|
||||
handlePermissions,
|
||||
utils.handlePublicPermissions(docName, 'read'),
|
||||
utils.convertOptions(allowedIncludes),
|
||||
doQuery
|
||||
];
|
||||
|
@ -77,20 +77,6 @@ users = {
|
||||
permittedOptions = utils.browseDefaultOptions.concat(extraOptions),
|
||||
tasks;
|
||||
|
||||
/**
|
||||
* ### Handle Permissions
|
||||
* We need to either be an authorised user, or only return published posts.
|
||||
* @param {Object} options
|
||||
* @returns {Object} options
|
||||
*/
|
||||
function handlePermissions(options) {
|
||||
return canThis(options.context).browse.user().then(function permissionGranted() {
|
||||
return options;
|
||||
}).catch(function handleError(error) {
|
||||
return errors.handleAPIError(error, 'You do not have permission to browse users.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ### Model Query
|
||||
* Make the call to the Model layer
|
||||
@ -104,7 +90,7 @@ users = {
|
||||
// Push all of our tasks into a `tasks` array in the correct order
|
||||
tasks = [
|
||||
utils.validate(docName, {opts: permittedOptions}),
|
||||
handlePermissions,
|
||||
utils.handlePublicPermissions(docName, 'browse'),
|
||||
utils.convertOptions(allowedIncludes),
|
||||
doQuery
|
||||
];
|
||||
@ -122,18 +108,9 @@ users = {
|
||||
var attrs = ['id', 'slug', 'status', 'email', 'role'],
|
||||
tasks;
|
||||
|
||||
/**
|
||||
* ### Handle Permissions
|
||||
* Convert 'me' safely
|
||||
* @param {Object} options
|
||||
* @returns {Object} options
|
||||
*/
|
||||
function handlePermissions(options) {
|
||||
if (options.data.id === 'me' && options.context && options.context.user) {
|
||||
options.data.id = options.context.user;
|
||||
}
|
||||
|
||||
return options;
|
||||
// Special handling for id = 'me'
|
||||
if (options.id === 'me' && options.context && options.context.user) {
|
||||
options.id = options.context.user;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -149,7 +126,7 @@ users = {
|
||||
// Push all of our tasks into a `tasks` array in the correct order
|
||||
tasks = [
|
||||
utils.validate(docName, {attrs: attrs}),
|
||||
handlePermissions,
|
||||
utils.handlePublicPermissions(docName, 'read'),
|
||||
utils.convertOptions(allowedIncludes),
|
||||
doQuery
|
||||
];
|
||||
|
@ -1,10 +1,12 @@
|
||||
// # API Utils
|
||||
// Shared helpers for working with the API
|
||||
var Promise = require('bluebird'),
|
||||
_ = require('lodash'),
|
||||
path = require('path'),
|
||||
errors = require('../errors'),
|
||||
validation = require('../data/validation'),
|
||||
var Promise = require('bluebird'),
|
||||
_ = require('lodash'),
|
||||
path = require('path'),
|
||||
errors = require('../errors'),
|
||||
permissions = require('../permissions'),
|
||||
validation = require('../data/validation'),
|
||||
|
||||
utils;
|
||||
|
||||
utils = {
|
||||
@ -131,13 +133,67 @@ utils = {
|
||||
return errors;
|
||||
},
|
||||
|
||||
/**
|
||||
* ## Is Public Context?
|
||||
* If this is a public context, return true
|
||||
* @param {Object} options
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isPublicContext: function isPublicContext(options) {
|
||||
return permissions.parseContext(options.context).public;
|
||||
},
|
||||
/**
|
||||
* ## Apply Public Permissions
|
||||
* Update the options object so that the rules reflect what is permitted to be retrieved from a public request
|
||||
* @param {String} docName
|
||||
* @param {String} method (read || browse)
|
||||
* @param {Object} options
|
||||
* @returns {Object} options
|
||||
*/
|
||||
applyPublicPermissions: function applyPublicPermissions(docName, method, options) {
|
||||
return permissions.applyPublicRules(docName, method, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* ## Handle Public Permissions
|
||||
* @param {String} docName
|
||||
* @param {String} method (read || browse)
|
||||
* @returns {Function}
|
||||
*/
|
||||
handlePublicPermissions: function handlePublicPermissions(docName, method) {
|
||||
var singular = docName.replace(/s$/, '');
|
||||
|
||||
/**
|
||||
* Check if this is a public request, if so use the public permissions, otherwise use standard canThis
|
||||
* @param {Object} options
|
||||
* @returns {Object} options
|
||||
*/
|
||||
return function doHandlePublicPermissions(options) {
|
||||
var permsPromise;
|
||||
|
||||
if (utils.isPublicContext(options)) {
|
||||
permsPromise = utils.applyPublicPermissions(docName, method, options);
|
||||
} else {
|
||||
permsPromise = permissions.canThis(options.context)[method][singular](options.data);
|
||||
}
|
||||
|
||||
return permsPromise.then(function permissionGranted() {
|
||||
return options;
|
||||
}).catch(function handleError(error) {
|
||||
return errors.handleAPIError(error);
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
prepareInclude: function prepareInclude(include, allowedIncludes) {
|
||||
include = include || '';
|
||||
include = _.intersection(include.split(','), allowedIncludes);
|
||||
|
||||
return include;
|
||||
},
|
||||
|
||||
/**
|
||||
* ## Convert Options
|
||||
* @param {Array} allowedIncludes
|
||||
* @returns {Function} doConversion
|
||||
*/
|
||||
|
@ -20,30 +20,90 @@ function hasActionsMap() {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Move this to its own file so others can use it?
|
||||
function parseContext(context) {
|
||||
// Parse what's passed to canThis.beginCheck for standard user and app scopes
|
||||
var parsed = {
|
||||
internal: false,
|
||||
user: null,
|
||||
app: null
|
||||
app: null,
|
||||
public: true
|
||||
};
|
||||
|
||||
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 errorMsg = 'You do not have permission to retrieve ' + docName + ' with that status';
|
||||
|
||||
// Enforce status 'active' for users
|
||||
if (docName === 'users') {
|
||||
if (!opts.status) {
|
||||
return 'active';
|
||||
} else if (opts.status !== 'active') {
|
||||
throw errorMsg;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 errorMsg;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
CanThisResult = function () {
|
||||
return;
|
||||
@ -244,5 +304,7 @@ module.exports = exported = {
|
||||
init: init,
|
||||
refresh: refresh,
|
||||
canThis: canThis,
|
||||
parseContext: parseContext,
|
||||
applyPublicRules: applyPublicRules,
|
||||
actionsMap: {}
|
||||
};
|
||||
|
@ -126,15 +126,35 @@ describe('Post API', function () {
|
||||
|
||||
it('without context.user cannot fetch all posts', function (done) {
|
||||
PostAPI.browse({status: 'all'}).then(function (results) {
|
||||
should.exist(results);
|
||||
testUtils.API.checkResponse(results, 'posts');
|
||||
should.exist(results.posts);
|
||||
results.posts.length.should.eql(4);
|
||||
results.posts[0].status.should.eql('published');
|
||||
testUtils.API.checkResponse(results.posts[0], 'post');
|
||||
should.not.exist(results);
|
||||
|
||||
done(new Error('should not provide results if invalid status provided'));
|
||||
}).catch(function (err) {
|
||||
err.errorType.should.eql('NoPermissionError');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('without context.user cannot fetch draft posts', function (done) {
|
||||
PostAPI.browse({status: 'draft'}).then(function (results) {
|
||||
should.not.exist(results);
|
||||
|
||||
done(new Error('should not provide results if invalid status provided'));
|
||||
}).catch(function (err) {
|
||||
err.errorType.should.eql('NoPermissionError');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('without context.user cannot use uuid to fetch draft posts in browse', function (done) {
|
||||
PostAPI.browse({status: 'draft', uuid: 'imastring'}).then(function (results) {
|
||||
should.not.exist(results);
|
||||
|
||||
done(new Error('should not provide results if invalid status provided'));
|
||||
}).catch(function (err) {
|
||||
err.errorType.should.eql('NoPermissionError');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('with context.user can fetch drafts', function (done) {
|
||||
@ -230,15 +250,13 @@ describe('Post API', function () {
|
||||
});
|
||||
|
||||
it('without context.user cannot fetch draft', function (done) {
|
||||
PostAPI.read({slug: 'unfinished', status: 'draft'}).then(function (results) {
|
||||
should.not.exist(results.posts);
|
||||
done();
|
||||
PostAPI.read({slug: 'unfinished', status: 'draft'}).then(function () {
|
||||
done(new Error('Should not return a result with no permission'));
|
||||
}).catch(function (err) {
|
||||
should.exist(err);
|
||||
err.message.should.eql('Post not found.');
|
||||
|
||||
err.errorType.should.eql('NoPermissionError');
|
||||
done();
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('with context.user can fetch a draft', function (done) {
|
||||
@ -260,13 +278,13 @@ describe('Post API', function () {
|
||||
|
||||
it('cannot fetch post with unknown id', function (done) {
|
||||
PostAPI.read({context: {user: 1}, slug: 'not-a-post'}).then(function () {
|
||||
done();
|
||||
done(new Error('Should not return a result with unknown id'));
|
||||
}).catch(function (err) {
|
||||
should.exist(err);
|
||||
err.message.should.eql('Post not found.');
|
||||
|
||||
done();
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can fetch post with by id', function (done) {
|
||||
|
@ -49,15 +49,15 @@ describe('Users API', function () {
|
||||
});
|
||||
|
||||
describe('Browse', function () {
|
||||
function checkBrowseResponse(response, count) {
|
||||
function checkBrowseResponse(response, count, additional, missing) {
|
||||
should.exist(response);
|
||||
testUtils.API.checkResponse(response, 'users');
|
||||
should.exist(response.users);
|
||||
response.users.should.have.length(count);
|
||||
testUtils.API.checkResponse(response.users[0], 'user');
|
||||
testUtils.API.checkResponse(response.users[1], 'user');
|
||||
testUtils.API.checkResponse(response.users[2], 'user');
|
||||
testUtils.API.checkResponse(response.users[3], 'user');
|
||||
testUtils.API.checkResponse(response.users[0], 'user', additional, missing);
|
||||
testUtils.API.checkResponse(response.users[1], 'user', additional, missing);
|
||||
testUtils.API.checkResponse(response.users[2], 'user', additional, missing);
|
||||
testUtils.API.checkResponse(response.users[3], 'user', additional, missing);
|
||||
}
|
||||
|
||||
it('Owner can browse', function (done) {
|
||||
@ -88,9 +88,16 @@ describe('Users API', function () {
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('No-auth CANNOT browse', function (done) {
|
||||
UserAPI.browse().then(function () {
|
||||
done(new Error('Browse users is not denied without authentication.'));
|
||||
it('No-auth CAN browse, but only gets filtered active users', function (done) {
|
||||
UserAPI.browse().then(function (response) {
|
||||
checkBrowseResponse(response, 7, null, ['email']);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('No-auth CANNOT browse non-active users', function (done) {
|
||||
UserAPI.browse({status: 'invited'}).then(function () {
|
||||
done(new Error('Browse non-active users is not denied without authentication.'));
|
||||
}, function () {
|
||||
done();
|
||||
}).catch(done);
|
||||
@ -111,21 +118,6 @@ describe('Users API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Author can browse', function (done) {
|
||||
UserAPI.browse(context.author).then(function (response) {
|
||||
checkBrowseResponse(response, 7);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('No-auth CANNOT browse', function (done) {
|
||||
UserAPI.browse().then(function () {
|
||||
done(new Error('Browse users is not denied without authentication.'));
|
||||
}, function () {
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('Can browse all', function (done) {
|
||||
UserAPI.browse(_.extend({}, testUtils.context.admin, {status: 'all'})).then(function (response) {
|
||||
checkBrowseResponse(response, 7);
|
||||
|
@ -1,19 +1,16 @@
|
||||
/*globals describe, it, beforeEach, afterEach */
|
||||
/*globals describe, it, afterEach */
|
||||
/*jshint expr:true*/
|
||||
var should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
_ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
|
||||
apiUtils = require('../../server/api/utils');
|
||||
permissions = require('../../server/permissions'),
|
||||
apiUtils = require('../../server/api/utils'),
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('API Utils', function () {
|
||||
var sandbox;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.sandbox.create();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
@ -405,4 +402,74 @@ describe('API Utils', function () {
|
||||
apiUtils.checkFileIsValid({name: 'test.txt', type: 'text'}, ['archive'], ['.txt']).should.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPublicContext', function () {
|
||||
it('should call out to permissions', function () {
|
||||
var permsStub = sandbox.stub(permissions, 'parseContext').returns({public: true});
|
||||
apiUtils.isPublicContext({context: 'test'}).should.be.true;
|
||||
permsStub.called.should.be.true;
|
||||
permsStub.calledWith('test').should.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyPublicPermissions', function () {
|
||||
it('should call out to permissions', function () {
|
||||
var permsStub = sandbox.stub(permissions, 'applyPublicRules');
|
||||
apiUtils.applyPublicPermissions('test', {});
|
||||
permsStub.called.should.be.true;
|
||||
permsStub.calledWith('test', {}).should.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('handlePublicPermissions', function () {
|
||||
it('should return empty options if passed empty options', function (done) {
|
||||
apiUtils.handlePublicPermissions('tests', 'test')({}).then(function (options) {
|
||||
options.should.eql({});
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should treat no context as public', function (done) {
|
||||
var aPPStub = sandbox.stub(apiUtils, 'applyPublicPermissions').returns(Promise.resolve({}));
|
||||
apiUtils.handlePublicPermissions('tests', 'test')({}).then(function (options) {
|
||||
aPPStub.calledOnce.should.eql(true);
|
||||
options.should.eql({});
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should treat user context as NOT public', function (done) {
|
||||
var cTMethodStub = {
|
||||
test: {
|
||||
test: sandbox.stub().returns(Promise.resolve())
|
||||
}
|
||||
},
|
||||
cTStub = sandbox.stub(permissions, 'canThis').returns(cTMethodStub);
|
||||
|
||||
apiUtils.handlePublicPermissions('tests', 'test')({context: {user: 1}}).then(function (options) {
|
||||
cTStub.calledOnce.should.eql(true);
|
||||
cTMethodStub.test.test.calledOnce.should.eql(true);
|
||||
options.should.eql({context: {user: 1}});
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should throw a permissions error if permission is not granted', function (done) {
|
||||
var cTMethodStub = {
|
||||
test: {
|
||||
test: sandbox.stub().returns(Promise.reject())
|
||||
}
|
||||
},
|
||||
cTStub = sandbox.stub(permissions, 'canThis').returns(cTMethodStub);
|
||||
|
||||
apiUtils.handlePublicPermissions('tests', 'test')({context: {user: 1}}).then(function () {
|
||||
done(new Error('should throw error when no permissions'));
|
||||
}).catch(function (err) {
|
||||
cTStub.calledOnce.should.eql(true);
|
||||
cTMethodStub.test.test.calledOnce.should.eql(true);
|
||||
err.errorType.should.eql('NoPermissionError');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -14,39 +14,268 @@ var testUtils = require('../utils'),
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
// TODO move to integrations or stub
|
||||
|
||||
describe('Permissions', function () {
|
||||
before(function (done) {
|
||||
Models.init().then(done).catch(done);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
var permissions = _.map(testUtils.DataGenerator.Content.permissions, function (testPerm) {
|
||||
return testUtils.DataGenerator.forKnex.createPermission(testPerm);
|
||||
describe('actions map', function () {
|
||||
before(function (done) {
|
||||
Models.init().then(done).catch(done);
|
||||
});
|
||||
|
||||
sandbox.stub(Models.Permission, 'findAll', function () {
|
||||
return Promise.resolve(Models.Permissions.forge(permissions));
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
it('can load an actions map from existing permissions', function (done) {
|
||||
permissions.init().then(function (actionsMap) {
|
||||
should.exist(actionsMap);
|
||||
describe('parseContext', function () {
|
||||
it('should return public for no context', function () {
|
||||
permissions.parseContext().should.eql({
|
||||
internal: false,
|
||||
user: null,
|
||||
app: null,
|
||||
public: true
|
||||
});
|
||||
permissions.parseContext({}).should.eql({
|
||||
internal: false,
|
||||
user: null,
|
||||
app: null,
|
||||
public: true
|
||||
});
|
||||
});
|
||||
|
||||
actionsMap.edit.sort().should.eql(['post', 'tag', 'user', 'page'].sort());
|
||||
it('should return public for random context', function () {
|
||||
permissions.parseContext('public').should.eql({
|
||||
internal: false,
|
||||
user: null,
|
||||
app: null,
|
||||
public: true
|
||||
});
|
||||
permissions.parseContext({client: 'thing'}).should.eql({
|
||||
internal: false,
|
||||
user: null,
|
||||
app: null,
|
||||
public: true
|
||||
});
|
||||
});
|
||||
|
||||
actionsMap.should.equal(permissions.actionsMap);
|
||||
it('should return user if user populated', function () {
|
||||
permissions.parseContext({user: 1}).should.eql({
|
||||
internal: false,
|
||||
user: 1,
|
||||
app: null,
|
||||
public: false
|
||||
});
|
||||
});
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
it('should return app if app populated', function () {
|
||||
permissions.parseContext({app: 5}).should.eql({
|
||||
internal: false,
|
||||
user: null,
|
||||
app: 5,
|
||||
public: false
|
||||
});
|
||||
});
|
||||
|
||||
it('should return internal if internal provided', function () {
|
||||
permissions.parseContext({internal: true}).should.eql({
|
||||
internal: true,
|
||||
user: null,
|
||||
app: null,
|
||||
public: false
|
||||
});
|
||||
|
||||
permissions.parseContext('internal').should.eql({
|
||||
internal: true,
|
||||
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 public = {context: {}};
|
||||
|
||||
permissions.applyPublicRules('posts', 'browse', _.cloneDeep(public)).then(function (result) {
|
||||
result.should.not.eql(public);
|
||||
result.should.eql({
|
||||
context: {},
|
||||
status: 'published'
|
||||
});
|
||||
|
||||
return permissions.applyPublicRules('posts', 'browse', _.extend({}, _.cloneDeep(public), {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.should.be.a.String;
|
||||
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.should.be.a.String;
|
||||
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.should.be.a.String;
|
||||
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.should.be.a.String;
|
||||
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.should.be.a.String;
|
||||
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.should.be.a.String;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return unchanged object for user with public context', function (done) {
|
||||
var public = {context: {}};
|
||||
|
||||
permissions.applyPublicRules('users', 'browse', _.cloneDeep(public)).then(function (result) {
|
||||
result.should.not.eql(public);
|
||||
result.should.eql({
|
||||
context: {},
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
return permissions.applyPublicRules('users', 'browse', _.extend({}, _.cloneDeep(public), {status: 'active'}));
|
||||
}).then(function (result) {
|
||||
result.should.eql({
|
||||
context: {},
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should throw an error for an inactive user', function (done) {
|
||||
var inactive = {context: {}, status: 'inactive'};
|
||||
|
||||
permissions.applyPublicRules('users', 'browse', _.cloneDeep(inactive)).then(function () {
|
||||
done('Did not throw an error for inactive');
|
||||
}).catch(function (err) {
|
||||
err.should.be.a.String;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// @TODO: move to integrations or stub
|
||||
// it('does not allow edit post without permission', function (done) {
|
||||
// var fakePage = {
|
||||
// id: 1
|
||||
|
Loading…
Reference in New Issue
Block a user