Skip to content

Commit

Permalink
Merge pull request #6080 from ErisDS/model-access-rules
Browse files Browse the repository at this point in the history
Add access rules bookshelf plugin
  • Loading branch information
sebgie committed Nov 16, 2015
2 parents 007c06f + 666a616 commit 88ee22d
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 10 deletions.
11 changes: 6 additions & 5 deletions core/server/api/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 4 additions & 1 deletion core/server/models/base/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions core/server/models/plugins/access-rules.js
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions core/server/models/plugins/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
accessRules: require('./access-rules'),
includeCount: require('./include-count'),
pagination: require('./pagination')
};
8 changes: 4 additions & 4 deletions core/test/unit/api_utils_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
Expand All @@ -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);
});
Expand All @@ -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);
});
Expand All @@ -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);
});
Expand Down
54 changes: 54 additions & 0 deletions core/test/unit/models_plugins/access-rules_spec.js
Original file line number Diff line number Diff line change
@@ -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;
});
});
});
});

0 comments on commit 88ee22d

Please sign in to comment.