Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add access rules bookshelf plugin #6080

Merged
merged 1 commit into from
Nov 16, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
});
});
});
});