0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Contributor Role (#9315)

closes #9314 

* added fixtures for contributor role
* update post api tests to prevent contributor publishing post
* update permissible function in role/user model
* fix additional author code in invites
* update contributor role migration for knex-migrator v3
* fix paths in contrib migration
* ensure contributors can't edit or delete published posts, fix routing tests [ci skip]
* update db fixtures hash
* strip tags from post if contributor
* cleanup post permissible function
* excludedAttrs to ignore tag updates for now (might be removed later)
* ensure contributors can't edit another's post
* migration script for 1.21
This commit is contained in:
Austin Burdine 2018-02-07 10:46:22 +01:00 committed by Katharina Irrgang
parent a274d61a8c
commit 777247cbc7
28 changed files with 1448 additions and 227 deletions

View file

@ -192,9 +192,9 @@ invites = {
allowed = [];
if (loggedInUserRole === 'Owner' || loggedInUserRole === 'Administrator') {
allowed = ['Administrator', 'Editor', 'Author'];
allowed = ['Administrator', 'Editor', 'Author', 'Contributor'];
} else if (loggedInUserRole === 'Editor') {
allowed = ['Author'];
allowed = ['Author', 'Contributor'];
}
if (allowed.indexOf(roleToInvite.get('name')) === -1) {

View file

@ -10,7 +10,7 @@ var Promise = require('bluebird'),
allowedIncludes = [
'created_by', 'updated_by', 'published_by', 'author', 'tags', 'fields'
],
unsafeAttrs = ['author_id'],
unsafeAttrs = ['author_id', 'status'],
posts;
/**

View file

@ -209,7 +209,21 @@ utils = {
var unsafeAttrObject = unsafeAttrNames && _.has(options, 'data.[' + docName + '][0]') ? _.pick(options.data[docName][0], unsafeAttrNames) : {},
permsPromise = permissions.canThis(options.context)[method][singular](options.id, unsafeAttrObject);
return permsPromise.then(function permissionGranted() {
return permsPromise.then(function permissionGranted(result) {
/*
* Allow the permissions function to return a list of excluded attributes.
* If it does, omit those attrs from the data passed through
*
* NOTE: excludedAttrs differ from unsafeAttrs in that they're determined by the model's permissible function,
* and the attributes are simply excluded rather than throwing a NoPermission exception
*
* TODO: This is currently only needed because of the posts model and the contributor role. Once we extend the
* contributor role to be able to edit existing tags, this concept can be removed.
*/
if (result && result.excludedAttrs && _.has(options, 'data.[' + docName + '][0]')) {
options.data[docName][0] = _.omit(options.data[docName][0], result.excludedAttrs);
}
return options;
}).catch(function handleNoPermissionError(err) {
if (err instanceof common.errors.NoPermissionError) {

View file

@ -0,0 +1,60 @@
'use strict';
const merge = require('lodash/merge'),
utils = require('../../../schema/fixtures/utils'),
models = require('../../../../models'),
permissions = require('../../../../services/permissions'),
logging = require('../../../../lib/common/logging'),
_private = {};
_private.addRole = function addRole(options) {
const contributorRole = utils.findModelFixtureEntry('Role', {name: 'Contributor'}),
message = 'Adding "Contributor" role to roles table';
return models.Role.findOne({name: contributorRole.name}, options)
.then((role) => {
if (!role) {
logging.info(message);
return utils.addFixturesForModel({name: 'Role', entries: [contributorRole]}, options);
}
logging.warn(message);
return Promise.resolve();
});
};
_private.addContributorPermissions = function getPermissions(options) {
const relations = utils.findRelationFixture('Role', 'Permission'),
message = 'Adding permissions_roles fixtures for the contributor role';
return utils.addFixturesForRelation({
from: relations.from,
to: relations.to,
entries: {
Contributor: relations.entries.Contributor
}
}, options).then((result) => {
if (result.done === result.expected) {
logging.info(message);
return;
}
logging.warn(`(${result.done}/${result.expected}) ${message}`);
});
};
module.exports.config = {
transaction: true
};
module.exports.up = function addContributorRole(options) {
var localOptions = merge({
context: {internal: true}
}, options);
return _private.addRole(localOptions).then(() => {
return _private.addContributorPermissions(localOptions);
}).then(() => {
return permissions.init(localOptions);
});
};

View file

@ -158,6 +158,10 @@
"name": "Author",
"description": "Authors"
},
{
"name": "Contributor",
"description": "Contributors"
},
{
"name": "Owner",
"description": "Blog Owner"
@ -507,6 +511,17 @@
"client": "all",
"subscriber": ["add"],
"theme": ["browse"]
},
"Contributor": {
"post": ["browse", "read", "add"],
"setting": ["browse", "read"],
"slug": "all",
"tag": ["browse", "read"],
"user": ["browse", "read"],
"role": ["browse"],
"client": "all",
"subscriber": ["add"],
"theme": ["browse"]
}
}
},

View file

@ -264,6 +264,7 @@ module.exports = {
addFixturesForRelation: addFixturesForRelation,
findModelFixtureEntry: findModelFixtureEntry,
findModelFixtures: findModelFixtures,
findRelationFixture: findRelationFixture,
findPermissionRelationsForObject: findPermissionRelationsForObject,
removeFixturesForModel: removeFixturesForModel
};

View file

@ -655,7 +655,8 @@ Post = ghostBookshelf.Model.extend({
permissible: function permissible(postModelOrId, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasAppPermission) {
var self = this,
postModel = postModelOrId,
origArgs;
result = {},
origArgs, isContributor, isAuthor, isEdit, isAdd, isDestroy;
// If we passed in an id instead of a model, get the model
// then check the permissions
@ -676,24 +677,58 @@ Post = ghostBookshelf.Model.extend({
return unsafeAttrs[attr] && unsafeAttrs[attr] !== postModel.get(attr);
}
function actorIsAuthor(loadedPermissions) {
return loadedPermissions.user && _.some(loadedPermissions.user.roles, {name: 'Author'});
}
function isOwner() {
return unsafeAttrs.author_id && unsafeAttrs.author_id === context.user;
}
if (actorIsAuthor(loadedPermissions) && action === 'edit' && isChanging('author_id')) {
hasUserPermission = false;
} else if (actorIsAuthor(loadedPermissions) && action === 'add') {
function isCurrentOwner() {
return context.user === postModel.get('author_id');
}
function isPublished() {
return unsafeAttrs.status && unsafeAttrs.status !== 'draft';
}
function isDraft() {
return postModel.get('status') === 'draft';
}
isContributor = loadedPermissions.user && _.some(loadedPermissions.user.roles, {name: 'Contributor'});
isAuthor = loadedPermissions.user && _.some(loadedPermissions.user.roles, {name: 'Author'});
isEdit = (action === 'edit');
isAdd = (action === 'add');
isDestroy = (action === 'destroy');
if (isContributor && isEdit) {
// Only allow contributor edit if neither status or author id are changing, and the post is a draft post
hasUserPermission = !isChanging('status') && !isChanging('author_id') && isDraft() && isCurrentOwner();
} else if (isContributor && isAdd) {
// If adding, make sure it's a draft post and has the correct ownership
hasUserPermission = !isPublished() && isOwner();
} else if (isContributor && isDestroy) {
// If destroying, only allow contributor to destroy their own draft posts
hasUserPermission = isCurrentOwner() && isDraft();
} else if (isAuthor && isEdit) {
// Don't allow author to change author ids
hasUserPermission = isCurrentOwner() && !isChanging('author_id');
} else if (isAuthor && isAdd) {
// Make sure new post is authored by the current user
hasUserPermission = isOwner();
} else if (postModel) {
hasUserPermission = hasUserPermission || context.user === postModel.get('author_id');
hasUserPermission = hasUserPermission || isCurrentOwner();
}
if (isContributor) {
// Note: at the moment primary_tag is a computed field,
// meaning we don't add it to this list. However, if the primary_tag
// ever becomes a db field rather than a computed field, add it to this list
//
// TODO: once contribitors are able to edit existing tags, this can be removed
result.excludedAttrs = ['tags'];
}
if (hasUserPermission && hasAppPermission) {
return Promise.resolve();
return Promise.resolve(result);
}
return Promise.reject(new common.errors.NoPermissionError({message: common.i18n.t('errors.models.post.notEnoughPermission')}));

View file

@ -62,11 +62,11 @@ Role = ghostBookshelf.Model.extend({
if (action === 'assign' && loadedPermissions.user) {
if (_.some(loadedPermissions.user.roles, {name: 'Owner'})) {
checkAgainst = ['Owner', 'Administrator', 'Editor', 'Author'];
checkAgainst = ['Owner', 'Administrator', 'Editor', 'Author', 'Contributor'];
} else if (_.some(loadedPermissions.user.roles, {name: 'Administrator'})) {
checkAgainst = ['Administrator', 'Editor', 'Author'];
checkAgainst = ['Administrator', 'Editor', 'Author', 'Contributor'];
} else if (_.some(loadedPermissions.user.roles, {name: 'Editor'})) {
checkAgainst = ['Author'];
checkAgainst = ['Author', 'Contributor'];
}
// Role in the list of permissible roles

View file

@ -641,39 +641,31 @@ User = ghostBookshelf.Model.extend({
}
if (action === 'edit') {
// Owner can only be edited by owner
if (loadedPermissions.user && userModel.hasRole('Owner')) {
hasUserPermission = _.some(loadedPermissions.user.roles, {name: 'Owner'});
}
// Users with the role 'Editor' and 'Author' have complex permissions when the action === 'edit'
// Users with the role 'Editor', 'Author', and 'Contributor' have complex permissions when the action === 'edit'
// We now have all the info we need to construct the permissions
if (loadedPermissions.user && _.some(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 (loadedPermissions.user && _.some(loadedPermissions.user.roles, {name: 'Editor'})) {
if (context.user === userModel.get('id')) {
// 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');
hasUserPermission = true;
} else if (loadedPermissions.user && userModel.hasRole('Owner')) {
// Owner can only be edited by owner
hasUserPermission = loadedPermissions.user && _.some(loadedPermissions.user.roles, {name: 'Owner'});
} else if (loadedPermissions.user && _.some(loadedPermissions.user.roles, {name: 'Editor'})) {
// If the user we are trying to edit is an Author or Contributor, allow it
hasUserPermission = userModel.hasRole('Author') || userModel.hasRole('Contributor');
}
}
if (action === 'destroy') {
// Owner cannot be deleted EVER
if (loadedPermissions.user && userModel.hasRole('Owner')) {
if (userModel.hasRole('Owner')) {
return Promise.reject(new common.errors.NoPermissionError({message: common.i18n.t('errors.models.user.notEnoughPermission')}));
}
// Users with the role 'Editor' have complex permissions when the action === 'destroy'
if (loadedPermissions.user && _.some(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');
hasUserPermission = context.user === userModel.get('id') || userModel.hasRole('Author') || userModel.hasRole('Contributor');
}
}

View file

@ -1424,30 +1424,6 @@ describe('Post API', function () {
done();
});
});
it('CANNOT add post with other author ID', function (done) {
var post = {
title: 'Freshly added',
slug: 'freshly-added',
author_id: 1
};
request.post(testUtils.API.getApiQuery('posts/'))
.set('Authorization', 'Bearer ' + authorAccessToken)
.send({posts: [post]})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403)
.end(function (err, res) {
if (err) {
return done(err);
}
should.exist(res.body.errors);
res.body.errors[0].errorType.should.eql('NoPermissionError');
done();
});
});
});
describe('Edit', function () {
@ -1507,10 +1483,110 @@ describe('Post API', function () {
});
});
});
});
it('CANNOT change author of own post', function (done) {
request.get(testUtils.API.getApiQuery('posts/' + authorPostId + '/?include=tags'))
.set('Authorization', 'Bearer ' + authorAccessToken)
describe('Delete', function () {
it('can delete own published post', function (done) {
testUtils.createPost({
post: {
title: 'Author\'s test post for deletion',
slug: 'author-delete-post-published',
author_id: author.id,
status: 'published'
}
}).then(function (post) {
request.del(testUtils.API.getApiQuery('posts/' + post.id + '/'))
.set('Authorization', 'Bearer ' + authorAccessToken)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(204)
.end(function (err, res) {
if (err) {
return done(err);
}
res.body.should.be.empty();
done();
});
});
});
});
});
describe('As Contributor', function () {
var contributorAccessToken, contributor;
before(function () {
return ghost()
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
// create contributor
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({email: 'test+3@ghost.org'}),
role: testUtils.DataGenerator.Content.roles[4]
});
})
.then(function (_contributor) {
request.user = contributor = _contributor;
return testUtils.doAuth(request, 'posts');
})
.then(function (token) {
contributorAccessToken = token;
});
});
describe('Add', function () {
it('can add own post', function (done) {
var post = {
title: 'Freshly added',
slug: 'freshly-added',
author_id: contributor.id
};
request.post(testUtils.API.getApiQuery('posts/'))
.set('Authorization', 'Bearer ' + contributorAccessToken)
.send({posts: [post]})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.end(function (err, res) {
if (err) {
return done(err);
}
var postBody = res.body;
res.headers['x-cache-invalidate'].should.eql('/p/' + postBody.posts[0].uuid + '/');
should.exist(postBody);
testUtils.API.checkResponse(postBody.posts[0], 'post');
done();
});
});
});
describe('Edit', function () {
var contributorPostId;
before(function () {
return testUtils.createPost({
post: {
title: 'Contributor\'s test post',
slug: 'contributor-post',
author_id: contributor.id,
status: 'draft'
}
})
.then(function (post) {
contributorPostId = post.id;
});
});
it('can edit own post', function (done) {
request.get(testUtils.API.getApiQuery('posts/' + contributorPostId + '/?include=tags&status=draft'))
.set('Authorization', 'Bearer ' + contributorAccessToken)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
@ -1521,33 +1597,38 @@ describe('Post API', function () {
var jsonResponse = res.body,
changedTitle = 'My new Title',
changedAuthor = ObjectId.generate();
changedSlug = 'my-new-slug';
should.exist(jsonResponse.posts[0]);
jsonResponse.posts[0].title = changedTitle;
jsonResponse.posts[0].author = changedAuthor;
jsonResponse.posts[0].slug = changedSlug;
request.put(testUtils.API.getApiQuery('posts/' + authorPostId + '/'))
.set('Authorization', 'Bearer ' + authorAccessToken)
request.put(testUtils.API.getApiQuery('posts/' + contributorPostId + '/'))
.set('Authorization', 'Bearer ' + contributorAccessToken)
.send(jsonResponse)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.exist(res.body.errors);
res.body.errors[0].errorType.should.eql('NoPermissionError');
var putBody = res.body;
should.exist(putBody);
res.headers['x-cache-invalidate'].should.eql('/p/' + putBody.posts[0].uuid + '/');
putBody.posts[0].title.should.eql(changedTitle);
putBody.posts[0].slug.should.eql(changedSlug);
testUtils.API.checkResponse(putBody.posts[0], 'post');
done();
});
});
});
it('CANNOT become author of other post', function (done) {
request.get(testUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/?include=tags'))
.set('Authorization', 'Bearer ' + authorAccessToken)
it('CANNOT publish a post', function (done) {
request.get(testUtils.API.getApiQuery('posts/' + contributorPostId + '/?include=tags&status=draft'))
.set('Authorization', 'Bearer ' + contributorAccessToken)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
@ -1557,15 +1638,13 @@ describe('Post API', function () {
}
var jsonResponse = res.body,
changedTitle = 'My new Title',
changedAuthor = author.id;
changedStatus = 'published';
should.exist(jsonResponse.posts[0]);
jsonResponse.posts[0].title = changedTitle;
jsonResponse.posts[0].author = changedAuthor;
jsonResponse.posts[0].status = changedStatus;
request.put(testUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
.set('Authorization', 'Bearer ' + authorAccessToken)
request.put(testUtils.API.getApiQuery('posts/' + contributorPostId + '/'))
.set('Authorization', 'Bearer ' + contributorAccessToken)
.send(jsonResponse)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
@ -1582,5 +1661,57 @@ describe('Post API', function () {
});
});
});
describe('Delete', function () {
it('can delete own draft post', function (done) {
testUtils.createPost({
post: {
title: 'Contrbutor\'s test post for deletion',
slug: 'contributor-delete-post',
author_id: contributor.id,
status: 'draft'
}
}).then(function (post) {
request.del(testUtils.API.getApiQuery('posts/' + post.id + '/'))
.set('Authorization', 'Bearer ' + contributorAccessToken)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(204)
.end(function (err, res) {
if (err) {
return done(err);
}
res.body.should.be.empty();
done();
});
});
});
it('can\'t delete own published post', function (done) {
testUtils.createPost({
post: {
title: 'Contributor\'s test post for deletion',
slug: 'contributor-delete-post-published',
author_id: contributor.id,
status: 'published'
}
}).then(function (post) {
request.del(testUtils.API.getApiQuery('posts/' + post.id + '/'))
.set('Authorization', 'Bearer ' + contributorAccessToken)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403)
.end(function (err, res) {
if (err) {
return done(err);
}
should.exist(res.body.errors);
res.body.errors[0].errorType.should.eql('NoPermissionError');
done();
});
});
});
});
});
});

View file

@ -53,7 +53,7 @@ describe('DB API', function () {
});
});
it('delete all content is denied (editor, author & without authentication)', function () {
it('delete all content is denied (editor, author, contributor & without authentication)', function () {
return dbAPI.deleteAllContent(testUtils.context.editor).then(function () {
throw new Error('Delete all content is not denied for editor.');
}, function (error) {
@ -61,6 +61,11 @@ describe('DB API', function () {
return dbAPI.deleteAllContent(testUtils.context.author);
}).then(function () {
throw new Error('Delete all content is not denied for author.');
}, function (error) {
error.errorType.should.eql('NoPermissionError');
return dbAPI.deleteAllContent(testUtils.context.contributor);
}).then(function () {
throw new Error('Delete all content is not denied for contributor.');
}, function (error) {
error.errorType.should.eql('NoPermissionError');
return dbAPI.deleteAllContent();
@ -71,7 +76,7 @@ describe('DB API', function () {
});
});
it('export content is denied (editor, author & without authentication)', function () {
it('export content is denied (editor, author, contributor & without authentication)', function () {
return dbAPI.exportContent(testUtils.context.editor).then(function () {
throw new Error('Export content is not denied for editor.');
}, function (error) {
@ -79,6 +84,11 @@ describe('DB API', function () {
return dbAPI.exportContent(testUtils.context.author);
}).then(function () {
throw new Error('Export content is not denied for author.');
}, function (error) {
error.errorType.should.eql('NoPermissionError');
return dbAPI.exportContent(testUtils.context.contributor);
}).then(function () {
throw new Error('Export content is not denied for contributor.');
}, function (error) {
error.errorType.should.eql('NoPermissionError');
return dbAPI.exportContent();
@ -89,7 +99,7 @@ describe('DB API', function () {
});
});
it('import content is denied (editor, author & without authentication)', function () {
it('import content is denied (editor, author, contributor & without authentication)', function () {
var file = {
originalname: 'myFile.json',
path: '/my/path/myFile.json',
@ -103,6 +113,11 @@ describe('DB API', function () {
return dbAPI.importContent(_.extend(testUtils.context.author, file));
}).then(function () {
throw new Error('Import content is not denied for author.');
}, function (error) {
error.errorType.should.eql('NoPermissionError');
return dbAPI.importContent(_.extend(testUtils.context.contributor, file));
}).then(function () {
throw new Error('Import content is not denied for contributor.');
}, function (error) {
error.errorType.should.eql('NoPermissionError');
return dbAPI.importContent(file);

View file

@ -51,6 +51,17 @@ describe('Invites API', function () {
}).catch(done);
});
it('add invite 3', function (done) {
InvitesAPI.add({
invites: [{email: 'test3@example.com', role_id: testUtils.roles.ids.contributor}]
}, testUtils.context.owner)
.then(function (response) {
response.invites.length.should.eql(1);
response.invites[0].role_id.should.eql(testUtils.roles.ids.contributor);
done();
}).catch(done);
});
it('add invite: empty invites object', function (done) {
InvitesAPI.add({invites: []}, testUtils.context.owner)
.then(function () {
@ -253,6 +264,21 @@ describe('Invites API', function () {
}).catch(done);
});
it('Can invite a Contributor', function (done) {
InvitesAPI.add({
invites: [
{
email: 'test@example.com',
role_id: testUtils.roles.ids.contributor
}
]
}, testUtils.context.owner).then(function (response) {
checkAddResponse(response);
response.invites[0].role_id.should.equal(testUtils.roles.ids.contributor);
done();
}).catch(done);
});
it('Can invite with role set as string', function (done) {
InvitesAPI.add({
invites: [
@ -327,6 +353,21 @@ describe('Invites API', function () {
done();
}).catch(done);
});
it('Can invite a Contributor', function (done) {
InvitesAPI.add({
invites: [
{
email: 'test@example.com',
role_id: testUtils.roles.ids.contributor
}
]
}, testUtils.context.admin).then(function (response) {
checkAddResponse(response);
response.invites[0].role_id.should.equal(testUtils.roles.ids.contributor);
done();
}).catch(done);
});
});
describe('Editor', function () {
@ -383,33 +424,20 @@ describe('Invites API', function () {
done();
}).catch(done);
});
});
describe('Author', function () {
it('CANNOT invite an Owner', function (done) {
it('Can invite a Contributor', function (done) {
InvitesAPI.add({
invites: [
{
email: 'test@example.com',
role_id: testUtils.roles.ids.owner
role_id: testUtils.roles.ids.contributor
}
]
}, context.author).then(function () {
done(new Error('Author should not be able to add an owner'));
}).catch(checkForErrorType('NoPermissionError', done));
});
it('CANNOT invite an Author', function (done) {
InvitesAPI.add({
invites: [
{
email: 'test@example.com',
role_id: testUtils.roles.ids.author
}
]
}, context.author).then(function () {
done(new Error('Author should not be able to add an Author'));
}).catch(checkForErrorType('NoPermissionError', done));
}, context.editor).then(function (response) {
checkAddResponse(response);
response.invites[0].role_id.should.equal(testUtils.roles.ids.contributor);
done();
}).catch(done);
});
});
});

View file

@ -15,11 +15,12 @@ describe('Roles API', function () {
should.exist(response);
testUtils.API.checkResponse(response, 'roles');
should.exist(response.roles);
response.roles.should.have.length(4);
response.roles.should.have.length(5);
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');
testUtils.API.checkResponse(response.roles[4], 'role');
}
it('Owner can browse', function (done) {
@ -50,6 +51,13 @@ describe('Roles API', function () {
}).catch(done);
});
it('Contributor can browse', function (done) {
RoleAPI.browse(context.contributor).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.'));
@ -64,13 +72,15 @@ describe('Roles API', function () {
should.exist(response);
should.exist(response.roles);
testUtils.API.checkResponse(response, 'roles');
response.roles.should.have.length(3);
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');
response.roles[0].name.should.equal('Administrator');
response.roles[1].name.should.equal('Editor');
response.roles[2].name.should.equal('Author');
response.roles[3].name.should.equal('Contributor');
}
it('Owner can assign all', function (done) {
@ -87,14 +97,16 @@ describe('Roles API', function () {
}).catch(done);
});
it('Editor can assign Author', function (done) {
it('Editor can assign Author & Contributor', 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);
response.roles.should.have.length(2);
testUtils.API.checkResponse(response.roles[0], 'role');
testUtils.API.checkResponse(response.roles[1], 'role');
response.roles[0].name.should.equal('Author');
response.roles[1].name.should.equal('Contributor');
done();
}).catch(done);
});
@ -109,6 +121,16 @@ describe('Roles API', function () {
}).catch(done);
});
it('Contributor CANNOT assign any', function (done) {
RoleAPI.browse(_.extend({}, context.contributor, {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.'));

View file

@ -50,6 +50,17 @@ describe('Tags API', function () {
}).catch(done);
});
it('can add a tag (author)', function (done) {
TagAPI.add({tags: [newTag]}, testUtils.context.author)
.then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
results.tags[0].visibility.should.eql('public');
done();
}).catch(done);
});
it('add internal tag', function (done) {
TagAPI
.add({tags: [{name: '#test'}]}, testUtils.context.editor)
@ -64,6 +75,15 @@ describe('Tags API', function () {
}).catch(done);
});
it('CANNOT add tag (contributor)', function (done) {
TagAPI.add({tags: [newTag]}, testUtils.context.contributor)
.then(function () {
done(new Error('Add tag is not denied for contributor.'));
}, function () {
done();
}).catch(done);
});
it('No-auth CANNOT add tag', function (done) {
TagAPI.add({tags: [newTag]}).then(function () {
done(new Error('Add tag is not denied without authentication.'));
@ -111,6 +131,15 @@ describe('Tags API', function () {
}).catch(done);
});
it('CANNOT edit a tag (author)', function (done) {
TagAPI.edit({tags: [{name: newTagName}]}, _.extend({}, context.author, {id: firstTag}))
.then(function () {
done(new Error('Add tag is not denied for author.'));
}, function () {
done();
}).catch(done);
});
it('No-auth CANNOT edit tag', function (done) {
TagAPI.edit({tags: [{name: newTagName}]}, _.extend({}, {id: firstTag}))
.then(function () {

View file

@ -75,46 +75,54 @@ describe('Users API', function () {
testUtils.API.checkResponse(response.users[1], 'user', additional, missing, only, options);
testUtils.API.checkResponse(response.users[2], 'user', additional, missing, only, options);
testUtils.API.checkResponse(response.users[3], 'user', additional, missing, only, options);
testUtils.API.checkResponse(response.users[4], 'user', additional, missing, only, options);
}
it('Owner can browse', function (done) {
UserAPI.browse(context.owner).then(function (response) {
checkBrowseResponse(response, 7);
checkBrowseResponse(response, 9);
done();
}).catch(done);
});
it('Admin can browse', function (done) {
UserAPI.browse(context.admin).then(function (response) {
checkBrowseResponse(response, 7);
checkBrowseResponse(response, 9);
done();
}).catch(done);
});
it('Editor can browse', function (done) {
UserAPI.browse(context.editor).then(function (response) {
checkBrowseResponse(response, 7);
checkBrowseResponse(response, 9);
done();
}).catch(done);
});
it('Author can browse active', function (done) {
UserAPI.browse(context.author).then(function (response) {
checkBrowseResponse(response, 7);
checkBrowseResponse(response, 9);
done();
}).catch(done);
});
it('Contributor can browse active', function (done) {
UserAPI.browse(context.contributor).then(function (response) {
checkBrowseResponse(response, 9);
done();
}).catch(done);
});
it('No-auth CAN browse, but only gets filtered active users', function (done) {
UserAPI.browse().then(function (response) {
checkBrowseResponse(response, 7, null, null, null, {public: true});
checkBrowseResponse(response, 9, null, null, null, {public: true});
done();
}).catch(done);
});
it('Can browse all', function (done) {
UserAPI.browse(_.extend({}, testUtils.context.admin, {status: 'all'})).then(function (response) {
checkBrowseResponse(response, 7);
checkBrowseResponse(response, 9);
done();
}).catch(done);
});
@ -127,7 +135,7 @@ describe('Users API', function () {
should.exist(response);
testUtils.API.checkResponse(response, 'users');
should.exist(response.users);
response.users.should.have.length(7);
response.users.should.have.length(9);
testUtils.API.checkResponse(response.users[0], 'user', 'roles');
testUtils.API.checkResponse(response.users[1], 'user', 'roles');
testUtils.API.checkResponse(response.users[2], 'user', 'roles');
@ -187,8 +195,7 @@ describe('Users API', function () {
should.exist(response);
testUtils.API.checkResponse(response, 'users');
should.exist(response.users);
response.users.should.have.length(7);
response.users.should.have.length(7);
response.users.should.have.length(9);
testUtils.API.checkResponse(response.users[0], 'user', 'count');
testUtils.API.checkResponse(response.users[1], 'user', 'count');
@ -197,19 +204,23 @@ describe('Users API', function () {
testUtils.API.checkResponse(response.users[4], 'user', 'count');
testUtils.API.checkResponse(response.users[5], 'user', 'count');
testUtils.API.checkResponse(response.users[6], 'user', 'count');
testUtils.API.checkResponse(response.users[7], 'user', 'count');
testUtils.API.checkResponse(response.users[8], 'user', 'count');
response.users[0].count.posts.should.eql(0);
response.users[1].count.posts.should.eql(0);
response.users[2].count.posts.should.eql(0);
response.users[3].count.posts.should.eql(8);
response.users[3].count.posts.should.eql(0);
response.users[4].count.posts.should.eql(0);
response.users[5].count.posts.should.eql(0);
response.users[5].count.posts.should.eql(8);
response.users[6].count.posts.should.eql(0);
response.users[7].count.posts.should.eql(0);
response.users[8].count.posts.should.eql(0);
response.meta.pagination.should.have.property('page', 1);
response.meta.pagination.should.have.property('limit', 15);
response.meta.pagination.should.have.property('pages', 1);
response.meta.pagination.should.have.property('total', 7);
response.meta.pagination.should.have.property('total', 9);
response.meta.pagination.should.have.property('next', null);
response.meta.pagination.should.have.property('prev', null);
@ -263,6 +274,13 @@ describe('Users API', function () {
}).catch(done);
});
it('Contributor can read', function (done) {
UserAPI.read(_.extend({}, context.contributor, {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, true, null, null, null, {public: true});
@ -320,11 +338,15 @@ describe('Users API', function () {
}).then(function (response) {
checkEditResponse(response);
return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.owner, {id: userIdFor.contributor}));
}).then(function (response) {
checkEditResponse(response);
done();
}).catch(done);
});
it('Admin can edit Admin, Editor and Author roles', function (done) {
it('Admin can edit Admin, Editor, Author, and Contributor roles', function (done) {
UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.admin, {id: userIdFor.admin}))
.then(function (response) {
checkEditResponse(response);
@ -336,6 +358,10 @@ describe('Users API', function () {
}).then(function (response) {
checkEditResponse(response);
return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.admin, {id: userIdFor.contributor}));
}).then(function (response) {
checkEditResponse(response);
done();
}).catch(done);
});
@ -350,7 +376,7 @@ describe('Users API', function () {
});
});
it('Admin can edit Admin, Editor and Author roles with roles in payload', function (done) {
it('Admin can edit Admin, Editor, Author, and Contributor roles with roles in payload', function (done) {
UserAPI.edit({
users: [{
name: newName,
@ -376,54 +402,25 @@ describe('Users API', function () {
}).then(function (response) {
checkEditResponse(response);
return UserAPI.edit({
users: [{
name: newName,
roles: [roleIdFor.contributor]
}]
}, _.extend({}, context.admin, {id: userIdFor.contributor}));
}).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.errorType.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.errorType.should.eql('NoPermissionError');
}).finally(function () {
// Cannot edit Editor
UserAPI.edit(
{users: [{name: newName}]}, _.extend({}, context.editor, {id: testUtils.DataGenerator.Content.extraUsers[1].id})
).then(function () {
done(new Error('Editor should not be able to edit other editor account'));
}).catch(function (error) {
error.errorType.should.eql('NoPermissionError');
done();
});
});
});
});
it('Editor can edit self or Author role', function (done) {
// Can edit self
UserAPI.edit(
it('Editor can edit self', function () {
return 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) {
@ -431,7 +428,7 @@ describe('Users API', function () {
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'));
done(new Error('Author should not be able to edit owner account'));
}).catch(function (error) {
error.errorType.should.eql('NoPermissionError');
}).finally(function () {
@ -439,7 +436,7 @@ describe('Users API', function () {
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'));
done(new Error('Author should not be able to edit admin account'));
}).catch(function (error) {
error.errorType.should.eql('NoPermissionError');
}).finally(function () {
@ -495,6 +492,16 @@ describe('Users API', function () {
}).catch(done);
});
it('Contributor can edit self', function (done) {
// Next test that contributor CAN edit self
UserAPI.edit(
{users: [{name: newName}]}, _.extend({}, context.contributor, {id: userIdFor.contributor})
).then(function (response) {
checkEditResponse(response);
done();
}).catch(done);
});
it('Does not allow password to be set', function (done) {
UserAPI.edit(
{
@ -796,7 +803,7 @@ describe('Users API', function () {
});
});
it('[failure] can change status to inactive for author', function () {
it('[failure] can\'t change status to inactive for author', function () {
return UserAPI.edit(
{
users: [
@ -812,6 +819,24 @@ describe('Users API', function () {
});
});
});
describe('as contributor', function () {
it('[failure] can\' change my own status to inactive', function () {
return UserAPI.edit(
{
users: [
{
status: 'inactive'
}
]
}, _.extend({}, context.contributor, {id: userIdFor.contributor})
).then(function () {
throw new Error('this is not allowed');
}).catch(function (err) {
(err instanceof common.errors.NoPermissionError).should.eql(true);
});
});
});
});
});
@ -915,7 +940,7 @@ describe('Users API', function () {
}).catch(checkForErrorType('NoPermissionError', done));
});
it('Can destroy admin, editor, author', function (done) {
it('Can destroy admin, editor, author, contributor', function (done) {
// Admin
UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.admin}))
.then(function (response) {
@ -930,6 +955,10 @@ describe('Users API', function () {
}).then(function (response) {
should.not.exist(response);
// Contributor
return UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.contributor}));
}).then(function (response) {
should.not.exist(response);
done();
}).catch(done);
});
@ -943,15 +972,15 @@ describe('Users API', function () {
}).catch(checkForErrorType('NoPermissionError', done));
});
it('Can destroy admin, editor, author', function (done) {
it('Can destroy admin, editor, author, contributor', function (done) {
// Admin
UserAPI.destroy(_.extend({}, context.admin, {id: testUtils.DataGenerator.Content.extraUsers[0].id}))
.then(function (response) {
should.not.exist(response);
.then(function (response) {
should.not.exist(response);
// Editor
return UserAPI.destroy(_.extend({}, context.admin, {id: testUtils.DataGenerator.Content.extraUsers[1].id}));
}).then(function (response) {
// Editor
return UserAPI.destroy(_.extend({}, context.admin, {id: testUtils.DataGenerator.Content.extraUsers[1].id}));
}).then(function (response) {
should.not.exist(response);
// Author
@ -959,6 +988,10 @@ describe('Users API', function () {
}).then(function (response) {
should.not.exist(response);
// Contributor
return UserAPI.destroy(_.extend({}, context.admin, {id: testUtils.DataGenerator.Content.extraUsers[3].id}));
}).then(function (response) {
should.not.exist(response);
done();
}).catch(done);
});
@ -1039,6 +1072,22 @@ describe('Users API', function () {
}).catch(checkForErrorType('NoPermissionError', done));
});
});
describe('Contributor', function () {
it('CANNOT destroy owner', function (done) {
UserAPI.destroy(_.extend({}, context.contributor, {id: userIdFor.owner}))
.then(function () {
done(new Error('Contributor should not be able to delete owner'));
}).catch(checkForErrorType('NoPermissionError', done));
});
it('CANNOT destroy self', function (done) {
UserAPI.destroy(_.extend({}, context.contributor, {id: userIdFor.contributor}))
.then(function () {
done(new Error('Contributor should not be able to delete self'));
}).catch(checkForErrorType('NoPermissionError', done));
});
});
});
describe('Edit and assign role', function () {
@ -1122,6 +1171,26 @@ describe('Users API', function () {
}).catch(done);
});
});
it('Can assign Contributor 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.contributor]}
]
}, options).then(function (response) {
checkEditResponse(response);
response.users[0].id.should.equal(userIdFor.admin);
response.users[0].roles[0].name.should.equal('Contributor');
done();
}).catch(done);
});
});
});
describe('Admin', function () {
@ -1185,6 +1254,26 @@ describe('Users API', function () {
});
});
it('Can assign Contributor 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.contributor]}
]
}, options).then(function (response) {
checkEditResponse(response);
response.users[0].id.should.equal(userIdFor.editor);
response.users[0].roles[0].name.should.equal('Contributor');
done();
}).catch(done);
});
});
it('CANNOT downgrade owner', function (done) {
var options = _.extend({}, context.admin, {id: userIdFor.owner}, {include: 'roles'});
UserAPI.read(options).then(function (response) {
@ -1214,12 +1303,47 @@ describe('Users API', function () {
}).catch(done);
});
it('Can assign contributor role to author', function (done) {
UserAPI.edit(
{users: [{name: newName, roles: [roleIdFor.contributor]}]},
_.extend({}, context.editor, {id: testUtils.DataGenerator.Content.extraUsers[2].id}, {include: 'roles'})
).then(function (response) {
checkEditResponse(response);
response.users[0].id.should.equal(testUtils.DataGenerator.Content.extraUsers[2].id);
response.users[0].roles[0].name.should.equal('Contributor');
done();
}).catch(done);
});
it('Can assign contributor role to contributor', function (done) {
UserAPI.edit(
{users: [{name: newName, roles: [roleIdFor.contributor]}]},
_.extend({}, context.editor, {id: testUtils.DataGenerator.Content.extraUsers[3].id}, {include: 'roles'})
).then(function (response) {
checkEditResponse(response);
response.users[0].id.should.equal(testUtils.DataGenerator.Content.extraUsers[3].id);
response.users[0].roles[0].name.should.equal('Contributor');
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 () {
done(new Error('Editor should not be able to upgrade their role'));
done(new Error('Editor should not be able to downgrade their role'));
}).catch(checkForErrorType('NoPermissionError', done));
});
it('CANNOT assign contributor role to self', function (done) {
UserAPI.edit(
{users: [{name: newName, roles: [roleIdFor.contributor]}]},
_.extend({}, context.editor, {id: userIdFor.editor}, {include: 'roles'})
).then(function () {
done(new Error('Editor should not be able to downgrade their role'));
}).catch(checkForErrorType('NoPermissionError', done));
});
@ -1232,6 +1356,15 @@ describe('Users API', function () {
}).catch(checkForErrorType('NoPermissionError', done));
});
it('CANNOT assign contributor role to other Editor', function (done) {
UserAPI.edit(
{users: [{name: newName, roles: [roleIdFor.contributor]}]},
_.extend({}, context.editor, {id: testUtils.DataGenerator.Content.extraUsers[1].id}, {include: 'roles'})
).then(function () {
done(new Error('Editor should not be able to change the roles of other editors'));
}).catch(checkForErrorType('NoPermissionError', done));
});
it('CANNOT assign author role to admin', function (done) {
UserAPI.edit(
{users: [{name: newName, roles: [roleIdFor.author]}]},
@ -1240,12 +1373,31 @@ describe('Users API', function () {
done(new Error('Editor should not be able to change the roles of admins'));
}).catch(checkForErrorType('NoPermissionError', done));
});
it('CANNOT assign contributor role to admin', function (done) {
UserAPI.edit(
{users: [{name: newName, roles: [roleIdFor.contributor]}]},
_.extend({}, context.editor, {id: userIdFor.admin}, {include: 'roles'})
).then(function () {
done(new Error('Editor should not be able to change the roles of admins'));
}).catch(checkForErrorType('NoPermissionError', 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 () {
done(new Error('Editor should not be able to upgrade the role of authors'));
done(new Error('Editor should not be able to upgrade the role of contributors'));
}).catch(checkForErrorType('NoPermissionError', done));
});
it('CANNOT assign admin role to contributor', function (done) {
UserAPI.edit(
{users: [{name: newName, roles: [roleIdFor.admin]}]},
_.extend({}, context.editor, {id: userIdFor.contributor}, {include: 'roles'})
).then(function () {
done(new Error('Editor should not be able to upgrade the role of contributors'));
}).catch(checkForErrorType('NoPermissionError', done));
});
});
@ -1260,6 +1412,17 @@ describe('Users API', function () {
}).catch(checkForErrorType('NoPermissionError', done));
});
});
describe('Contributor', function () {
it('CANNOT assign higher role to self', function (done) {
UserAPI.edit(
{users: [{name: newName, roles: [roleIdFor.editor]}]},
_.extend({}, context.contributor, {id: userIdFor.contributor}, {include: 'roles'})
).then(function () {
done(new Error('Contributor should not be able to upgrade their role'));
}).catch(checkForErrorType('NoPermissionError', done));
});
});
});
describe('Transfer ownership', function () {
@ -1305,7 +1468,7 @@ describe('Users API', function () {
{owner: [{id: userIdFor.admin}]},
context.editor
).then(function () {
done(new Error('Admin is not denied transferring ownership.'));
done(new Error('Editor is not denied transferring ownership.'));
}).catch(checkForErrorType('NoPermissionError', done));
});
@ -1315,7 +1478,17 @@ describe('Users API', function () {
{owner: [{id: userIdFor.admin}]},
context.author
).then(function () {
done(new Error('Admin is not denied transferring ownership.'));
done(new Error('Author is not denied transferring ownership.'));
}).catch(checkForErrorType('NoPermissionError', done));
});
it('Contributor CANNOT transfer ownership', function (done) {
// transfer ownership to user id: 2
UserAPI.transferOwnership(
{owner: [{id: userIdFor.admin}]},
context.contributor
).then(function () {
done(new Error('Contributor is not denied transferring ownership.'));
}).catch(checkForErrorType('NoPermissionError', done));
});

View file

@ -139,7 +139,7 @@ describe('Webhooks API', function () {
it('CANNOT delete', function (done) {
WebhookAPI.destroy(_.extend({}, testUtils.context.author, {id: firstWebhook}))
.then(function () {
done(new Error('Editor should not be able to delete a webhook'));
done(new Error('Author should not be able to delete a webhook'));
}).catch(checkForErrorType('NoPermissionError', done));
});
});

View file

@ -1259,8 +1259,8 @@ describe('Import (new test structure)', function () {
users = importedData[0];
rolesUsers = importedData[1];
// we imported 3 users, there were already 3 users, only one of the imported users is new
users.length.should.equal(6, 'There should only be 6 users');
// we imported 3 users, there were already 4 users
users.length.should.equal(7, 'There should only be 7 users');
// the original owner user is first
ownerUser = users[0];
@ -1292,7 +1292,7 @@ describe('Import (new test structure)', function () {
newUser.updated_by.should.equal(ownerUser.id);
newUser.updated_at.should.not.equal(exportData.data.users[1].updated_at);
rolesUsers.length.should.equal(6, 'There should be 6 role relations');
rolesUsers.length.should.equal(7, 'There should be 7 role relations');
_.each(rolesUsers, function (roleUser) {
if (roleUser.user_id === ownerUser.id) {
@ -1450,10 +1450,10 @@ describe('Import (new test structure)', function () {
return user.name === exportData.data.users[2].name;
});
// we imported 3 users, there were already 4 users, only one of the imported users is new
users.length.should.equal(5, 'There should only be three users');
// we imported 3 users, there were already 5 users, only one of the imported users is new
users.length.should.equal(6, 'There should only be three users');
rolesUsers.length.should.equal(5, 'There should be 5 role relations');
rolesUsers.length.should.equal(6, 'There should be 6 role relations');
_.each(rolesUsers, function (roleUser) {
if (roleUser.user_id === ownerUser.id) {

View file

@ -56,33 +56,33 @@ describe('Database Migration (special functions)', function () {
// Posts
permissions[7].name.should.eql('Browse posts');
permissions[7].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[7].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[8].name.should.eql('Read posts');
permissions[8].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[8].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[9].name.should.eql('Edit posts');
permissions[9].should.be.AssignedToRoles(['Administrator', 'Editor']);
permissions[10].name.should.eql('Add posts');
permissions[10].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[10].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[11].name.should.eql('Delete posts');
permissions[11].should.be.AssignedToRoles(['Administrator', 'Editor']);
// Settings
permissions[12].name.should.eql('Browse settings');
permissions[12].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[12].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[13].name.should.eql('Read settings');
permissions[13].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[13].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[14].name.should.eql('Edit settings');
permissions[14].should.be.AssignedToRoles(['Administrator']);
// Slugs
permissions[15].name.should.eql('Generate slugs');
permissions[15].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[15].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
// Tags
permissions[16].name.should.eql('Browse tags');
permissions[16].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[16].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[17].name.should.eql('Read tags');
permissions[17].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[17].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[18].name.should.eql('Edit tags');
permissions[18].should.be.AssignedToRoles(['Administrator', 'Editor']);
permissions[19].name.should.eql('Add tags');
@ -92,7 +92,7 @@ describe('Database Migration (special functions)', function () {
// Themes
permissions[21].name.should.eql('Browse themes');
permissions[21].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[21].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[22].name.should.eql('Edit themes');
permissions[22].should.be.AssignedToRoles(['Administrator']);
permissions[23].name.should.eql('Activate themes');
@ -106,9 +106,9 @@ describe('Database Migration (special functions)', function () {
// Users
permissions[27].name.should.eql('Browse users');
permissions[27].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[27].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[28].name.should.eql('Read users');
permissions[28].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[28].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[29].name.should.eql('Edit users');
permissions[29].should.be.AssignedToRoles(['Administrator', 'Editor']);
permissions[30].name.should.eql('Add users');
@ -120,19 +120,19 @@ describe('Database Migration (special functions)', function () {
permissions[32].name.should.eql('Assign a role');
permissions[32].should.be.AssignedToRoles(['Administrator', 'Editor']);
permissions[33].name.should.eql('Browse roles');
permissions[33].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[33].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
// Clients
permissions[34].name.should.eql('Browse clients');
permissions[34].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[34].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[35].name.should.eql('Read clients');
permissions[35].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[35].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[36].name.should.eql('Edit clients');
permissions[36].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[36].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[37].name.should.eql('Add clients');
permissions[37].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[37].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[38].name.should.eql('Delete clients');
permissions[38].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[38].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
// Subscribers
permissions[39].name.should.eql('Browse subscribers');
@ -142,7 +142,7 @@ describe('Database Migration (special functions)', function () {
permissions[41].name.should.eql('Edit subscribers');
permissions[41].should.be.AssignedToRoles(['Administrator']);
permissions[42].name.should.eql('Add subscribers');
permissions[42].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[42].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author', 'Contributor']);
permissions[43].name.should.eql('Delete subscribers');
permissions[43].should.be.AssignedToRoles(['Administrator']);
@ -217,11 +217,12 @@ describe('Database Migration (special functions)', function () {
// Roles
should.exist(result.roles);
result.roles.length.should.eql(4);
result.roles.length.should.eql(5);
result.roles.at(0).get('name').should.eql('Administrator');
result.roles.at(1).get('name').should.eql('Editor');
result.roles.at(2).get('name').should.eql('Author');
result.roles.at(3).get('name').should.eql('Owner');
result.roles.at(3).get('name').should.eql('Contributor');
result.roles.at(4).get('name').should.eql('Owner');
// Permissions
result.permissions.length.should.eql(53);

View file

@ -1681,12 +1681,12 @@ describe('Post Model', function () {
found.length.should.equal(50);
return PostModel.destroyByAuthor(authorData);
}).then(function (results) {
// User 1 has 13 posts in the database
results.length.should.equal(13);
// User 1 has 10 posts in the database
results.length.should.equal(10);
return PostModel.findAll({context: {internal: true}});
}).then(function (found) {
// Only 37 should remain
found.length.should.equal(37);
// Only 40 should remain
found.length.should.equal(40);
done();
}).catch(done);
});

View file

@ -215,7 +215,7 @@ describe('User Model', function run() {
it('can findAll', function (done) {
UserModel.findAll().then(function (results) {
should.exist(results);
results.length.should.equal(4);
results.length.should.equal(5);
done();
}).catch(done);
@ -228,7 +228,7 @@ describe('User Model', function run() {
results.meta.pagination.page.should.equal(1);
results.meta.pagination.limit.should.equal(15);
results.meta.pagination.pages.should.equal(1);
results.users.length.should.equal(4);
results.users.length.should.equal(5);
done();
}).catch(done);
@ -274,7 +274,7 @@ describe('User Model', function run() {
results.meta.pagination.page.should.equal(1);
results.meta.pagination.limit.should.equal('all');
results.meta.pagination.pages.should.equal(1);
results.users.length.should.equal(7);
results.users.length.should.equal(9);
});
});

View file

@ -661,5 +661,29 @@ describe('API Utils', function () {
})
.catch(done);
});
it('should strip excludedAttrs from data if permissions function returns them', function () {
var testStub = sandbox.stub().resolves({excludedAttrs: ['foo']}),
permsStub = sandbox.stub(permissions, 'canThis').returns({
testing: {
test: testStub
}
}),
permsFunc = apiUtils.handlePermissions('tests', 'testing'),
testObj = {data: {tests: [{id: 5, name: 'testing', foo: 'bar'}]}, id: 5};
return permsFunc(testObj).then(function (res) {
permsStub.calledOnce.should.be.true();
testStub.calledOnce.should.be.true();
testStub.calledWithExactly(5, {}).should.be.true();
should(res).deepEqual({
data: {
tests: [{id: 5, name: 'testing'}]
},
id: 5
});
});
});
});
});

View file

@ -151,19 +151,19 @@ describe('Migration Fixture Utils', function () {
fixtureUtils.addFixturesForRelation(fixtures.relations[0]).then(function (result) {
should.exist(result);
result.should.be.an.Object();
result.should.have.property('expected', 34);
result.should.have.property('done', 34);
result.should.have.property('expected', 43);
result.should.have.property('done', 43);
// Permissions & Roles
permsAllStub.calledOnce.should.be.true();
rolesAllStub.calledOnce.should.be.true();
dataMethodStub.filter.callCount.should.eql(34);
dataMethodStub.find.callCount.should.eql(3);
baseUtilAttachStub.callCount.should.eql(34);
dataMethodStub.filter.callCount.should.eql(43);
dataMethodStub.find.callCount.should.eql(4);
baseUtilAttachStub.callCount.should.eql(43);
fromItem.related.callCount.should.eql(34);
fromItem.findWhere.callCount.should.eql(34);
toItem[0].get.callCount.should.eql(68);
fromItem.related.callCount.should.eql(43);
fromItem.findWhere.callCount.should.eql(43);
toItem[0].get.callCount.should.eql(86);
done();
}).catch(done);

View file

@ -20,7 +20,7 @@ var should = require('should'), // jshint ignore:line
describe('DB version integrity', function () {
// Only these variables should need updating
var currentSchemaHash = '329f9b498944c459040426e16fc65b11',
currentFixturesHash = '90925e0004a0cedd1e6ea789c81ec67d';
currentFixturesHash = 'bd814f2c2aa19c745fc84f9ecc615140';
// If this test is failing, then it is likely a change has been made that requires a DB version bump,
// and the values above will need updating as confirmation

View file

@ -0,0 +1,475 @@
var should = require('should'), // jshint ignore:line
sinon = require('sinon'),
models = require('../../../server/models'),
common = require('../../../server/lib/common'),
utils = require('../../utils'),
sandbox = sinon.sandbox.create();
describe('Models: Post', function () {
before(function () {
models.init();
});
describe('Permissible', function () {
describe('As Contributor', function () {
describe('Editing', function () {
it('rejects if changing status', function (done) {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {status: 'published'};
mockPostObj.get.withArgs('status').returns('draft');
mockPostObj.get.withArgs('author_id').returns(1);
models.Post.permissible(
mockPostObj,
'edit',
context,
unsafeAttrs,
utils.permissions.contributor,
false,
false
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockPostObj.get.calledOnce).be.true();
done();
});
});
it('rejects if changing author id', function (done) {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {status: 'draft', author_id: 2};
mockPostObj.get.withArgs('status').returns('draft');
mockPostObj.get.withArgs('author_id').returns(1);
models.Post.permissible(
mockPostObj,
'edit',
context,
unsafeAttrs,
utils.permissions.contributor,
false,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockPostObj.get.calledTwice).be.true();
done();
});
});
it('rejects if post is not draft', function (done) {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {status: 'published', author_id: 1};
mockPostObj.get.withArgs('status').returns('published');
mockPostObj.get.withArgs('author_id').returns(1);
models.Post.permissible(
mockPostObj,
'edit',
context,
unsafeAttrs,
utils.permissions.contributor,
false,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockPostObj.get.calledThrice).be.true();
done();
});
});
it('rejects if contributor is not author of post', function (done) {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {status: 'draft', author_id: 2};
mockPostObj.get.withArgs('status').returns('draft');
mockPostObj.get.withArgs('author_id').returns(2);
models.Post.permissible(
mockPostObj,
'edit',
context,
unsafeAttrs,
utils.permissions.contributor,
false,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockPostObj.get.callCount).eql(4);
done();
});
});
it('resolves if none of the above cases are true', function () {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {status: 'draft', author_id: 1};
mockPostObj.get.withArgs('status').returns('draft');
mockPostObj.get.withArgs('author_id').returns(1);
return models.Post.permissible(
mockPostObj,
'edit',
context,
unsafeAttrs,
utils.permissions.contributor,
false,
true
).then((result) => {
should.exist(result);
should(result.excludedAttrs).deepEqual(['tags']);
should(mockPostObj.get.callCount).eql(4);
});
});
});
describe('Adding', function () {
it('rejects if "published" status', function (done) {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {status: 'published', author_id: 1};
models.Post.permissible(
mockPostObj,
'add',
context,
unsafeAttrs,
utils.permissions.contributor,
false,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockPostObj.get.called).be.false();
done();
});
});
it('rejects if different author id', function (done) {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {status: 'draft', author_id: 2};
models.Post.permissible(
mockPostObj,
'add',
context,
unsafeAttrs,
utils.permissions.contributor,
false,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockPostObj.get.called).be.false();
done();
});
});
it('resolves if none of the above cases are true', function () {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {status: 'draft', author_id: 1};
return models.Post.permissible(
mockPostObj,
'add',
context,
unsafeAttrs,
utils.permissions.contributor,
false,
true
).then((result) => {
should.exist(result);
should(result.excludedAttrs).deepEqual(['tags']);
should(mockPostObj.get.called).be.false();
});
});
});
describe('Destroying', function () {
it('rejects if destroying another author\'s post', function (done) {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1};
mockPostObj.get.withArgs('author_id').returns(2);
models.Post.permissible(
mockPostObj,
'destroy',
context,
{},
utils.permissions.contributor,
false,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockPostObj.get.calledOnce).be.true();
done();
});
});
it('rejects if destroying a published post', function (done) {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1};
mockPostObj.get.withArgs('author_id').returns(1);
mockPostObj.get.withArgs('status').returns('published');
models.Post.permissible(
mockPostObj,
'destroy',
context,
{},
utils.permissions.contributor,
false,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockPostObj.get.calledTwice).be.true();
done();
});
});
it('resolves if none of the above cases are true', function () {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1};
mockPostObj.get.withArgs('status').returns('draft');
mockPostObj.get.withArgs('author_id').returns(1);
return models.Post.permissible(
mockPostObj,
'destroy',
context,
{},
utils.permissions.contributor,
false,
true
).then((result) => {
should.exist(result);
should(result.excludedAttrs).deepEqual(['tags']);
should(mockPostObj.get.calledTwice).be.true();
});
});
});
});
describe('As Author', function () {
describe('Editing', function () {
it('rejects if editing another\'s post', function (done) {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {author_id: 2};
mockPostObj.get.withArgs('author_id').returns(2);
models.Post.permissible(
mockPostObj,
'edit',
context,
unsafeAttrs,
utils.permissions.author,
false,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockPostObj.get.calledOnce).be.true();
done();
});
});
it('rejects if changing author', function (done) {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {author_id: 2};
mockPostObj.get.withArgs('author_id').returns(1);
models.Post.permissible(
mockPostObj,
'edit',
context,
unsafeAttrs,
utils.permissions.author,
false,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockPostObj.get.calledTwice).be.true();
done();
});
});
it('resolves if none of the above cases are true', function () {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {author_id: 1};
mockPostObj.get.withArgs('author_id').returns(1);
return models.Post.permissible(
mockPostObj,
'edit',
context,
unsafeAttrs,
utils.permissions.author,
false,
true
).then(() => {
should(mockPostObj.get.calledTwice).be.true();
});
});
});
describe('Adding', function () {
it('rejects if different author id', function (done) {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {author_id: 2};
models.Post.permissible(
mockPostObj,
'add',
context,
unsafeAttrs,
utils.permissions.author,
false,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockPostObj.get.called).be.false();
done();
});
});
it('resolves if none of the above cases are true', function () {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {author_id: 1};
return models.Post.permissible(
mockPostObj,
'add',
context,
unsafeAttrs,
utils.permissions.author,
false,
true
).then(() => {
should(mockPostObj.get.called).be.false();
});
});
});
});
describe('Everyone Else', function () {
it('rejects if hasUserPermissions is false and not current owner', function (done) {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {author_id: 2};
mockPostObj.get.withArgs('author_id').returns(2);
models.Post.permissible(
mockPostObj,
'edit',
context,
unsafeAttrs,
utils.permissions.editor,
false,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockPostObj.get.calledOnce).be.true();
done();
});
});
it('resolves if hasUserPermission is true', function () {
var mockPostObj = {
get: sandbox.stub()
},
context = {user: 1},
unsafeAttrs = {author_id: 2};
mockPostObj.get.withArgs('author_id').returns(2);
return models.Post.permissible(
mockPostObj,
'edit',
context,
unsafeAttrs,
utils.permissions.editor,
true,
true
).then(() => {
should(mockPostObj.get.called).be.false();
});
});
});
});
});

View file

@ -0,0 +1,157 @@
var should = require('should'),
sinon = require('sinon'),
models = require('../../../server/models'),
common = require('../../../server/lib/common'),
utils = require('../../utils'),
sandbox = sinon.sandbox.create();
describe('Models: User', function () {
before(function () {
models.init();
});
describe('Permissible', function () {
function getUserModel(id, role) {
var hasRole = sandbox.stub();
hasRole.withArgs(role).returns(true);
return {
hasRole: hasRole,
related: sandbox.stub().returns([{name: role}]),
get: sandbox.stub().returns(id)
};
}
it('cannot delete owner', function (done) {
var mockUser = getUserModel(1, 'Owner'),
context = {user: 1};
models.User.permissible(mockUser, 'destroy', context, {}, utils.permissions.owner, true, true).then(() => {
done(new Error('Permissible function should have errored'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockUser.hasRole.calledOnce).be.true();
done();
});
});
it('can always edit self', function () {
var mockUser = getUserModel(3, 'Contributor'),
context = {user: 3};
return models.User.permissible(mockUser, 'edit', context, {}, utils.permissions.contributor, false, true).then(() => {
should(mockUser.get.calledOnce).be.true();
});
});
describe('as editor', function () {
it('can\'t edit another editor', function (done) {
var mockUser = getUserModel(3, 'Editor'),
context = {user: 2};
models.User.permissible(mockUser, 'edit', context, {}, utils.permissions.editor, true, true).then(() => {
done(new Error('Permissible function should have errored'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockUser.hasRole.called).be.true();
should(mockUser.get.calledOnce).be.true();
done();
});
});
it('can\'t edit an admin', function (done) {
var mockUser = getUserModel(3, 'Administrator'),
context = {user: 2};
models.User.permissible(mockUser, 'edit', context, {}, utils.permissions.editor, true, true).then(() => {
done(new Error('Permissible function should have errored'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockUser.hasRole.called).be.true();
should(mockUser.get.calledOnce).be.true();
done();
});
});
it('can edit author', function () {
var mockUser = getUserModel(3, 'Author'),
context = {user: 2};
return models.User.permissible(mockUser, 'edit', context, {}, utils.permissions.editor, true, true).then(() => {
should(mockUser.hasRole.called).be.true();
should(mockUser.get.calledOnce).be.true();
});
});
it('can edit contributor', function () {
var mockUser = getUserModel(3, 'Contributor'),
context = {user: 2};
return models.User.permissible(mockUser, 'edit', context, {}, utils.permissions.editor, true, true).then(() => {
should(mockUser.hasRole.called).be.true();
should(mockUser.get.calledOnce).be.true();
});
});
it('can destroy self', function () {
var mockUser = getUserModel(3, 'Editor'),
context = {user: 3};
return models.User.permissible(mockUser, 'destroy', context, {}, utils.permissions.editor, true, true).then(() => {
should(mockUser.hasRole.called).be.true();
should(mockUser.get.calledOnce).be.true();
});
});
it('can\'t destroy another editor', function (done) {
var mockUser = getUserModel(3, 'Editor'),
context = {user: 2};
models.User.permissible(mockUser, 'destroy', context, {}, utils.permissions.editor, true, true).then(() => {
done(new Error('Permissible function should have errored'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockUser.hasRole.called).be.true();
should(mockUser.get.calledOnce).be.true();
done();
});
});
it('can\'t destroy an admin', function (done) {
var mockUser = getUserModel(3, 'Administrator'),
context = {user: 2};
models.User.permissible(mockUser, 'destroy', context, {}, utils.permissions.editor, true, true).then(() => {
done(new Error('Permissible function should have errored'));
}).catch((error) => {
error.should.be.an.instanceof(common.errors.NoPermissionError);
should(mockUser.hasRole.called).be.true();
should(mockUser.get.calledOnce).be.true();
done();
});
});
it('can destroy an author', function () {
var mockUser = getUserModel(3, 'Author'),
context = {user: 2};
return models.User.permissible(mockUser, 'destroy', context, {}, utils.permissions.editor, true, true).then(() => {
should(mockUser.hasRole.called).be.true();
should(mockUser.get.calledOnce).be.true();
});
});
it('can destroy a contributor', function () {
var mockUser = getUserModel(3, 'Contributor'),
context = {user: 2};
return models.User.permissible(mockUser, 'destroy', context, {}, utils.permissions.editor, true, true).then(() => {
should(mockUser.hasRole.called).be.true();
should(mockUser.get.calledOnce).be.true();
});
});
});
});
});

View file

@ -177,6 +177,22 @@ DataGenerator.Content = {
slug: 'ad-2',
email: 'info3@ghost.org',
password: '$2b$10$ujPIlqjTsYwfc2/zrqZXZ.yd7cQQm2iOkAFenTAJfveKkc23nwdeS'
},
{
// contributor
id: ObjectId.generate(),
name: 'Contributor',
slug: 'contributor',
email: 'contributor@ghost.org',
password: '$2b$10$ujPIlqjTsYwfc2/zrqZXZ.yd7cQQm2iOkAFenTAJfveKkc23nwdeS'
},
{
// contributor
id: ObjectId.generate(),
name: 'contributor2',
slug: 'contrib-2',
email: 'contributor2@ghost.org',
password: '$2b$10$ujPIlqjTsYwfc2/zrqZXZ.yd7cQQm2iOkAFenTAJfveKkc23nwdeS'
}
],
@ -263,6 +279,11 @@ DataGenerator.Content = {
id: ObjectId.generate(),
name: 'Owner',
description: 'Blog Owner'
},
{
id: ObjectId.generate(),
name: 'Contributor',
description: 'Contributors'
}
],
@ -573,14 +594,16 @@ DataGenerator.forKnex = (function () {
createBasic(DataGenerator.Content.roles[0]),
createBasic(DataGenerator.Content.roles[1]),
createBasic(DataGenerator.Content.roles[2]),
createBasic(DataGenerator.Content.roles[3])
createBasic(DataGenerator.Content.roles[3]),
createBasic(DataGenerator.Content.roles[4])
];
users = [
createUser(DataGenerator.Content.users[0]),
createUser(DataGenerator.Content.users[1]),
createUser(DataGenerator.Content.users[2]),
createUser(DataGenerator.Content.users[3])
createUser(DataGenerator.Content.users[3]),
createUser(DataGenerator.Content.users[7])
];
clients = [
@ -610,6 +633,11 @@ DataGenerator.forKnex = (function () {
id: ObjectId.generate(),
user_id: DataGenerator.Content.users[3].id,
role_id: DataGenerator.Content.roles[2].id
},
{
id: ObjectId.generate(),
user_id: DataGenerator.Content.users[7].id,
role_id: DataGenerator.Content.roles[4].id
}
];

View file

@ -1197,6 +1197,15 @@
"created_by": "1",
"updated_at": "2017-09-01T12:29:50.000Z",
"updated_by": "1"
},
{
"id": "59a952be7d79ed06b0d21137",
"name": "Contributor",
"description": "Contributors",
"created_at": "2017-09-01T12:29:50.000Z",
"created_by": "1",
"updated_at": "2017-09-01T12:29:50.000Z",
"updated_by": "1"
}
],
"roles_users": [

View file

@ -275,7 +275,7 @@ fixtures = {
createExtraUsers: function createExtraUsers() {
// grab 3 more users
var extraUsers = DataGenerator.Content.users.slice(2, 5);
var extraUsers = DataGenerator.Content.users.slice(2, 6);
extraUsers = _.map(extraUsers, function (user) {
return DataGenerator.forKnex.createUser(_.extend({}, user, {
@ -294,7 +294,8 @@ fixtures = {
return db.knex('roles_users').insert([
{id: ObjectId.generate(), user_id: extraUsers[0].id, role_id: DataGenerator.Content.roles[0].id},
{id: ObjectId.generate(), user_id: extraUsers[1].id, role_id: DataGenerator.Content.roles[1].id},
{id: ObjectId.generate(), user_id: extraUsers[2].id, role_id: DataGenerator.Content.roles[2].id}
{id: ObjectId.generate(), user_id: extraUsers[2].id, role_id: DataGenerator.Content.roles[2].id},
{id: ObjectId.generate(), user_id: extraUsers[3].id, role_id: DataGenerator.Content.roles[4].id}
]);
});
},
@ -370,7 +371,8 @@ fixtures = {
Administrator: DataGenerator.Content.roles[0].id,
Editor: DataGenerator.Content.roles[1].id,
Author: DataGenerator.Content.roles[2].id,
Owner: DataGenerator.Content.roles[3].id
Owner: DataGenerator.Content.roles[3].id,
Contributor: DataGenerator.Content.roles[4].id
};
// CASE: if empty db will throw SQLITE_MISUSE, hard to debug
@ -976,7 +978,15 @@ module.exports = {
owner: {context: {user: DataGenerator.Content.users[0].id}},
admin: {context: {user: DataGenerator.Content.users[1].id}},
editor: {context: {user: DataGenerator.Content.users[2].id}},
author: {context: {user: DataGenerator.Content.users[3].id}}
author: {context: {user: DataGenerator.Content.users[3].id}},
contributor: {context: {user: DataGenerator.Content.users[7].id}}
},
permissions: {
owner: {user: {roles: [DataGenerator.Content.roles[3]]}},
admin: {user: {roles: [DataGenerator.Content.roles[0]]}},
editor: {user: {roles: [DataGenerator.Content.roles[1]]}},
author: {user: {roles: [DataGenerator.Content.roles[2]]}},
contributor: {user: {roles: [DataGenerator.Content.roles[4]]}},
},
users: {
ids: {
@ -986,7 +996,9 @@ module.exports = {
author: DataGenerator.Content.users[3].id,
admin2: DataGenerator.Content.users[6].id,
editor2: DataGenerator.Content.users[4].id,
author2: DataGenerator.Content.users[5].id
author2: DataGenerator.Content.users[5].id,
contributor: DataGenerator.Content.users[7].id,
contributor2: DataGenerator.Content.users[8].id
}
},
roles: {
@ -994,10 +1006,10 @@ module.exports = {
owner: DataGenerator.Content.roles[3].id,
admin: DataGenerator.Content.roles[0].id,
editor: DataGenerator.Content.roles[1].id,
author: DataGenerator.Content.roles[2].id
author: DataGenerator.Content.roles[2].id,
contributor: DataGenerator.Content.roles[4].id
}
},
cacheRules: {
public: 'public, max-age=0',
hour: 'public, max-age=' + 3600,