mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-18 16:01:40 +03:00
1882278b5b
- 🛠 add bunyan and prettyjson, remove morgan - ✨ add logging module - GhostLogger class that handles setup of bunyan - PrettyStream for stdout - ✨ config for logging - @TODO: testing level fatal? - ✨ log each request via GhostLogger (express middleware) - @TODO: add errors to output - 🔥 remove errors.updateActiveTheme - we can read the value from config - 🔥 remove 15 helper functions in core/server/errors/index.js - all these functions get replaced by modules: 1. logging 2. error middleware handling for html/json 3. error creation (which will be part of PR #7477) - ✨ add express error handler for html/json - one true error handler for express responses - contains still some TODO's, but they are not high priority for first implementation/integration - this middleware only takes responsibility of either rendering html responses or return json error responses - 🎨 use new express error handler in middleware/index - 404 and 500 handling - 🎨 return error instead of error message in permissions/index.js - the rule for error handling should be: if you call a unit, this unit should return a custom Ghost error - 🎨 wrap serve static module - rule: if you call a module/unit, you should always wrap this error - it's always the same rule - so the caller never has to worry about what comes back - it's always a clear error instance - in this case: we return our notfounderror if serve static does not find the resource - this avoid having checks everywhere - 🎨 replace usages of errors/index.js functions and adapt tests - use logging.error, logging.warn - make tests green - remove some usages of logging and throwing api errors -> because when a request is involved, logging happens automatically - 🐛 return errorDetails to Ghost-Admin - errorDetails is used for Theme error handling - 🎨 use 500er error for theme is missing error in theme-handler - 🎨 extend file rotation to 1w
515 lines
19 KiB
JavaScript
515 lines
19 KiB
JavaScript
var testUtils = require('../utils'),
|
|
should = require('should'),
|
|
sinon = require('sinon'),
|
|
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: 'active'
|
|
});
|
|
|
|
return permissions.applyPublicRules('users', 'browse', _.extend({}, _.cloneDeep(publicContext), {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 instanceof errors.NoPermissionError).should.eql(true);
|
|
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 }'));
|
|
// });
|
|
// });
|
|
});
|