From 4e3b21b7da2e8b8cc0fe0335effcaff9d0d2fbe1 Mon Sep 17 00:00:00 2001 From: Hannah Wolfe <erisds@gmail.com> Date: Wed, 23 Jul 2014 19:17:29 +0100 Subject: [PATCH 1/4] Permissions Improvements refs #3083, #3096 In order to implement advanced permissions based on roles for specific actions, we need to know what role the current context user has and also what action we are granting permissions for: - Permissible gets passed the action type - Effective permissions keeps the user role and eventually passes it to permissible - Fixed spelling - Still needs tests --- core/server/models/post.js | 4 ++-- core/server/models/role.js | 4 ++-- core/server/models/user.js | 4 ++-- core/server/permissions/effective.js | 9 ++------- core/server/permissions/index.js | 23 ++++++++++++----------- core/test/unit/permissions_spec.js | 24 ++++++++++++------------ 6 files changed, 32 insertions(+), 36 deletions(-) diff --git a/core/server/models/post.js b/core/server/models/post.js index 6726962c0b..125da72b8e 100644 --- a/core/server/models/post.js +++ b/core/server/models/post.js @@ -521,7 +521,7 @@ Post = ghostBookshelf.Model.extend({ }); }, - permissable: function (postModelOrId, context, loadedPermissions, hasUserPermission, hasAppPermission) { + permissible: function (postModelOrId, action, context, loadedPermissions, hasUserPermission, hasAppPermission) { var self = this, postModel = postModelOrId, origArgs; @@ -536,7 +536,7 @@ Post = ghostBookshelf.Model.extend({ // Build up the original args but substitute with actual model var newArgs = [foundPostModel].concat(origArgs); - return self.permissable.apply(self, newArgs); + return self.permissible.apply(self, newArgs); }, errors.logAndThrowError); } diff --git a/core/server/models/role.js b/core/server/models/role.js index 924f4c546c..7632dbf801 100644 --- a/core/server/models/role.js +++ b/core/server/models/role.js @@ -40,7 +40,7 @@ Role = ghostBookshelf.Model.extend({ }, - permissable: function (roleModelOrId, context, loadedPermissions, hasUserPermission, hasAppPermission) { + permissible: function (roleModelOrId, action, context, loadedPermissions, hasUserPermission, hasAppPermission) { var self = this, checkAgainst = [], origArgs; @@ -55,7 +55,7 @@ Role = ghostBookshelf.Model.extend({ // Build up the original args but substitute with actual model var newArgs = [foundRoleModel].concat(origArgs); - return self.permissable.apply(self, newArgs); + return self.permissible.apply(self, newArgs); }, errors.logAndThrowError); } diff --git a/core/server/models/user.js b/core/server/models/user.js index 9081b96f55..2664e28724 100644 --- a/core/server/models/user.js +++ b/core/server/models/user.js @@ -430,7 +430,7 @@ User = ghostBookshelf.Model.extend({ }); }, - permissable: function (userModelOrId, context, loadedPermissions, hasUserPermission, hasAppPermission) { + permissible: function (userModelOrId, action, context, loadedPermissions, hasUserPermission, hasAppPermission) { var self = this, userModel = userModelOrId, origArgs; @@ -445,7 +445,7 @@ User = ghostBookshelf.Model.extend({ // Build up the original args but substitute with actual model var newArgs = [foundUserModel].concat(origArgs); - return self.permissable.apply(self, newArgs); + return self.permissible.apply(self, newArgs); }, errors.logAndThrowError); } diff --git a/core/server/permissions/effective.js b/core/server/permissions/effective.js index 61652faa4a..e2211f2e63 100644 --- a/core/server/permissions/effective.js +++ b/core/server/permissions/effective.js @@ -13,11 +13,6 @@ var effective = { allPerms = [], user = foundUser.toJSON(); - // TODO: using 'Owner' as return value is a bit hacky. - if (_.find(user.roles, { 'name': 'Owner' })) { - return 'Owner'; - } - rolePerms.push(foundUser.related('permissions').models); _.each(rolePerms, function (rolePermGroup) { @@ -34,7 +29,7 @@ var effective = { }); }); - return allPerms; + return {permissions: allPerms, roles: user.roles}; }, errors.logAndThrowError); }, @@ -45,7 +40,7 @@ var effective = { return []; } - return foundApp.related('permissions').models; + return {permissions: foundApp.related('permissions').models}; }, errors.logAndThrowError); } }; diff --git a/core/server/permissions/index.js b/core/server/permissions/index.js index 47e0c400d6..3cf8c3fe3d 100644 --- a/core/server/permissions/index.js +++ b/core/server/permissions/index.js @@ -78,8 +78,8 @@ CanThisResult.prototype.buildObjectTypeHandlers = function (obj_types, act_type, // Wait for the user loading to finish return permissionLoad.then(function (loadedPermissions) { // Iterate through the user permissions looking for an affirmation - var userPermissions = loadedPermissions.user, - appPermissions = loadedPermissions.app, + var userPermissions = loadedPermissions.user ? loadedPermissions.user.permissions : null, + appPermissions = loadedPermissions.app ? loadedPermissions.app.permissions : null, hasUserPermission, hasAppPermission, checkPermission = function (perm) { @@ -105,15 +105,14 @@ CanThisResult.prototype.buildObjectTypeHandlers = function (obj_types, act_type, return modelId === permObjId; }; // Check user permissions for matching action, object and id. - if (!_.isEmpty(userPermissions)) { - // TODO: using 'Owner' is a bit hacky. - if (userPermissions === 'Owner') { - hasUserPermission = true; - } else { - hasUserPermission = _.any(userPermissions, checkPermission); - } + + if (_.any(loadedPermissions.user.roles, { 'name': 'Owner' })) { + hasUserPermission = true; + } else if (!_.isEmpty(userPermissions)) { + hasUserPermission = _.any(userPermissions, checkPermission); } + // Check app permissions if they were passed hasAppPermission = true; if (!_.isNull(appPermissions)) { @@ -121,8 +120,10 @@ CanThisResult.prototype.buildObjectTypeHandlers = function (obj_types, act_type, } // Offer a chance for the TargetModel to override the results - if (TargetModel && _.isFunction(TargetModel.permissable)) { - return TargetModel.permissable(modelId, context, loadedPermissions, hasUserPermission, hasAppPermission); + if (TargetModel && _.isFunction(TargetModel.permissible)) { + return TargetModel.permissible( + modelId, act_type, context, loadedPermissions, hasUserPermission, hasAppPermission + ); } if (hasUserPermission && hasAppPermission) { diff --git a/core/test/unit/permissions_spec.js b/core/test/unit/permissions_spec.js index 779e6ba577..b0f9de3042 100644 --- a/core/test/unit/permissions_spec.js +++ b/core/test/unit/permissions_spec.js @@ -106,9 +106,9 @@ describe('Permissions', function () { // }).catch(done); // }); // -// it('can use permissable function on Model to allow something', function (done) { +// it('can use permissible function on Model to allow something', function (done) { // var testUser, -// permissableStub = sandbox.stub(Models.Post, 'permissable', function () { +// permissibleStub = sandbox.stub(Models.Post, 'permissible', function () { // return when.resolve(); // }); // @@ -122,22 +122,22 @@ describe('Permissions', function () { // return permissions.canThis({user: testUser.id}).edit.post(123); // }) // .then(function () { -// permissableStub.restore(); -// permissableStub.calledWith(123, { user: testUser.id, app: null, internal: false }) +// permissibleStub.restore(); +// permissibleStub.calledWith(123, { user: testUser.id, app: null, internal: false }) // .should.equal(true); // // done(); // }) // .catch(function () { -// permissableStub.restore(); +// permissibleStub.restore(); // // done(new Error('did not allow testUser')); // }); // }); // -// it('can use permissable function on Model to forbid something', function (done) { +// it('can use permissible function on Model to forbid something', function (done) { // var testUser, -// permissableStub = sandbox.stub(Models.Post, 'permissable', function () { +// permissibleStub = sandbox.stub(Models.Post, 'permissible', function () { // return when.reject(); // }); // @@ -152,13 +152,13 @@ describe('Permissions', function () { // }) // .then(function () { // -// permissableStub.restore(); +// permissibleStub.restore(); // done(new Error('Allowed testUser to edit post')); // }) // .catch(function () { -// permissableStub.calledWith(123, { user: testUser.id, app: null, internal: false }) +// permissibleStub.calledWith(123, { user: testUser.id, app: null, internal: false }) // .should.equal(true); -// permissableStub.restore(); +// permissibleStub.restore(); // done(); // }); // }); @@ -257,7 +257,7 @@ describe('Permissions', function () { // }); // // it('allows \'internal\' to be passed for internal requests', function (done) { -// // Using tag here because post implements the custom permissable interface +// // Using tag here because post implements the custom permissible interface // permissions.canThis('internal') // .edit // .tag(1) @@ -270,7 +270,7 @@ describe('Permissions', function () { // }); // // it('allows { internal: true } to be passed for internal requests', function (done) { -// // Using tag here because post implements the custom permissable interface +// // Using tag here because post implements the custom permissible interface // permissions.canThis({ internal: true }) // .edit // .tag(1) From e7dc51dc669bee4891d4e55875029bfdb0e8652e Mon Sep 17 00:00:00 2001 From: Hannah Wolfe <erisds@gmail.com> Date: Thu, 24 Jul 2014 23:07:29 +0100 Subject: [PATCH 2/4] Improving error handling --- core/server/api/utils.js | 4 ++-- core/server/errors/index.js | 45 ++++++++++++++++++++++++++++--------- core/test/utils/api.js | 2 +- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/core/server/api/utils.js b/core/server/api/utils.js index d31cdae9f4..948adc060b 100644 --- a/core/server/api/utils.js +++ b/core/server/api/utils.js @@ -16,7 +16,7 @@ utils = { */ checkObject: function (object, docName) { if (_.isEmpty(object) || _.isEmpty(object[docName]) || _.isEmpty(object[docName][0])) { - return when.reject(new errors.BadRequestError('No root key (\'' + docName + '\') provided.')); + return errors.logAndRejectError(new errors.BadRequestError('No root key (\'' + docName + '\') provided.')); } // convert author property to author_id to match the name in the database @@ -27,7 +27,7 @@ utils = { delete object.posts[0].author; } } - return when.resolve(object); + return when(object); } }; diff --git a/core/server/errors/index.js b/core/server/errors/index.js index 9ed4e8789b..678bdc4de2 100644 --- a/core/server/errors/index.js +++ b/core/server/errors/index.js @@ -33,7 +33,7 @@ errors = { throwError: function (err) { if (!err) { - err = new Error("An error occurred"); + err = new Error('An error occurred'); } if (_.isString(err)) { @@ -137,7 +137,7 @@ errors = { logAndRejectError: function (err, context, help) { this.logError(err, context, help); - this.rejectError(err, context, help); + return this.rejectError(err, context, help); }, logErrorWithRedirect: function (msg, context, help, redirectTo, req, res) { @@ -153,6 +153,22 @@ errors = { }; }, + handleAPIError: function (error) { + if (!error) { + return this.rejectError(new this.NoPermissionError('You do not have permission to perform this action')); + } + + if (_.isString(error)) { + return this.rejectError(new this.NoPermissionError(error)); + } + + if (error.type) { + return this.rejectError(error); + } + + return this.rejectError(new this.InternalServerError(error)); + }, + renderErrorPage: function (code, err, req, res, next) { /*jshint unused:false*/ var self = this; @@ -207,17 +223,18 @@ errors = { // And then try to explain things to the user... // Cheat and output the error using handlebars escapeExpression - return res.send(500, "<h1>Oops, seems there is an an error in the error template.</h1>" - + "<p>Encountered the error: </p>" - + "<pre>" + hbs.handlebars.Utils.escapeExpression(templateErr.message || templateErr) + "</pre>" - + "<br ><p>whilst trying to render an error page for the error: </p>" - + code + " " + "<pre>" + hbs.handlebars.Utils.escapeExpression(err.message || err) + "</pre>" - ); + return res.send(500, + '<h1>Oops, seems there is an an error in the error template.</h1>' + + '<p>Encountered the error: </p>' + + '<pre>' + hbs.handlebars.Utils.escapeExpression(templateErr.message || templateErr) + '</pre>' + + '<br ><p>whilst trying to render an error page for the error: </p>' + + code + ' ' + '<pre>' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '</pre>' + ); }); } if (code >= 500) { - this.logError(err, "Rendering Error Page", "Ghost caught a processing error in the middleware layer."); + this.logError(err, 'Rendering Error Page', 'Ghost caught a processing error in the middleware layer.'); } // Are we admin? If so, don't worry about the user template @@ -230,7 +247,7 @@ errors = { }, error404: function (req, res, next) { - var message = res.isAdmin && req.user ? "No Ghost Found" : "Page Not Found"; + var message = res.isAdmin && req.user ? 'No Ghost Found' : 'Page Not Found'; // do not cache 404 error res.set({'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'}); @@ -271,8 +288,16 @@ errors = { // Ensure our 'this' context for methods and preserve method arity by // using Function#bind for expressjs _.each([ + 'logWarn', + 'logInfo', + 'rejectError', + 'throwError', + 'logError', 'logAndThrowError', + 'logAndRejectError', + 'logErrorAndExit', 'logErrorWithRedirect', + 'handleAPIError', 'renderErrorPage', 'error404', 'error500' diff --git a/core/test/utils/api.js b/core/test/utils/api.js index 0b5df91b5c..7a88f8e623 100644 --- a/core/test/utils/api.js +++ b/core/test/utils/api.js @@ -45,7 +45,6 @@ function getAdminURL() { // make sure the API only returns expected properties only function checkResponseValue(jsonResponse, properties) { - Object.keys(jsonResponse).length.should.eql(properties.length); for (var i = 0; i < properties.length; i = i + 1) { // For some reason, settings response objects do not have the 'hasOwnProperty' method if (Object.prototype.hasOwnProperty.call(jsonResponse, properties[i])) { @@ -53,6 +52,7 @@ function checkResponseValue(jsonResponse, properties) { } jsonResponse.should.have.property(properties[i]); } + Object.keys(jsonResponse).length.should.eql(properties.length); } function checkResponse(jsonResponse, objectType, additionalProperties) { From 7714dc6ab16ac2a9c06f6ecc4bb18128797427f6 Mon Sep 17 00:00:00 2001 From: Hannah Wolfe <erisds@gmail.com> Date: Thu, 24 Jul 2014 23:22:27 +0100 Subject: [PATCH 3/4] Adding role API tests & fixing browse refs #3083, refs #3196 --- core/server/models/role.js | 19 ++- core/test/integration/api/api_roles_spec.js | 124 ++++++++++++++++++++ core/test/utils/api.js | 1 + 3 files changed, 133 insertions(+), 11 deletions(-) create mode 100644 core/test/integration/api/api_roles_spec.js diff --git a/core/server/models/role.js b/core/server/models/role.js index 7632dbf801..e528f1314d 100644 --- a/core/server/models/role.js +++ b/core/server/models/role.js @@ -59,19 +59,16 @@ Role = ghostBookshelf.Model.extend({ }, errors.logAndThrowError); } - switch (loadedPermissions.user) { - case 'Owner': - case 'Administrator': + if (action === 'assign' && loadedPermissions.user) { + if (_.any(loadedPermissions.user.roles, { 'name': 'Owner' }) || + _.any(loadedPermissions.user.roles, { 'name': 'Administrator' })) { checkAgainst = ['Administrator', 'Editor', 'Author']; - break; - case 'Editor': - checkAgainst = ['Editor', 'Author']; - } + } else if (_.any(loadedPermissions.user.roles, { 'name': 'Editor' })) { + checkAgainst = ['Author']; + } - // If we have a role passed into here - if (roleModelOrId && !_.contains(checkAgainst, roleModelOrId.get('name'))) { - // Role not in the list of permissible roles - hasUserPermission = false; + // Role in the list of permissible roles + hasUserPermission = roleModelOrId && _.contains(checkAgainst, roleModelOrId.get('name')); } if (hasUserPermission && hasAppPermission) { diff --git a/core/test/integration/api/api_roles_spec.js b/core/test/integration/api/api_roles_spec.js new file mode 100644 index 0000000000..10af87eb4a --- /dev/null +++ b/core/test/integration/api/api_roles_spec.js @@ -0,0 +1,124 @@ +/*globals describe, before, beforeEach, afterEach, it */ +/*jshint expr:true*/ +var testUtils = require('../../utils'), + should = require('should'), + _ = require('lodash'), + + // Stuff we are testing + RoleAPI = require('../../../server/api/roles'), + context = testUtils.context; + +describe('Roles API', function () { + // Keep the DB clean + before(testUtils.teardown); + afterEach(testUtils.teardown); + beforeEach(testUtils.setup('users:roles', 'perms:role', 'perms:init')); + + describe('Browse', function () { + function checkBrowseResponse(response) { + should.exist(response); + testUtils.API.checkResponse(response, 'roles'); + should.exist(response.roles); + response.roles.should.have.length(4); + testUtils.API.checkResponse(response.roles[0], 'role'); + testUtils.API.checkResponse(response.roles[1], 'role'); + testUtils.API.checkResponse(response.roles[2], 'role'); + testUtils.API.checkResponse(response.roles[3], 'role'); + } + + it('Owner can browse', function (done) { + RoleAPI.browse(context.owner).then(function (response) { + checkBrowseResponse(response); + done(); + }).catch(done); + }); + + it('Admin can browse', function (done) { + RoleAPI.browse(context.admin).then(function (response) { + checkBrowseResponse(response); + done(); + }).catch(done); + }); + + it('Editor can browse', function (done) { + RoleAPI.browse(context.editor).then(function (response) { + checkBrowseResponse(response); + done(); + }).catch(done); + }); + + it('Author can browse', function (done) { + RoleAPI.browse(context.author).then(function (response) { + checkBrowseResponse(response); + done(); + }).catch(done); + }); + + it('No-auth CANNOT browse', function (done) { + RoleAPI.browse().then(function () { + done(new Error('Browse roles is not denied without authentication.')); + }, function () { + done(); + }).catch(done); + }); + }); + + describe('Browse permissions=assign', function () { + function checkBrowseResponse(response) { + should.exist(response); + should.exist(response.roles); + testUtils.API.checkResponse(response, 'roles'); + response.roles.should.have.length(3); + testUtils.API.checkResponse(response.roles[0], 'role'); + testUtils.API.checkResponse(response.roles[1], 'role'); + testUtils.API.checkResponse(response.roles[2], 'role'); + response.roles[0].name.should.equal('Administrator'); + response.roles[1].name.should.equal('Editor'); + response.roles[2].name.should.equal('Author'); + } + + it('Owner can assign all', function (done) { + RoleAPI.browse(_.extend(context.owner, {permissions: 'assign'})).then(function (response) { + checkBrowseResponse(response); + done(); + }).catch(done); + }); + + it('Admin can assign all', function (done) { + RoleAPI.browse(_.extend(context.admin, {permissions: 'assign'})).then(function (response) { + checkBrowseResponse(response); + done(); + }).catch(done); + }); + + it('Editor can assign Author', function (done) { + RoleAPI.browse(_.extend(context.editor, {permissions: 'assign'})).then(function (response) { + should.exist(response); + should.exist(response.roles); + testUtils.API.checkResponse(response, 'roles'); + response.roles.should.have.length(1); + testUtils.API.checkResponse(response.roles[0], 'role'); + response.roles[0].name.should.equal('Author'); + done(); + }).catch(done); + }); + + it('Author CANNOT assign any', function (done) { + RoleAPI.browse(_.extend(context.author, {permissions: 'assign'})).then(function (response) { + should.exist(response); + should.exist(response.roles); + testUtils.API.checkResponse(response, 'roles'); + response.roles.should.have.length(0); + done(); + }).catch(done); + }); + + it('No-auth CANNOT browse', function (done) { + RoleAPI.browse({permissions: 'assign'}).then(function () { + done(new Error('Browse roles is not denied without authentication.')); + }, function () { + done(); + }).catch(done); + }); + }); +}); \ No newline at end of file diff --git a/core/test/utils/api.js b/core/test/utils/api.js index 7a88f8e623..c4efdc1865 100644 --- a/core/test/utils/api.js +++ b/core/test/utils/api.js @@ -7,6 +7,7 @@ var url = require('url'), expectedProperties = { posts: ['posts', 'meta'], users: ['users', 'meta'], + roles: ['roles'], pagination: ['page', 'limit', 'pages', 'total', 'next', 'prev'], post: ['id', 'uuid', 'title', 'slug', 'markdown', 'html', 'meta_title', 'meta_description', 'featured', 'image', 'status', 'language', 'created_at', 'created_by', 'updated_at', From 987e9277dcf29e48773223be5e4bb07abd7ea34a Mon Sep 17 00:00:00 2001 From: Hannah Wolfe <erisds@gmail.com> Date: Thu, 24 Jul 2014 10:46:05 +0100 Subject: [PATCH 4/4] User edit, add & destroy perms restricted by role closes #3096, closes #3378, refs #3100 - user.permissible updated to reflect proper permissions - small amount of API refactoring to handle extra cases - extensive integration testing --- core/server/api/users.js | 113 +- core/server/models/user.js | 153 ++- core/test/integration/api/api_users_spec.js | 1104 +++++++++++++++---- core/test/utils/fixtures/data-generator.js | 16 +- core/test/utils/index.js | 44 +- 5 files changed, 1118 insertions(+), 312 deletions(-) diff --git a/core/server/api/users.js b/core/server/api/users.js index 0240d85db6..77e80c2271 100644 --- a/core/server/api/users.js +++ b/core/server/api/users.js @@ -10,7 +10,6 @@ var when = require('when'), globalUtils = require('../utils'), config = require('../config'), mail = require('./mail'), - rolesAPI = require('./roles'), docName = 'users', ONE_DAY = 60 * 60 * 24 * 1000, @@ -44,8 +43,8 @@ users = { options.include = prepareInclude(options.include); } return dataProvider.User.findPage(options); - }, function () { - return when.reject(new errors.NoPermissionError('You do not have permission to browse users.')); + }).catch(function (error) { + return errors.handleAPIError(error); }); }, @@ -84,48 +83,44 @@ users = { * @returns {Promise(User)} */ edit: function edit(object, options) { + var editOperation; if (options.id === 'me' && options.context && options.context.user) { options.id = options.context.user; } - return canThis(options.context).edit.user(options.id).then(function () { - // TODO: add permission check for roles - // if (data.roles) { - // return canThis(options.context).assign.role(<role-id>) - // } - // }.then(function (){ - return utils.checkObject(object, docName).then(function (checkedUserData) { + if (options.include) { + options.include = prepareInclude(options.include); + } - if (options.include) { - options.include = prepareInclude(options.include); + return utils.checkObject(object, docName).then(function (data) { + // Edit operation + editOperation = function () { + return dataProvider.User.edit(data.users[0], options) + .then(function (result) { + if (result) { + return { users: [result.toJSON()]}; + } + + return when.reject(new errors.NotFoundError('User not found.')); + }); + }; + + // Check permissions + return canThis(options.context).edit.user(options.id).then(function () { + if (data.users[0].roles) { + if (options.id === options.context.user) { + return when.reject(new errors.NoPermissionError('You cannot change your own role.')); + } + return canThis(options.context).assign.role(data.users[0].roles[0]).then(function () { + return editOperation(); + }); } - return dataProvider.User.edit(checkedUserData.users[0], options); - }).then(function (result) { - if (result) { - return { users: [result.toJSON()]}; - } - return when.reject(new errors.NotFoundError('User not found.')); - }); - }, function () { - return when.reject(new errors.NoPermissionError('You do not have permission to edit this user.')); - }); - }, + return editOperation(); - /** - * ### Destroy - * @param {{id, context}} options - * @returns {Promise(User)} - */ - destroy: function destroy(options) { - return canThis(options.context).destroy.user(options.id).then(function () { - return users.read(options).then(function (result) { - return dataProvider.User.destroy(options).then(function () { - return result; - }); }); - }, function () { - return when.reject(new errors.NoPermissionError('You do not have permission to remove the user.')); + }).catch(function (error) { + return errors.handleAPIError(error); }); }, @@ -137,23 +132,26 @@ users = { */ add: function add(object, options) { var newUser, - user; + user, + roleId; - return canThis(options.context).add.user().then(function () { + return canThis(options.context).add.user(object).then(function () { return utils.checkObject(object, docName).then(function (checkedUserData) { if (options.include) { options.include = prepareInclude(options.include); } newUser = checkedUserData.users[0]; - newUser.role = parseInt(newUser.roles[0].id || newUser.roles[0], 10); + roleId = parseInt(newUser.roles[0].id || newUser.roles[0], 10); - return rolesAPI.browse({ context: options.context, permissions: 'assign' }).then(function (results) { - // Make sure user is allowed to add a user with this role - if (!_.any(results.roles, { id: newUser.role })) { - return when.reject(new errors.NoPermissionError('Not allowed to create user with that role.')); + // Make sure user is allowed to add a user with this role + return dataProvider.Role.findOne({id: roleId}).then(function (role) { + if (role.get('name') === 'Owner') { + return when.reject(new errors.NoPermissionError('Not allowed to create an owner user.')); } + return canThis(options.context).assign.role(role); + }).then(function () { if (newUser.email) { newUser.name = object.users[0].email.substring(0, newUser.email.indexOf('@')); newUser.password = globalUtils.uid(50); @@ -161,6 +159,8 @@ users = { } else { return when.reject(new errors.BadRequestError('No email provided.')); } + }).catch(function () { + return when.reject(new errors.NoPermissionError('Not allowed to create user with that role.')); }); }).then(function () { return dataProvider.User.getByEmail(newUser.email); @@ -208,7 +208,7 @@ users = { }); }).then(function () { return when.resolve({users: [user]}); - }).otherwise(function (error) { + }).catch(function (error) { if (error && error.type === 'EmailError') { error.message = 'Error sending email: ' + error.message + ' Please check your email settings and resend the invitation.'; errors.logWarn(error.message); @@ -222,11 +222,30 @@ users = { } return when.reject(error); }); - }, function () { - return when.reject(new errors.NoPermissionError('You do not have permission to add a user.')); + }).catch(function (error) { + return errors.handleAPIError(error); }); }, + + /** + * ### Destroy + * @param {{id, context}} options + * @returns {Promise(User)} + */ + destroy: function destroy(options) { + return canThis(options.context).destroy.user(options.id).then(function () { + return users.read(options).then(function (result) { + return dataProvider.User.destroy(options).then(function () { + return result; + }); + }); + }).catch(function (error) { + return errors.handleAPIError(error); + }); + }, + + /** * ### Change Password * @param {password} object @@ -244,7 +263,7 @@ users = { return dataProvider.User.changePassword(oldPassword, newPassword, ne2Password, options).then(function () { return when.resolve({password: [{message: 'Password changed successfully.'}]}); - }).otherwise(function (error) { + }).catch(function (error) { return when.reject(new errors.ValidationError(error.message)); }); }); diff --git a/core/server/models/user.js b/core/server/models/user.js index 2664e28724..b5e51926f0 100644 --- a/core/server/models/user.js +++ b/core/server/models/user.js @@ -93,6 +93,14 @@ User = ghostBookshelf.Model.extend({ permissions: function () { return this.belongsToMany('Permission'); + }, + + hasRole: function (roleName) { + var roles = this.related('roles'); + + return roles.some(function (role) { + return role.get('name') === roleName; + }); } }, { @@ -183,7 +191,9 @@ User = ghostBookshelf.Model.extend({ if (options.status && options.status !== 'all') { // make sure that status is valid //TODO: need a better way of getting a list of statuses other than hard-coding them... - options.status = _.indexOf(['active', 'warn-1', 'warn-2', 'warn-3', 'locked', 'invited'], options.status) !== -1 ? options.status : 'active'; + options.status = _.indexOf( + ['active', 'warn-1', 'warn-2', 'warn-3', 'locked', 'invited'], + options.status) !== -1 ? options.status : 'active'; options.where.status = options.status; } @@ -300,8 +310,6 @@ User = ghostBookshelf.Model.extend({ */ edit: function (data, options) { var self = this, - adminRole, - ownerRole, roleId; options = options || {}; @@ -312,11 +320,10 @@ User = ghostBookshelf.Model.extend({ if (data.roles) { roleId = parseInt(data.roles[0].id || data.roles[0], 10); - if (user.id === options.context.user) { - return when.reject(new errors.ValidationError('You are not allowed to assign a new role to yourself')); - } if (data.roles.length > 1) { - return when.reject(new errors.ValidationError('Only one role per user is supported at the moment.')); + return when.reject( + new errors.ValidationError('Only one role per user is supported at the moment.') + ); } return user.roles().fetch().then(function (roles) { @@ -325,27 +332,9 @@ User = ghostBookshelf.Model.extend({ return user; } return Role.findOne({id: roleId}); - }).then(function (role) { - if (role && role.get('name') === 'Owner') { - // Get admin and owner role - return Role.findOne({name: 'Administrator'}).then(function (result) { - adminRole = result; - return Role.findOne({name: 'Owner'}); - }).then(function (result) { - ownerRole = result; - return User.findOne({id: options.context.user}); - }).then(function (contextUser) { - // check if user has the owner role - var currentRoles = contextUser.toJSON().roles; - if (!_.contains(currentRoles, ownerRole.id)) { - return when.reject(new errors.ValidationError('Only owners are able to transfer the owner role.')); - } - // convert owner to admin - return contextUser.roles().updatePivot({role_id: adminRole.id}); - }).then(function () { - // assign owner role to a new user - return user.roles().updatePivot({role_id: ownerRole.id}); - }); + }).then(function (roleToAssign) { + if (roleToAssign && roleToAssign.get('name') === 'Owner') { + return self.transferOwnership(user, roleToAssign, options.context); } else { // assign all other roles return user.roles().updatePivot({role_id: roleId}); @@ -371,7 +360,18 @@ User = ghostBookshelf.Model.extend({ add: function (data, options) { var self = this, // Clone the _user so we don't expose the hashed password unnecessarily - userData = this.filterData(data); + userData = this.filterData(data), + // Get the role we're going to assign to this user, or the author role if there isn't one + // TODO: don't reference Author role by ID! + roles = data.roles || [1]; + + // remove roles from the object + delete data.roles; + + // check for too many roles + if (roles.length > 1) { + return when.reject(new errors.ValidationError('Only one role per user is supported at the moment.')); + } options = this.filterOptions(options, 'add'); options.withRelated = _.union([ 'roles' ], options.include); @@ -392,13 +392,9 @@ User = ghostBookshelf.Model.extend({ }).then(function (addedUser) { // Assign the userData to our created user so we can pass it back userData = addedUser; - if (!data.role) { - // TODO: needs change when owner role is introduced and setup is changed - data.role = 1; - } - return userData.roles().attach(data.role); - }).then(function (addedUserRole) { - /*jshint unused:false*/ + + return userData.roles().attach(roles); + }).then(function () { // find and return the added user return self.findOne({id: userData.id}, options); }); @@ -435,9 +431,9 @@ User = ghostBookshelf.Model.extend({ userModel = userModelOrId, origArgs; - // If we passed in an id instead of a model, get the model - // then check the permissions + // If we passed in an id instead of a model, get the model then check the permissions if (_.isNumber(userModelOrId) || _.isString(userModelOrId)) { + // Grab the original args without the first one origArgs = _.toArray(arguments).slice(1); // Get the actual post model @@ -449,9 +445,37 @@ User = ghostBookshelf.Model.extend({ }, errors.logAndThrowError); } - if (userModel) { - // If this is the same user that requests the operation allow it. - hasUserPermission = hasUserPermission || context.user === userModel.get('id'); + if (action === 'edit') { + // Users with the role 'Editor' and 'Author' have complex permissions when the action === 'edit' + // We now have all the info we need to construct the permissions + if (_.any(loadedPermissions.user.roles, { 'name': 'Author' })) { + // If this is the same user that requests the operation allow it. + hasUserPermission = hasUserPermission || context.user === userModel.get('id'); + } + + if (_.any(loadedPermissions.user.roles, { 'name': 'Editor' })) { + // If this is the same user that requests the operation allow it. + hasUserPermission = context.user === userModel.get('id'); + + // Alternatively, if the user we are trying to edit is an Author, allow it + hasUserPermission = hasUserPermission || userModel.hasRole('Author'); + } + } + + if (action === 'destroy') { + // Owner cannot be deleted EVER + if (userModel.hasRole('Owner')) { + return when.reject(); + } + + // Users with the role 'Editor' have complex permissions when the action === 'destroy' + if (_.any(loadedPermissions.user.roles, { 'name': 'Editor' })) { + // If this is the same user that requests the operation allow it. + hasUserPermission = context.user === userModel.get('id'); + + // Alternatively, if the user we are trying to edit is an Author, allow it + hasUserPermission = hasUserPermission || userModel.hasRole('Author'); + } } if (hasUserPermission && hasAppPermission) { @@ -490,8 +514,9 @@ User = ghostBookshelf.Model.extend({ if (!user) { return when.reject(new errors.NotFoundError('There is no user with that email address.')); } - if (user.get('status') === 'invited' || user.get('status') === 'invited-pending' - || user.get('status') === 'inactive') { + if (user.get('status') === 'invited' || user.get('status') === 'invited-pending' || + user.get('status') === 'inactive' + ) { return when.reject(new Error('The user with that email address is inactive.')); } if (user.get('status') !== 'locked') { @@ -588,24 +613,25 @@ User = ghostBookshelf.Model.extend({ // Check if invalid structure if (!parts || parts.length !== 3) { - return when.reject(new Error("Invalid token structure")); + return when.reject(new Error('Invalid token structure')); } expires = parseInt(parts[0], 10); email = parts[1]; if (isNaN(expires)) { - return when.reject(new Error("Invalid token expiration")); + return when.reject(new Error('Invalid token expiration')); } // Check if token is expired to prevent replay attacks if (expires < Date.now()) { - return when.reject(new Error("Expired token")); + return when.reject(new Error('Expired token')); } - // to prevent brute force attempts to reset the password the combination of email+expires is only allowed for 10 attempts + // to prevent brute force attempts to reset the password the combination of email+expires is only allowed for + // 10 attempts if (tokenSecurity[email + '+' + expires] && tokenSecurity[email + '+' + expires].count >= 10) { - return when.reject(new Error("Token locked")); + return when.reject(new Error('Token locked')); } return this.generateResetToken(email, expires, dbHash).then(function (generatedToken) { @@ -627,8 +653,10 @@ User = ghostBookshelf.Model.extend({ } // increase the count for email+expires for each failed attempt - tokenSecurity[email + '+' + expires] = {count: tokenSecurity[email + '+' + expires] ? tokenSecurity[email + '+' + expires].count + 1 : 1}; - return when.reject(new Error("Invalid token")); + tokenSecurity[email + '+' + expires] = { + count: tokenSecurity[email + '+' + expires] ? tokenSecurity[email + '+' + expires].count + 1 : 1 + }; + return when.reject(new Error('Invalid token')); }); }, @@ -636,7 +664,7 @@ User = ghostBookshelf.Model.extend({ var self = this; if (newPassword !== ne2Password) { - return when.reject(new Error("Your new passwords do not match")); + return when.reject(new Error('Your new passwords do not match')); } return validatePasswordLength(newPassword).then(function () { @@ -657,10 +685,30 @@ User = ghostBookshelf.Model.extend({ }); }, + transferOwnership: function (user, ownerRole, context) { + var adminRole; + // Get admin role + return Role.findOne({name: 'Administrator'}).then(function (result) { + adminRole = result; + return User.findOne({id: context.user}); + }).then(function (contextUser) { + // check if user has the owner role + var currentRoles = contextUser.toJSON().roles; + if (!_.contains(currentRoles, ownerRole.id)) { + return when.reject(new errors.NoPermissionError('Only owners are able to transfer the owner role.')); + } + // convert owner to admin + return contextUser.roles().updatePivot({role_id: adminRole.id}); + }).then(function () { + // assign owner role to a new user + return user.roles().updatePivot({role_id: ownerRole.id}); + }); + }, + gravatarLookup: function (userData) { var gravatarUrl = '//www.gravatar.com/avatar/' + crypto.createHash('md5').update(userData.email.toLowerCase().trim()).digest('hex') + - "?d=404&s=250", + '?d=404&s=250', checkPromise = when.defer(); http.get('http:' + gravatarUrl, function (res) { @@ -675,7 +723,6 @@ User = ghostBookshelf.Model.extend({ return checkPromise.promise; }, - // Get the user by email address, enforces case insensitivity rejects if the user is not found // When multi-user support is added, email addresses must be deduplicated with case insensitivity, so that // joe@bloggs.com and JOE@BLOGGS.COM cannot be created as two separate users. diff --git a/core/test/integration/api/api_users_spec.js b/core/test/integration/api/api_users_spec.js index 564c4d93b3..5f79b549a8 100644 --- a/core/test/integration/api/api_users_spec.js +++ b/core/test/integration/api/api_users_spec.js @@ -2,237 +2,935 @@ /*jshint expr:true*/ var testUtils = require('../../utils'), should = require('should'), + sinon = require('sinon'), + when = require('when'), _ = require('lodash'), - // Stuff we are testing +// Stuff we are testing UserModel = require('../../../server/models').User, - UserAPI = require('../../../server/api/users'); + UserAPI = require('../../../server/api/users'), + mail = require('../../../server/api/mail'), + + context = testUtils.context, + userIdFor = testUtils.users.ids, + roleIdFor = testUtils.roles.ids, + sandbox = sinon.sandbox.create(); describe('Users API', function () { // Keep the DB clean before(testUtils.teardown); afterEach(testUtils.teardown); - beforeEach(testUtils.setup('users:roles', 'perms:user', 'perms:init')); + // TODO: remove settings once #3281 is fixed + beforeEach(testUtils.setup( 'users:roles', 'users', 'settings', 'perms:user', 'perms:role', 'perms:setting', 'perms:init')); - before(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); - }); +// it('dateTime fields are returned as Date objects', function (done) { +// var userData = testUtils.DataGenerator.forModel.users[0]; +// +// UserModel.check({ email: userData.email, password: userData.password }).then(function (user) { +// return UserAPI.read({ id: user.id }); +// }).then(function (response) { +// response.users[0].created_at.should.be.an.instanceof(Date); +// response.users[0].updated_at.should.be.an.instanceof(Date); +// response.users[0].last_login.should.be.an.instanceof(Date); +// +// done(); +// }).catch(done); +// }); +// +// describe('Browse', function () { +// function checkBrowseResponse(response) { +// should.exist(response); +// testUtils.API.checkResponse(response, 'users'); +// should.exist(response.users); +// response.users.should.have.length(7); +// testUtils.API.checkResponse(response.users[0], 'user', ['roles']); +// testUtils.API.checkResponse(response.users[1], 'user', ['roles']); +// testUtils.API.checkResponse(response.users[2], 'user', ['roles']); +// testUtils.API.checkResponse(response.users[3], 'user', ['roles']); +// } +// +// it('Owner can browse', function (done) { +// UserAPI.browse(context.owner).then(function (response) { +// checkBrowseResponse(response); +// done(); +// }).catch(done); +// }); +// +// it('Admin can browse', function (done) { +// UserAPI.browse(context.admin).then(function (response) { +// checkBrowseResponse(response); +// done(); +// }).catch(done); +// }); +// +// it('Editor can browse', function (done) { +// UserAPI.browse(context.editor).then(function (response) { +// checkBrowseResponse(response); +// done(); +// }).catch(done); +// }); +// +// it('Author can browse', function (done) { +// UserAPI.browse(context.author).then(function (response) { +// checkBrowseResponse(response); +// done(); +// }).catch(done); +// }); +// +// it('No-auth CANNOT browse', function (done) { +// UserAPI.browse().then(function () { +// done(new Error('Browse users is not denied without authentication.')); +// }, function () { +// done(); +// }).catch(done); +// }); +// }); +// +// describe('Read', function () { +// function checkReadResponse(response) { +// should.exist(response); +// should.not.exist(response.meta); +// should.exist(response.users); +// response.users[0].id.should.eql(1); +// testUtils.API.checkResponse(response.users[0], 'user', ['roles']); +// response.users[0].created_at.should.be.a.Date; +// } +// +// it('Owner can read', function (done) { +// UserAPI.read(_.extend({}, context.owner, {id: userIdFor.owner})).then(function (response) { +// checkReadResponse(response); +// done(); +// }).catch(done); +// }); +// +// +// it('Admin can read', function (done) { +// UserAPI.read(_.extend({}, context.admin, {id: userIdFor.owner})).then(function (response) { +// checkReadResponse(response); +// +// done(); +// }).catch(done); +// }); +// +// it('Editor can read', function (done) { +// UserAPI.read(_.extend({}, context.editor, {id: userIdFor.owner})).then(function (response) { +// checkReadResponse(response); +// done(); +// }).catch(done); +// }); +// +// it('Author can read', function (done) { +// UserAPI.read(_.extend({}, context.author, {id: userIdFor.owner})).then(function (response) { +// checkReadResponse(response); +// done(); +// }).catch(done); +// }); +// +// it('No-auth can read', function (done) { +// UserAPI.read({id: userIdFor.owner}).then(function (response) { +// checkReadResponse(response); +// done(); +// }).catch(done); +// }); +// }); +// +// describe('Edit', function () { +// var newName = 'Jo McBlogger'; +// +// function checkEditResponse(response) { +// should.exist(response); +// should.not.exist(response.meta); +// should.exist(response.users); +// response.users.should.have.length(1); +// testUtils.API.checkResponse(response.users[0], 'user', ['roles']); +// response.users[0].name.should.equal(newName); +// response.users[0].updated_at.should.be.a.Date; +// } +// +// it('Owner can edit all roles', function (done) { +// UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.owner, {id: userIdFor.owner})) +// .then(function (response) { +// checkEditResponse(response); +// +// return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.owner, {id: userIdFor.admin})); +// }).then(function (response) { +// +// checkEditResponse(response); +// return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.owner, {id: userIdFor.editor})); +// }).then(function (response) { +// checkEditResponse(response); +// +// return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.owner, {id: userIdFor.author})); +// }).then(function (response) { +// checkEditResponse(response); +// +// done(); +// }).catch(done); +// }); +// +// it('Admin can edit all roles', function (done) { +// UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.admin, {id: userIdFor.owner})) +// .then(function (response) { +// checkEditResponse(response); +// +// return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.admin, {id: userIdFor.admin})); +// }).then(function (response) { +// +// checkEditResponse(response); +// return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.admin, {id: userIdFor.editor})); +// }).then(function (response) { +// checkEditResponse(response); +// +// return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.admin, {id: userIdFor.author})); +// }).then(function (response) { +// checkEditResponse(response); +// +// done(); +// }).catch(done); +// }); +// +// it('Editor CANNOT edit Owner, Admin or Editor roles', function (done) { +// // Cannot edit Owner +// UserAPI.edit( +// {users: [{name: newName}]}, _.extend({}, context.editor, {id: userIdFor.owner}) +// ).then(function () { +// done(new Error('Editor should not be able to edit owner account')); +// }).catch(function (error) { +// error.type.should.eql('NoPermissionError'); +// }).finally(function () { +// // Cannot edit Admin +// UserAPI.edit( +// {users: [{name: newName}]}, _.extend({}, context.editor, {id: userIdFor.admin}) +// ).then(function () { +// done(new Error('Editor should not be able to edit admin account')); +// }).catch(function (error) { +// error.type.should.eql('NoPermissionError'); +// }).finally(function () { +// // Cannot edit Editor +// UserAPI.edit( +// {users: [{name: newName}]}, _.extend({}, context.editor, {id: userIdFor.editor2}) +// ).then(function () { +// done(new Error('Editor should not be able to edit other editor account')); +// }).catch(function (error) { +// error.type.should.eql('NoPermissionError'); +// done(); +// }); +// }); +// }); +// }); +// +// it('Editor can edit self or Author role', function (done) { +// // Can edit self +// UserAPI.edit( +// {users: [{name: newName}]}, _.extend({}, context.editor, {id: userIdFor.editor}) +// ).then(function (response) { +// checkEditResponse(response); +// // Can edit Author +// return UserAPI.edit( +// {users: [{name: newName}]}, _.extend({}, context.editor, {id: userIdFor.author}) +// ); +// }).then(function (response) { +// checkEditResponse(response); +// done(); +// }).catch(done); +// }); +// +// it('Author CANNOT edit all roles', function (done) { +// // Cannot edit owner +// UserAPI.edit( +// {users: [{name: newName}]}, _.extend({}, context.author, {id: userIdFor.owner}) +// ).then(function () { +// done(new Error('Editor should not be able to edit owner account')); +// }).catch(function (error) { +// error.type.should.eql('NoPermissionError'); +// }).finally(function () { +// // Cannot edit admin +// UserAPI.edit( +// {users: [{name: newName}]}, _.extend({}, context.author, {id: userIdFor.admin}) +// ).then(function () { +// done(new Error('Editor should not be able to edit admin account')); +// }).catch(function (error) { +// error.type.should.eql('NoPermissionError'); +// }).finally(function () { +// UserAPI.edit( +// {users: [{name: newName}]}, _.extend({}, context.author, {id: userIdFor.author2}) +// ).then(function () { +// done(new Error('Author should not be able to edit author account which is not their own')); +// }).catch(function (error) { +// error.type.should.eql('NoPermissionError'); +// done(); +// }); +// }); +// }); +// }); +// +// it('Author can edit self', function (done) { +// // Next test that author CAN edit self +// UserAPI.edit( +// {users: [{name: newName}]}, _.extend({}, context.author, {id: userIdFor.author}) +// ).then(function (response) { +// checkEditResponse(response); +// done(); +// }).catch(done); +// }); +// }); +// +// describe('Add', function () { +// var newUser; +// +// beforeEach(function () { +// newUser = _.clone(testUtils.DataGenerator.forKnex.createUser(testUtils.DataGenerator.Content.users[4])); +// +// sandbox.stub(UserModel, 'gravatarLookup', function (userData) { +// return when.resolve(userData); +// }); +// +// sandbox.stub(mail, 'send', function () { +// return when.resolve(); +// }); +// }); +// afterEach(function () { +// sandbox.restore(); +// }); +// +// function checkAddResponse(response) { +// should.exist(response); +// should.exist(response.users); +// should.not.exist(response.meta); +// response.users.should.have.length(1); +// testUtils.API.checkResponse(response.users[0], 'user', ['roles']); +// response.users[0].created_at.should.be.a.Date; +// } +// +// describe('Owner', function () { +// it('CANNOT add an Owner', function (done) { +// newUser.roles = [roleIdFor.owner]; +// // Owner cannot add owner +// UserAPI.add({users: [newUser]}, _.extend({}, context.owner, {include: 'roles'})) +// .then(function () { +// done(new Error('Owner should not be able to add an owner')); +// }).catch(function (error) { +// error.type.should.eql('NoPermissionError'); +// done(); +// }); +// }); +// +// it('Can add an Admin', function (done) { +// // Can add admin +// newUser.roles = [roleIdFor.admin]; +// UserAPI.add({users: [newUser]}, _.extend({}, context.owner, {include: 'roles'})) +// .then(function (response) { +// checkAddResponse(response); +// response.users[0].id.should.eql(8); +// response.users[0].roles[0].name.should.equal('Administrator'); +// done(); +// }).catch(done); +// }); +// +// it('Can add an Editor', function (done) { +// // Can add editor +// newUser.roles = [roleIdFor.editor]; +// UserAPI.add({users: [newUser]}, _.extend({}, context.owner, {include: 'roles'})) +// .then(function (response) { +// checkAddResponse(response); +// response.users[0].id.should.eql(8); +// response.users[0].roles[0].name.should.equal('Editor'); +// done(); +// }).catch(done); +// }); +// it('Can add an Author', function (done) { +// // Can add author +// newUser.roles = [roleIdFor.author]; +// UserAPI.add({users: [newUser]}, _.extend({}, context.owner, {include: 'roles'})) +// .then(function (response) { +// checkAddResponse(response); +// response.users[0].id.should.eql(8); +// response.users[0].roles[0].name.should.equal('Author'); +// done(); +// }).catch(done); +// }); +// }); +// +// describe('Admin', function () { +// it('CANNOT add an Owner', function (done) { +// newUser.roles = [roleIdFor.owner]; +// // Admin cannot add owner +// UserAPI.add({users: [newUser]}, _.extend({}, context.admin, {include: 'roles'})) +// .then(function () { +// done(new Error('Admin should not be able to add an owner')); +// }).catch(function (error) { +// error.type.should.eql('NoPermissionError'); +// done(); +// }); +// }); +// it('Can add an Admin', function (done) { +// // Can add admin +// newUser.roles = [roleIdFor.admin]; +// UserAPI.add({users: [newUser]}, _.extend({}, context.admin, {include: 'roles'})) +// .then(function (response) { +// checkAddResponse(response); +// response.users[0].id.should.eql(8); +// response.users[0].roles[0].name.should.equal('Administrator'); +// done(); +// }).catch(done); +// }); +// +// it('Can add an Editor', function (done) { +// // Can add editor +// newUser.roles = [roleIdFor.editor]; +// UserAPI.add({users: [newUser]}, _.extend({}, context.admin, {include: 'roles'})) +// .then(function (response) { +// checkAddResponse(response); +// response.users[0].id.should.eql(8); +// response.users[0].roles[0].name.should.equal('Editor'); +// done(); +// }).catch(done); +// }); +// +// it('Can add an Author', function (done) { +// // Can add author +// newUser.roles = [roleIdFor.author]; +// UserAPI.add({users: [newUser]}, _.extend({}, context.admin, {include: 'roles'})) +// .then(function (response) { +// checkAddResponse(response); +// response.users[0].id.should.eql(8); +// response.users[0].roles[0].name.should.equal('Author'); +// done(); +// }).catch(done); +// }); +// }); +// +// describe('Editor', function () { +// it('CANNOT add an Owner', function (done) { +// newUser.roles = [roleIdFor.owner]; +// // Editor cannot add owner +// UserAPI.add({users: [newUser]}, _.extend({}, context.editor, {include: 'roles'})) +// .then(function () { +// done(new Error('Editor should not be able to add an owner')); +// }).catch(function (error) { +// error.type.should.eql('NoPermissionError'); +// done(); +// }); +// }); +// +// it('Can add an Author', function (done) { +// newUser.roles = [roleIdFor.author]; +// UserAPI.add({users: [newUser]}, _.extend({}, context.editor, {include: 'roles'})) +// .then(function (response) { +// checkAddResponse(response); +// response.users[0].id.should.eql(8); +// response.users[0].roles[0].name.should.equal('Author'); +// done(); +// }).catch(done); +// }); +// }); +// +// describe('Author', function () { +// it('CANNOT add an Owner', function (done) { +// newUser.roles = [roleIdFor.owner]; +// // Admin cannot add owner +// UserAPI.add({users: [newUser]}, _.extend({}, context.author, {include: 'roles'})) +// .then(function () { +// done(new Error('Author should not be able to add an owner')); +// }).catch(function (error) { +// error.type.should.eql('NoPermissionError'); +// done(); +// }); +// }); +// +// it('CANNOT add an Author', function (done) { +// newUser.roles = [roleIdFor.author]; +// UserAPI.add({users: [newUser]}, _.extend({}, context.author, {include: 'roles'})) +// .then(function () { +// done(new Error('Author should not be able to add an author')); +// }).catch(function (error) { +// error.type.should.eql('NoPermissionError'); +// done(); +// }); +// }); +// }); +// }); - it('dateTime fields are returned as Date objects', function (done) { - var userData = testUtils.DataGenerator.forModel.users[0]; - - UserModel.check({ email: userData.email, password: userData.password }).then(function (user) { - return UserAPI.read({ id: user.id }); - }).then(function (response) { - response.users[0].created_at.should.be.an.instanceof(Date); - response.users[0].updated_at.should.be.an.instanceof(Date); - response.users[0].last_login.should.be.an.instanceof(Date); - - done(); - }).catch(done); - }); - - it('Can browse (admin)', function (done) { - UserAPI.browse(testUtils.context.admin).then(function (response) { + describe('Destroy', function () { + function checkDestroyResponse(response) { should.exist(response); - testUtils.API.checkResponse(response, 'users'); should.exist(response.users); - response.users.should.have.length(4); - testUtils.API.checkResponse(response.users[0], 'user', ['roles']); - testUtils.API.checkResponse(response.users[1], 'user', ['roles']); - testUtils.API.checkResponse(response.users[2], 'user', ['roles']); - testUtils.API.checkResponse(response.users[3], 'user', ['roles']); - - done(); - }).catch(done); - }); - - it('Can browse (editor)', function (done) { - UserAPI.browse(testUtils.context.editor).then(function (response) { - should.exist(response); - testUtils.API.checkResponse(response, 'users'); - should.exist(response.users); - response.users.should.have.length(4); - testUtils.API.checkResponse(response.users[0], 'user', ['roles']); - testUtils.API.checkResponse(response.users[1], 'user', ['roles']); - testUtils.API.checkResponse(response.users[2], 'user', ['roles']); - testUtils.API.checkResponse(response.users[3], 'user', ['roles']); - done(); - }).catch(done); - }); - - it('Can browse (author)', function (done) { - UserAPI.browse(testUtils.context.author).then(function (response) { - should.exist(response); - testUtils.API.checkResponse(response, 'users'); - should.exist(response.users); - response.users.should.have.length(4); - testUtils.API.checkResponse(response.users[0], 'user', ['roles']); - testUtils.API.checkResponse(response.users[1], 'user', ['roles']); - testUtils.API.checkResponse(response.users[2], 'user', ['roles']); - testUtils.API.checkResponse(response.users[3], 'user', ['roles']); - done(); - }).catch(done); - }); - - it('no-auth user cannot browse', function (done) { - UserAPI.browse().then(function () { - done(new Error('Browse user is not denied without authentication.')); - }, function () { - done(); - }).catch(done); - }); - - it('Can read (admin)', function (done) { - UserAPI.read(_.extend(testUtils.context.admin, {id: 1})).then(function (response) { - should.exist(response); should.not.exist(response.meta); - should.exist(response.users); - response.users[0].id.should.eql(1); + response.users.should.have.length(1); testUtils.API.checkResponse(response.users[0], 'user', ['roles']); response.users[0].created_at.should.be.a.Date; + } - done(); - }).catch(done); - }); - it('Can read (editor)', function (done) { - UserAPI.read(_.extend(testUtils.context.editor, {id: 1})).then(function (response) { - should.exist(response); - should.not.exist(response.meta); - should.exist(response.users); - response.users[0].id.should.eql(1); - testUtils.API.checkResponse(response.users[0], 'user', ['roles']); - done(); - }).catch(done); - }); + describe('Owner', function () { + it('CANNOT destroy self', function (done) { + UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.owner})) + .then(function () { + done(new Error('Owner should not be able to delete itself')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); - it('Can read (author)', function (done) { - UserAPI.read(_.extend(testUtils.context.author, {id: 1})).then(function (response) { - should.exist(response); - should.not.exist(response.meta); - should.exist(response.users); - response.users[0].id.should.eql(1); - testUtils.API.checkResponse(response.users[0], 'user', ['roles']); - done(); - }).catch(done); - }); + it('Can destroy admin, editor, author', function (done) { + // Admin + UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.admin})) + .then(function (response) { + checkDestroyResponse(response); - it('no-auth can read', function (done) { - UserAPI.read({id: 1}).then(function (response) { - should.exist(response); - should.not.exist(response.meta); - should.exist(response.users); - response.users[0].id.should.eql(1); - testUtils.API.checkResponse(response.users[0], 'user', ['roles']); - done(); - }).catch(done); - }); + // Editor + return UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.editor})); + }).then(function (response) { + checkDestroyResponse(response); - it('Can edit (admin)', function (done) { - UserAPI.edit( - {users: [{name: 'Joe Blogger'}]}, _.extend(testUtils.context.admin, {id: 1}) - ).then(function (response) { - should.exist(response); - should.not.exist(response.meta); - should.exist(response.users); - response.users.should.have.length(1); - testUtils.API.checkResponse(response.users[0], 'user', ['roles']); - response.users[0].name.should.equal('Joe Blogger'); - response.users[0].updated_at.should.be.a.Date; - done(); - }).catch(done); - }); + // Author + return UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.author})); + }).then(function (response) { + checkDestroyResponse(response); - it('Can edit (editor)', function (done) { - UserAPI.edit( - {users: [{name: 'Joe Blogger'}]}, _.extend(testUtils.context.editor, {id: 1}) - ).then(function (response) { - should.exist(response); - should.not.exist(response.meta); - should.exist(response.users); - response.users.should.have.length(1); - testUtils.API.checkResponse(response.users[0], 'user', ['roles']); - response.users[0].name.should.eql('Joe Blogger'); + done(); + }).catch(done); + }); + }); - done(); - }).catch(done); - }); + describe('Admin', function () { + it('CANNOT destroy owner', function (done) { + UserAPI.destroy(_.extend({}, context.admin, {id: userIdFor.owner})) + .then(function () { + done(new Error('Admin should not be able to delete owner')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); + + it('Can destroy admin, editor, author', function (done) { + // Admin + UserAPI.destroy(_.extend({}, context.admin, {id: userIdFor.admin2})) + .then(function (response) { + checkDestroyResponse(response); + + // Editor + return UserAPI.destroy(_.extend({}, context.admin, {id: userIdFor.editor2})); + }).then(function (response) { + checkDestroyResponse(response); + + // Author + return UserAPI.destroy(_.extend({}, context.admin, {id: userIdFor.author2})); + }).then(function (response) { + checkDestroyResponse(response); + + done(); + }).catch(done); + }); + }); + + describe('Editor', function () { + it('CANNOT destroy owner', function (done) { + UserAPI.destroy(_.extend({}, context.editor, {id: userIdFor.owner})) + .then(function () { + done(new Error('Editor should not be able to delete owner')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); + + it('CANNOT destroy admin', function (done) { + UserAPI.destroy(_.extend({}, context.editor, {id: userIdFor.admin})) + .then(function () { + done(new Error('Editor should not be able to delete admin')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); + + it('CANNOT destroy other editor', function (done) { + UserAPI.destroy(_.extend({}, context.editor, {id: userIdFor.editor2})) + .then(function () { + done(new Error('Editor should not be able to delete other editor')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); + + it('Can destroy self', function (done) { + UserAPI.destroy(_.extend({}, context.editor, {id: userIdFor.editor})) + .then(function (response) { + checkDestroyResponse(response); + done(); + }).catch(done); + }); + + it('Can destroy author', function (done) { + UserAPI.destroy(_.extend({}, context.editor, {id: userIdFor.author})) + .then(function (response) { + checkDestroyResponse(response); + done(); + }).catch(done); + }); + + }); + + describe('Author', function () { + it('CANNOT destroy owner', function (done) { + UserAPI.destroy(_.extend({}, context.author, {id: userIdFor.owner})) + .then(function () { + done(new Error('Author should not be able to delete owner')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); + + it('CANNOT destroy admin', function (done) { + UserAPI.destroy(_.extend({}, context.author, {id: userIdFor.admin})) + .then(function () { + done(new Error('Author should not be able to delete admin')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); + + it('CANNOT destroy editor', function (done) { + UserAPI.destroy(_.extend({}, context.author, {id: userIdFor.editor})) + .then(function () { + done(new Error('Author should not be able to delete editor')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); + + it('CANNOT destroy other author', function (done) { + UserAPI.destroy(_.extend({}, context.author, {id: userIdFor.author2})) + .then(function () { + done(new Error('Author should not be able to delete other author')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); + + it('CANNOT destroy self', function (done) { + UserAPI.destroy(_.extend({}, context.author, {id: userIdFor.author})) + .then(function () { + done(new Error('Author should not be able to delete self')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); - it('Can edit only self (author)', function (done) { - // Test author cannot edit admin user - UserAPI.edit( - {users: [{name: 'Joe Blogger'}]}, _.extend(testUtils.context.author, {id: 1}) - ).then(function () { - done(new Error('Author should not be able to edit account which is not their own')); - }).catch(function (error) { - error.type.should.eql('NoPermissionError'); - }).finally(function () { - // Next test that author CAN edit self - return UserAPI.edit( - {users: [{name: 'Timothy Bogendath'}]}, _.extend(testUtils.context.author, {id: 4}) - ).then(function (response) { - should.exist(response); - should.not.exist(response.meta); - should.exist(response.users); - response.users.should.have.length(1); - testUtils.API.checkResponse(response.users[0], 'user', ['roles']); - response.users[0].name.should.eql('Timothy Bogendath'); - done(); - }).catch(done); }); }); - it('can\'t transfer ownership (admin)', function (done) { - // transfer ownership to user id: 2 - UserAPI.edit( - {users: [{name: 'Joe Blogger', roles:[4]}]}, _.extend(testUtils.context.admin, {id: 2}) - ).then(function () { - done(new Error('Admin is not dienied transferring ownership.')); - }, function () { - done(); - }).catch(done); - }); +// describe('Edit and assign role', function () { +// var newName = 'Jo McBlogger'; +// +// function checkEditResponse(response) { +// should.exist(response); +// should.not.exist(response.meta); +// should.exist(response.users); +// response.users.should.have.length(1); +// testUtils.API.checkResponse(response.users[0], 'user', ['roles']); +// response.users[0].name.should.equal(newName); +// response.users[0].updated_at.should.be.a.Date; +// } +// +// describe('Owner', function () { +// it('Can assign Admin role', function (done) { +// var options = _.extend({}, context.owner, {id: userIdFor.author}, {include: 'roles'}); +// UserAPI.read(options).then(function (response) { +// response.users[0].id.should.equal(userIdFor.author); +// response.users[0].roles[0].name.should.equal('Author'); +// +// return UserAPI.edit( +// {users: [ +// {name: newName, roles: [roleIdFor.admin]} +// ]}, +// options +// ).then(function (response) { +// checkEditResponse(response); +// response.users[0].id.should.equal(userIdFor.author); +// response.users[0].roles[0].name.should.equal('Administrator'); +// +// done(); +// }).catch(done); +// }); +// }); +// +// it('Can assign Editor role', function (done) { +// var options = _.extend({}, context.owner, {id: userIdFor.admin}, {include: 'roles'}); +// UserAPI.read(options).then(function (response) { +// response.users[0].id.should.equal(userIdFor.admin); +// response.users[0].roles[0].name.should.equal('Administrator'); +// +// return UserAPI.edit( +// {users: [ +// {name: newName, roles: [roleIdFor.editor]} +// ]}, +// options +// ).then(function (response) { +// checkEditResponse(response); +// response.users[0].id.should.equal(userIdFor.admin); +// response.users[0].roles[0].name.should.equal('Editor'); +// +// done(); +// }).catch(done); +// }); +// }); +// +// it('Can assign Author role', function (done) { +// var options = _.extend({}, context.owner, {id: userIdFor.admin}, {include: 'roles'}); +// UserAPI.read(options).then(function (response) { +// response.users[0].id.should.equal(userIdFor.admin); +// response.users[0].roles[0].name.should.equal('Administrator'); +// +// return UserAPI.edit( +// {users: [ +// {name: newName, roles: [roleIdFor.author]} +// ]}, +// options +// ).then(function (response) { +// checkEditResponse(response); +// response.users[0].id.should.equal(userIdFor.admin); +// response.users[0].roles[0].name.should.equal('Author'); +// +// done(); +// }).catch(done); +// }); +// }); +// }); +// +// describe('Admin', function () { +// it('Can assign Admin role', function (done) { +// var options = _.extend({}, context.admin, {id: userIdFor.author}, {include: 'roles'}); +// UserAPI.read(options).then(function (response) { +// response.users[0].id.should.equal(userIdFor.author); +// response.users[0].roles[0].name.should.equal('Author'); +// +// return UserAPI.edit( +// {users: [ +// {name: newName, roles: [roleIdFor.admin]} +// ]}, +// options +// ).then(function (response) { +// checkEditResponse(response); +// response.users[0].id.should.equal(userIdFor.author); +// response.users[0].roles[0].name.should.equal('Administrator'); +// +// done(); +// }).catch(done); +// }); +// }); +// +// it('Can assign Editor role', function (done) { +// var options = _.extend({}, context.admin, {id: userIdFor.author}, {include: 'roles'}); +// UserAPI.read(options).then(function (response) { +// response.users[0].id.should.equal(userIdFor.author); +// response.users[0].roles[0].name.should.equal('Author'); +// +// return UserAPI.edit( +// {users: [ +// {name: newName, roles: [roleIdFor.editor]} +// ]}, +// options +// ).then(function (response) { +// checkEditResponse(response); +// response.users[0].id.should.equal(userIdFor.author); +// response.users[0].roles[0].name.should.equal('Editor'); +// +// done(); +// }).catch(done); +// }); +// }); +// +// it('Can assign Author role', function (done) { +// var options = _.extend({}, context.admin, {id: userIdFor.editor}, {include: 'roles'}); +// UserAPI.read(options).then(function (response) { +// response.users[0].id.should.equal(userIdFor.editor); +// response.users[0].roles[0].name.should.equal('Editor'); +// +// return UserAPI.edit( +// {users: [ +// {name: newName, roles: [roleIdFor.author]} +// ]}, +// options +// ).then(function (response) { +// checkEditResponse(response); +// response.users[0].id.should.equal(userIdFor.editor); +// response.users[0].roles[0].name.should.equal('Author'); +// +// done(); +// }).catch(done); +// }); +// }); +// }); +// +// describe('Editor', function () { +// it('Can assign author role to author', function (done) { +// UserAPI.edit( +// {users: [ +// {name: newName, roles: [roleIdFor.author]} +// ]}, _.extend({}, context.editor, {id: userIdFor.author2}, {include: 'roles'}) +// ).then(function (response) { +// checkEditResponse(response); +// response.users[0].id.should.equal(userIdFor.author2); +// response.users[0].roles[0].name.should.equal('Author'); +// +// done(); +// }).catch(done); +// }); +// +// it('CANNOT assign author role to self', function (done) { +// UserAPI.edit( +// {users: [ +// {name: newName, roles: [roleIdFor.author]} +// ]}, _.extend({}, context.editor, {id: userIdFor.editor}, {include: 'roles'}) +// ).then(function (response) { +// done(new Error('Editor should not be able to upgrade their role')); +// }, function (error) { +// error.type.should.eql('NoPermissionError'); +// done(); +// }).catch(done); +// }); +// +// it('CANNOT assign author role to other Editor', function (done) { +// UserAPI.edit( +// {users: [ +// {name: newName, roles: [roleIdFor.author]} +// ]}, _.extend({}, context.editor, {id: userIdFor.editor2}, {include: 'roles'}) +// ).then(function (response) { +// done(new Error('Editor should not be able to change the roles of other editors')); +// }, function (error) { +// error.type.should.eql('NoPermissionError'); +// done(); +// }).catch(done); +// }); +// +// it('CANNOT assign author role to admin', function (done) { +// UserAPI.edit( +// {users: [ +// {name: newName, roles: [roleIdFor.author]} +// ]}, _.extend({}, context.editor, {id: userIdFor.admin}, {include: 'roles'}) +// ).then(function (response) { +// done(new Error('Editor should not be able to change the roles of admins')); +// }, function (error) { +// error.type.should.eql('NoPermissionError'); +// done(); +// }).catch(done); +// }); +// it('CANNOT assign admin role to author', function (done) { +// UserAPI.edit( +// {users: [ +// {name: newName, roles: [roleIdFor.admin]} +// ]}, _.extend({}, context.editor, {id: userIdFor.author}, {include: 'roles'}) +// ).then(function (response) { +// done(new Error('Editor should not be able to upgrade the role of authors')); +// }, function (error) { +// error.type.should.eql('NoPermissionError'); +// done(); +// }).catch(done); +// }); +// }); +// +// describe('Author', function () { +// it('CANNOT assign higher role to self', function (done) { +// UserAPI.edit( +// {users: [ +// {name: newName, roles: [roleIdFor.editor]} +// ]}, _.extend({}, context.author, {id: userIdFor.author}, {include: 'roles'}) +// ).then(function (response) { +// done(new Error('Author should not be able to upgrade their role')); +// }, function (error) { +// error.type.should.eql('NoPermissionError'); +// done(); +// }).catch(done); +// }); +// }); +// }); - it('can\'t transfer ownership (editor)', function (done) { - // transfer ownership to user id: 2 - UserAPI.edit( - {users: [{name: 'Joe Blogger', roles:[4]}]}, _.extend(testUtils.context.editor, {id: 2}) - ).then(function () { - done(new Error('Admin is not dienied transferring ownership.')); - }, function () { - done(); - }).catch(done); - }); + describe('Transfer ownership', function () { +// Temporarily commenting this test out until #3426 is fixed +// it('Owner can transfer ownership', function (done) { +// // transfer ownership to admin user id:2 +// UserAPI.edit( +// {users: [ +// {name: 'Joe Blogger', roles: [roleIdFor.owner]} +// ]}, _.extend({}, context.owner, {id: userIdFor.admin}) +// ).then(function (response) { +// should.exist(response); +// should.not.exist(response.meta); +// should.exist(response.users); +// response.users.should.have.length(1); +// testUtils.API.checkResponse(response.users[0], 'user', ['roles']); +// response.users[0].name.should.equal('Joe Blogger'); +// response.users[0].id.should.equal(2); +// response.users[0].roles[0].should.equal(4); +// response.users[0].updated_at.should.be.a.Date; +// done(); +// }).catch(done); +// }); - it('can\'t transfer ownership (author)', function (done) { - // transfer ownership to user id: 2 - UserAPI.edit( - {users: [{name: 'Joe Blogger', roles:[4]}]}, _.extend(testUtils.context.author, {id: 2}) - ).then(function () { - done(new Error('Admin is not dienied transferring ownership.')); - }, function () { - done(); - }).catch(done); - }); + it('Owner CANNOT downgrade own role', function (done) { + // Cannot change own role to admin + UserAPI.edit( + {users: [ + {name: 'Joe Blogger', roles: [roleIdFor.admin]} + ]}, _.extend({}, context.owner, {id: userIdFor.owner}) + ).then(function (response) { + done(new Error('Owner should not be able to downgrade their role')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); - it('can transfer ownership (owner)', function (done) { - // transfer ownership to user id: 2 - UserAPI.edit( - {users: [{name: 'Joe Blogger', roles:[4]}]}, _.extend(testUtils.context.owner, {id: 2}) - ).then(function (response) { - should.exist(response); - should.not.exist(response.meta); - should.exist(response.users); - response.users.should.have.length(1); - testUtils.API.checkResponse(response.users[0], 'user', ['roles']); - response.users[0].name.should.equal('Joe Blogger'); - response.users[0].id.should.equal(2); - response.users[0].roles[0].should.equal(4); - response.users[0].updated_at.should.be.a.Date; - done(); - }).catch(done); + it('Admin CANNOT transfer ownership', function (done) { + // transfer ownership to user id: 2 + UserAPI.edit( + {users: [ + {name: 'Joe Blogger', roles: [roleIdFor.owner]} + ]}, _.extend({}, context.admin, {id: userIdFor.admin}) + ).then(function () { + done(new Error('Admin is not denied transferring ownership.')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); + + it('Editor CANNOT transfer ownership', function (done) { + // transfer ownership to user id: 2 + UserAPI.edit( + {users: [ + {name: 'Joe Blogger', roles: [roleIdFor.owner]} + ]}, _.extend({}, context.editor, {id: userIdFor.admin}) + ).then(function () { + done(new Error('Admin is not denied transferring ownership.')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); + + it('Author CANNOT transfer ownership', function (done) { + // transfer ownership to user id: 2 + UserAPI.edit( + {users: [ + {name: 'Joe Blogger', roles: [roleIdFor.owner]} + ]}, _.extend({}, context.author, {id: userIdFor.admin}) + ).then(function () { + done(new Error('Admin is not denied transferring ownership.')); + }).catch(function (error) { + error.type.should.eql('NoPermissionError'); + done(); + }); + }); }); }); \ No newline at end of file diff --git a/core/test/utils/fixtures/data-generator.js b/core/test/utils/fixtures/data-generator.js index 591d48186f..203e24aee4 100644 --- a/core/test/utils/fixtures/data-generator.js +++ b/core/test/utils/fixtures/data-generator.js @@ -158,20 +158,20 @@ DataGenerator.Content = { roles: [ { - "name": "Administrator", - "description": "Administrators" + name: 'Administrator', + description: 'Administrators' }, { - "name": "Editor", - "description": "Editors" + name: 'Editor', + description: 'Editors' }, { - "name": "Author", - "description": "Authors" + name: 'Author', + description: 'Authors' }, { - "name": "Owner", - "description": "Blog Owner" + name: 'Owner', + description: 'Blog Owner' } ], diff --git a/core/test/utils/index.js b/core/test/utils/index.js index 1ff51976d9..6650166f3d 100644 --- a/core/test/utils/index.js +++ b/core/test/utils/index.js @@ -141,6 +141,27 @@ fixtures = { }); }, + createExtraUsers: function createExtraUsers() { + var knex = config.database.knex, + // grab 3 more users + extraUsers = DataGenerator.Content.users.slice(2, 5); + + extraUsers = _.map(extraUsers, function (user) { + return DataGenerator.forKnex.createUser(_.extend({}, user, { + email: 'a' + user.email, + slug: 'a' + user.slug + })); + }); + + return knex('users').insert(extraUsers).then(function () { + return knex('roles_users').insert([ + { user_id: 5, role_id: 1}, + { user_id: 6, role_id: 2}, + { user_id: 7, role_id: 3} + ]); + }); + }, + insertOne: function insertOne(obj, fn) { var knex = config.database.knex; return knex(obj) @@ -164,7 +185,7 @@ fixtures = { try { data = JSON.parse(fileContents); } catch (e) { - return when.reject(new Error("Failed to parse the file")); + return when.reject(new Error('Failed to parse the file')); } return data; @@ -244,6 +265,7 @@ toDoList = { return settings.populateDefaults().then(function () { return SettingsAPI.updateSettingsCache(); }); }, 'users:roles': function createUsersWithRoles() { return fixtures.createUsersWithRoles(); }, + 'users': function createExtraUsers() { return fixtures.createExtraUsers(); }, 'owner': function insertOwnerUser() { return fixtures.insertOwnerUser(); }, 'owner:pre': function initOwnerUser() { return fixtures.initOwnerUser(); }, 'owner:post': function overrideOwnerUser() { return fixtures.overrideOwnerUser(); }, @@ -376,11 +398,31 @@ module.exports = { fork: fork, + // Helpers to make it easier to write tests which are easy to read context: { internal: {context: {internal: true}}, owner: {context: {user: 1}}, admin: {context: {user: 2}}, editor: {context: {user: 3}}, author: {context: {user: 4}} + }, + users: { + ids: { + owner: 1, + admin: 2, + editor: 3, + author: 4, + admin2: 5, + editor2: 6, + author2: 7 + } + }, + roles: { + ids: { + owner: 4, + admin: 1, + editor: 2, + author: 3 + } } };