mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Add access rules bookshelf plugin
refs #5614 - change isPublicContext to detectPublicContext - behaviour now expands the context object out - this is a bit of a sideeffect, but this is the simplest change that makes it possible to use the context in the model layer without significant wider changes - add new access rules plugin - takes a context object as part of `forge()` & caches it on the model instance - provides helper functions for testing access rules later on
This commit is contained in:
parent
007c06fdc0
commit
666a616551
6 changed files with 114 additions and 10 deletions
|
@ -137,13 +137,14 @@ utils = {
|
|||
},
|
||||
|
||||
/**
|
||||
* ## Is Public Context?
|
||||
* If this is a public context, return true
|
||||
* ## Detect Public Context
|
||||
* Calls parse context to expand the options.context object
|
||||
* @param {Object} options
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isPublicContext: function isPublicContext(options) {
|
||||
return permissions.parseContext(options.context).public;
|
||||
detectPublicContext: function detectPublicContext(options) {
|
||||
options.context = permissions.parseContext(options.context);
|
||||
return options.context.public;
|
||||
},
|
||||
/**
|
||||
* ## Apply Public Permissions
|
||||
|
@ -174,7 +175,7 @@ utils = {
|
|||
return function doHandlePublicPermissions(options) {
|
||||
var permsPromise;
|
||||
|
||||
if (utils.isPublicContext(options)) {
|
||||
if (utils.detectPublicContext(options)) {
|
||||
permsPromise = utils.applyPublicPermissions(docName, method, options);
|
||||
} else {
|
||||
permsPromise = permissions.canThis(options.context)[method][singular](options.data);
|
||||
|
|
|
@ -30,6 +30,9 @@ ghostBookshelf = bookshelf(config.database.knex);
|
|||
// Load the Bookshelf registry plugin, which helps us avoid circular dependencies
|
||||
ghostBookshelf.plugin('registry');
|
||||
|
||||
// Load the Ghost access rules plugin, which handles passing permissions/context through the model layer
|
||||
ghostBookshelf.plugin(plugins.accessRules);
|
||||
|
||||
// Load the Ghost include count plugin, which allows for the inclusion of cross-table counts
|
||||
ghostBookshelf.plugin(plugins.includeCount);
|
||||
|
||||
|
@ -268,7 +271,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
|||
options = options || {};
|
||||
|
||||
var self = this,
|
||||
itemCollection = this.forge(),
|
||||
itemCollection = this.forge(null, {context: options.context}),
|
||||
tableName = _.result(this.prototype, 'tableName');
|
||||
|
||||
// Filter options so that only permitted ones remain
|
||||
|
|
45
core/server/models/plugins/access-rules.js
Normal file
45
core/server/models/plugins/access-rules.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
// # Access Rules
|
||||
//
|
||||
// Extends Bookshelf.Model.force to take a 'context' option which provides information on how this query should
|
||||
// be treated in terms of data access rules - currently just detecting public requests
|
||||
module.exports = function (Bookshelf) {
|
||||
var model = Bookshelf.Model,
|
||||
Model;
|
||||
|
||||
Model = Bookshelf.Model.extend({
|
||||
/**
|
||||
* Cached copy of the context setup for this model instance
|
||||
*/
|
||||
_context: null,
|
||||
/**
|
||||
* ## Is Public Context?
|
||||
* A helper to determine if this is a public request or not
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isPublicContext: function isPublicContext() {
|
||||
return !!(this._context && this._context.public);
|
||||
}
|
||||
},
|
||||
{
|
||||
/**
|
||||
* ## Forge
|
||||
* Ensure that context gets set as part of the forge
|
||||
*
|
||||
* @param {object} attributes
|
||||
* @param {object} options
|
||||
* @returns {Bookshelf.Model} model
|
||||
*/
|
||||
forge: function forge(attributes, options) {
|
||||
var self = model.forge.apply(this, arguments);
|
||||
|
||||
if (options && options.context) {
|
||||
self._context = options.context;
|
||||
delete options.context;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
});
|
||||
|
||||
Bookshelf.Model = Model;
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
module.exports = {
|
||||
accessRules: require('./access-rules'),
|
||||
includeCount: require('./include-count'),
|
||||
pagination: require('./pagination')
|
||||
};
|
||||
|
|
|
@ -406,7 +406,7 @@ describe('API Utils', function () {
|
|||
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;
|
||||
apiUtils.detectPublicContext({context: 'test'}).should.be.true;
|
||||
permsStub.called.should.be.true;
|
||||
permsStub.calledWith('test').should.be.true;
|
||||
});
|
||||
|
@ -424,7 +424,7 @@ describe('API Utils', function () {
|
|||
describe('handlePublicPermissions', function () {
|
||||
it('should return empty options if passed empty options', function (done) {
|
||||
apiUtils.handlePublicPermissions('tests', 'test')({}).then(function (options) {
|
||||
options.should.eql({});
|
||||
options.should.eql({context: {app: null, internal: false, public: true, user: null}});
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
@ -433,7 +433,7 @@ describe('API Utils', function () {
|
|||
var aPPStub = sandbox.stub(apiUtils, 'applyPublicPermissions').returns(Promise.resolve({}));
|
||||
apiUtils.handlePublicPermissions('tests', 'test')({}).then(function (options) {
|
||||
aPPStub.calledOnce.should.eql(true);
|
||||
options.should.eql({});
|
||||
options.should.eql({context: {app: null, internal: false, public: true, user: null}});
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
@ -449,7 +449,7 @@ describe('API Utils', function () {
|
|||
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}});
|
||||
options.should.eql({context: {app: null, internal: false, public: false, user: 1}});
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
|
54
core/test/unit/models_plugins/access-rules_spec.js
Normal file
54
core/test/unit/models_plugins/access-rules_spec.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*globals describe, it, beforeEach, afterEach */
|
||||
/*jshint expr:true*/
|
||||
var should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
|
||||
// Thing we're testing
|
||||
// accessRules = require('../../../server/models/plugins/access-rules'),
|
||||
models = require('../../../server/models'),
|
||||
ghostBookshelf,
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
// To stop jshint complaining
|
||||
should.equal(true, true);
|
||||
|
||||
describe('Access Rules', function () {
|
||||
beforeEach(function () {
|
||||
return models.init().then(function () {
|
||||
ghostBookshelf = models.Base;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('Base Model', function () {
|
||||
it('should assign isPublicContext to prototype', function () {
|
||||
ghostBookshelf.Model.prototype.isPublicContext.should.be.a.Function;
|
||||
});
|
||||
|
||||
it('should get called when a model is forged', function () {
|
||||
ghostBookshelf.Model.forge(null, {context: 'test'})._context.should.eql('test');
|
||||
});
|
||||
|
||||
describe('isPublicContext', function () {
|
||||
it('should isPublicContext false if no context is set', function () {
|
||||
ghostBookshelf.Model.forge().isPublicContext().should.be.false;
|
||||
});
|
||||
|
||||
it('should return false if context has no `public` property', function () {
|
||||
ghostBookshelf.Model.forge(null, {context: 'test'}).isPublicContext().should.be.false;
|
||||
});
|
||||
|
||||
it('should return false if context.public is false', function () {
|
||||
ghostBookshelf.Model.forge(null, {context: {public: false}}).isPublicContext().should.be.false;
|
||||
});
|
||||
|
||||
it('should return true if context.public is true', function () {
|
||||
ghostBookshelf.Model.forge(null, {context: {public: true}}).isPublicContext().should.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue