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 }')); // }); // }); });