From 80f9023020791c142481dd452f5a65885dbbd865 Mon Sep 17 00:00:00 2001 From: Gabor Javorszky Date: Tue, 15 Jul 2014 16:22:06 +0100 Subject: [PATCH] Added /roles/ API endpoint Closes #3196 * adds `/roles/` endpoint * is given the current user as context * wraps everything in a canthis.browse.role * gets all the available roles (should "Owner" be filtered out?) * optional parameter: `permission=assign`. Gets all roles authenticated user could assign * if we're not signed in, gives a "please sign in" (standard) error * if we're signed in, but user is not in the context, gives a "there was no user in the context" error * if the user is an "Author", gives a "there are no available roles to assign" error * implemented hacky filter because when.js produces heisenbugs past 3.2.3 (when.filter not available) * added extra fixtures to `permissions.json`. Might need a migration. Caveats: * there are no tests * for some reason the setup functional test was failing for me locally --- core/server/api/index.js | 2 + core/server/api/roles.js | 60 +++++++++++++++++++ .../fixtures/permissions/permissions.json | 22 +++++-- core/server/models/role.js | 46 +++++++++++++- core/server/routes/api.js | 3 + 5 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 core/server/api/roles.js diff --git a/core/server/api/index.js b/core/server/api/index.js index e0ee52a633b..056d6a97904 100644 --- a/core/server/api/index.js +++ b/core/server/api/index.js @@ -12,6 +12,7 @@ var _ = require('lodash'), mail = require('./mail'), notifications = require('./notifications'), posts = require('./posts'), + roles = require('./roles'), settings = require('./settings'), tags = require('./tags'), themes = require('./themes'), @@ -280,6 +281,7 @@ module.exports = { mail: mail, notifications: notifications, posts: posts, + roles: roles, settings: settings, tags: tags, themes: themes, diff --git a/core/server/api/roles.js b/core/server/api/roles.js new file mode 100644 index 00000000000..f205e772cd9 --- /dev/null +++ b/core/server/api/roles.js @@ -0,0 +1,60 @@ +// # Posts API +// RESTful API for the Post resource +var when = require('when'), + _ = require('lodash'), + canThis = require('../permissions').canThis, + dataProvider = require('../models'), + errors = require('../errors'), + + roles; + +/** + * ## Roles API Methods + * + * **See:** [API Methods](index.js.html#api%20methods) + */ +roles = { + /** + * ### Browse + * Find all roles + * + * Will return all roles that the current user is able to assign + * + * + * @public + * @param {{context, page, limit, status, staticPages, tag}} options (optional) + * @returns {Promise(Roles)} Roles Collection + */ + browse: function browse(options) { + var permissionMap = []; + options = options || {}; + + return canThis(options.context).browse.role().then(function () { + return dataProvider.Role.findAll(options).then(function (foundRoles) { + if (options.permissions === 'assign') { + // Hacky implementation of filtering because when.filter is only available in when 3.4.0, + // but that's buggy and kills other tests and introduces Heisenbugs. Until we turn everything + // to Bluebird, this works. Sorry. + // TODO: replace with better filter when bluebird lands + _.each(foundRoles.toJSON(), function (role) { + permissionMap.push(canThis(options.context).assign.role(role).then(function () { + return role; + }, function () { + return null; + })); + }); + + return when.all(permissionMap).then(function (resolved) { + return { roles: _.filter(resolved, function (role) { + return role !== null; + }) }; + }).catch(errors.logAndThrowError); + } + return { roles: foundRoles.toJSON() }; + }); + }) + .catch(errors.logAndThrowError); + } +}; + +module.exports = roles; diff --git a/core/server/data/fixtures/permissions/permissions.json b/core/server/data/fixtures/permissions/permissions.json index 686f1d73e1f..a13b5276d53 100644 --- a/core/server/data/fixtures/permissions/permissions.json +++ b/core/server/data/fixtures/permissions/permissions.json @@ -129,6 +129,16 @@ "name": "Delete users", "action_type": "destroy" } + ], + "role": [ + { + "name": "Assign a role", + "action_type": "assign" + }, + { + "name": "Browse roles", + "action_type": "browse" + } ] }, "permissions_roles": { @@ -141,22 +151,26 @@ "slug": "all", "tag": "all", "theme": "all", - "user": "all" + "user": "all", + "role": "all" }, "Editor": { "post": "all", "setting": ["browse", "read"], "slug": "all", "tag": "all", - "user": "all" - + "user": "all", + "setting": ["browse", "read"], + "role": "all" }, "Author": { "post": ["browse", "read", "add"], "setting": ["browse", "read"], "slug": "all", "tag": ["browse", "read", "add"], - "user": ["browse", "read"] + "user": ["browse", "read"], + "setting": ["browse", "read"], + "role": ["browse"] } } } \ No newline at end of file diff --git a/core/server/models/role.js b/core/server/models/role.js index a0cc50f7cc4..924f4c546c1 100644 --- a/core/server/models/role.js +++ b/core/server/models/role.js @@ -1,4 +1,7 @@ -var ghostBookshelf = require('./base'), +var _ = require('lodash'), + errors = require('../errors'), + ghostBookshelf = require('./base'), + when = require('when'), Role, Roles; @@ -34,6 +37,47 @@ Role = ghostBookshelf.Model.extend({ } return options; + }, + + + permissable: function (roleModelOrId, context, loadedPermissions, hasUserPermission, hasAppPermission) { + var self = this, + checkAgainst = [], + origArgs; + + // If we passed in an id instead of a model, get the model + // then check the permissions + if (_.isNumber(roleModelOrId) || _.isString(roleModelOrId)) { + // Grab the original args without the first one + origArgs = _.toArray(arguments).slice(1); + // Get the actual post model + return this.findOne({id: roleModelOrId, status: 'all'}).then(function (foundRoleModel) { + // Build up the original args but substitute with actual model + var newArgs = [foundRoleModel].concat(origArgs); + + return self.permissable.apply(self, newArgs); + }, errors.logAndThrowError); + } + + switch (loadedPermissions.user) { + case 'Owner': + case 'Administrator': + checkAgainst = ['Administrator', 'Editor', 'Author']; + break; + case 'Editor': + checkAgainst = ['Editor', '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; + } + + if (hasUserPermission && hasAppPermission) { + return when.resolve(); + } + return when.reject(); } }); diff --git a/core/server/routes/api.js b/core/server/routes/api.js index 4d549c5566f..b1a8cf6532b 100644 --- a/core/server/routes/api.js +++ b/core/server/routes/api.js @@ -34,6 +34,9 @@ apiRoutes = function (middleware) { // ## Tags router.get('/tags', api.http(api.tags.browse)); + // ## Roles + router.get('/roles/', api.http(api.roles.browse)); + // ## Slugs router.get('/slugs/:type/:name', api.http(api.slugs.generate));