mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
🐛 Fixed author role permission to change author (#9067)
🐛 Fixed author role permission to change author no issue - To be able to fix this bug, we had to solve tasks from #9043 - This bug affects the private / undocumented API only - Author role users should not be allowed to change the author of a post
This commit is contained in:
parent
baf8116d6b
commit
fcd3c6847b
3 changed files with 166 additions and 23 deletions
|
@ -12,6 +12,7 @@ var Promise = require('bluebird'),
|
||||||
'created_by', 'updated_by', 'published_by', 'author', 'tags', 'fields',
|
'created_by', 'updated_by', 'published_by', 'author', 'tags', 'fields',
|
||||||
'next', 'previous', 'next.author', 'next.tags', 'previous.author', 'previous.tags'
|
'next', 'previous', 'next.author', 'next.tags', 'previous.author', 'previous.tags'
|
||||||
],
|
],
|
||||||
|
unsafeAttrs = ['author_id'],
|
||||||
posts;
|
posts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,7 +61,7 @@ posts = {
|
||||||
// Push all of our tasks into a `tasks` array in the correct order
|
// Push all of our tasks into a `tasks` array in the correct order
|
||||||
tasks = [
|
tasks = [
|
||||||
apiUtils.validate(docName, {opts: permittedOptions}),
|
apiUtils.validate(docName, {opts: permittedOptions}),
|
||||||
apiUtils.handlePublicPermissions(docName, 'browse'),
|
apiUtils.handlePublicPermissions(docName, 'browse', unsafeAttrs),
|
||||||
apiUtils.convertOptions(allowedIncludes, models.Post.allowedFormats),
|
apiUtils.convertOptions(allowedIncludes, models.Post.allowedFormats),
|
||||||
modelQuery
|
modelQuery
|
||||||
];
|
];
|
||||||
|
@ -94,7 +95,7 @@ posts = {
|
||||||
// Push all of our tasks into a `tasks` array in the correct order
|
// Push all of our tasks into a `tasks` array in the correct order
|
||||||
tasks = [
|
tasks = [
|
||||||
apiUtils.validate(docName, {attrs: attrs, opts: options.opts || []}),
|
apiUtils.validate(docName, {attrs: attrs, opts: options.opts || []}),
|
||||||
apiUtils.handlePublicPermissions(docName, 'read'),
|
apiUtils.handlePublicPermissions(docName, 'read', unsafeAttrs),
|
||||||
apiUtils.convertOptions(allowedIncludes, models.Post.allowedFormats),
|
apiUtils.convertOptions(allowedIncludes, models.Post.allowedFormats),
|
||||||
modelQuery
|
modelQuery
|
||||||
];
|
];
|
||||||
|
@ -135,7 +136,7 @@ posts = {
|
||||||
// Push all of our tasks into a `tasks` array in the correct order
|
// Push all of our tasks into a `tasks` array in the correct order
|
||||||
tasks = [
|
tasks = [
|
||||||
apiUtils.validate(docName, {opts: apiUtils.idDefaultOptions.concat(options.opts || [])}),
|
apiUtils.validate(docName, {opts: apiUtils.idDefaultOptions.concat(options.opts || [])}),
|
||||||
apiUtils.handlePermissions(docName, 'edit'),
|
apiUtils.handlePermissions(docName, 'edit', unsafeAttrs),
|
||||||
apiUtils.convertOptions(allowedIncludes),
|
apiUtils.convertOptions(allowedIncludes),
|
||||||
modelQuery
|
modelQuery
|
||||||
];
|
];
|
||||||
|
@ -182,7 +183,7 @@ posts = {
|
||||||
// Push all of our tasks into a `tasks` array in the correct order
|
// Push all of our tasks into a `tasks` array in the correct order
|
||||||
tasks = [
|
tasks = [
|
||||||
apiUtils.validate(docName),
|
apiUtils.validate(docName),
|
||||||
apiUtils.handlePermissions(docName, 'add'),
|
apiUtils.handlePermissions(docName, 'add', unsafeAttrs),
|
||||||
apiUtils.convertOptions(allowedIncludes),
|
apiUtils.convertOptions(allowedIncludes),
|
||||||
modelQuery
|
modelQuery
|
||||||
];
|
];
|
||||||
|
@ -229,7 +230,7 @@ posts = {
|
||||||
// Push all of our tasks into a `tasks` array in the correct order
|
// Push all of our tasks into a `tasks` array in the correct order
|
||||||
tasks = [
|
tasks = [
|
||||||
apiUtils.validate(docName, {opts: apiUtils.idDefaultOptions}),
|
apiUtils.validate(docName, {opts: apiUtils.idDefaultOptions}),
|
||||||
apiUtils.handlePermissions(docName, 'destroy'),
|
apiUtils.handlePermissions(docName, 'destroy', unsafeAttrs),
|
||||||
apiUtils.convertOptions(allowedIncludes),
|
apiUtils.convertOptions(allowedIncludes),
|
||||||
deletePost
|
deletePost
|
||||||
];
|
];
|
||||||
|
|
|
@ -830,8 +830,23 @@ Post = ghostBookshelf.Model.extend({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postModel) {
|
function isChanging(attr) {
|
||||||
// If this is the author of the post, allow it.
|
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') {
|
||||||
|
hasUserPermission = isOwner();
|
||||||
|
} else if (postModel) {
|
||||||
hasUserPermission = hasUserPermission || context.user === postModel.get('author_id');
|
hasUserPermission = hasUserPermission || context.user === postModel.get('author_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1321,25 +1321,120 @@ describe('Post API', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Add', function () {
|
||||||
|
it('can add own post', function (done) {
|
||||||
|
var post = {
|
||||||
|
title: 'Freshly added',
|
||||||
|
slug: 'freshly-added',
|
||||||
|
author_id: author.id
|
||||||
|
};
|
||||||
|
|
||||||
|
request.post(testUtils.API.getApiQuery('posts/'))
|
||||||
|
.set('Authorization', 'Bearer ' + authorAccessToken)
|
||||||
|
.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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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 () {
|
describe('Edit', function () {
|
||||||
var postId;
|
var authorPostId;
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
return testUtils
|
return testUtils.createPost({
|
||||||
.createPost({
|
|
||||||
post: {
|
post: {
|
||||||
title: 'Author\'s test post',
|
title: 'Author\'s test post',
|
||||||
slug: 'author-post'
|
slug: 'author-post',
|
||||||
},
|
author_id: author.id
|
||||||
author: author
|
}
|
||||||
})
|
})
|
||||||
.then(function (post) {
|
.then(function (post) {
|
||||||
postId = post.id;
|
authorPostId = post.id;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can edit own post', function (done) {
|
it('can edit own post', function (done) {
|
||||||
request.get(testUtils.API.getApiQuery('posts/' + postId + '/?include=tags'))
|
request.get(testUtils.API.getApiQuery('posts/' + authorPostId + '/?include=tags'))
|
||||||
|
.set('Authorization', 'Bearer ' + authorAccessToken)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(200)
|
||||||
|
.end(function (err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonResponse = res.body,
|
||||||
|
changedTitle = 'My new Title',
|
||||||
|
changedSlug = 'my-new-slug';
|
||||||
|
|
||||||
|
should.exist(jsonResponse.posts[0]);
|
||||||
|
jsonResponse.posts[0].title = changedTitle;
|
||||||
|
jsonResponse.posts[0].slug = changedSlug;
|
||||||
|
|
||||||
|
request.put(testUtils.API.getApiQuery('posts/' + authorPostId + '/'))
|
||||||
|
.set('Authorization', 'Bearer ' + authorAccessToken)
|
||||||
|
.send(jsonResponse)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(200)
|
||||||
|
.end(function (err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var putBody = res.body;
|
||||||
|
res.headers['x-cache-invalidate'].should.eql('/*');
|
||||||
|
should.exist(putBody);
|
||||||
|
putBody.posts[0].title.should.eql(changedTitle);
|
||||||
|
putBody.posts[0].slug.should.eql(changedSlug);
|
||||||
|
|
||||||
|
testUtils.API.checkResponse(putBody.posts[0], 'post');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('CANNOT change author of own post', function (done) {
|
||||||
|
request.get(testUtils.API.getApiQuery('posts/' + authorPostId + '/?include=tags'))
|
||||||
.set('Authorization', 'Bearer ' + authorAccessToken)
|
.set('Authorization', 'Bearer ' + authorAccessToken)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
@ -1357,24 +1452,56 @@ describe('Post API', function () {
|
||||||
jsonResponse.posts[0].title = changedTitle;
|
jsonResponse.posts[0].title = changedTitle;
|
||||||
jsonResponse.posts[0].author = changedAuthor;
|
jsonResponse.posts[0].author = changedAuthor;
|
||||||
|
|
||||||
request.put(testUtils.API.getApiQuery('posts/' + postId + '/'))
|
request.put(testUtils.API.getApiQuery('posts/' + authorPostId + '/'))
|
||||||
.set('Authorization', 'Bearer ' + authorAccessToken)
|
.set('Authorization', 'Bearer ' + authorAccessToken)
|
||||||
.send(jsonResponse)
|
.send(jsonResponse)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
.expect(200)
|
.expect(403)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
var putBody = res.body;
|
should.exist(res.body.errors);
|
||||||
res.headers['x-cache-invalidate'].should.eql('/*');
|
res.body.errors[0].errorType.should.eql('NoPermissionError');
|
||||||
should.exist(putBody);
|
done();
|
||||||
putBody.posts[0].title.should.eql(changedTitle);
|
});
|
||||||
putBody.posts[0].author.should.eql(changedAuthor);
|
});
|
||||||
|
});
|
||||||
|
|
||||||
testUtils.API.checkResponse(putBody.posts[0], 'post');
|
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)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(200)
|
||||||
|
.end(function (err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonResponse = res.body,
|
||||||
|
changedTitle = 'My new Title',
|
||||||
|
changedAuthor = author.id;
|
||||||
|
|
||||||
|
should.exist(jsonResponse.posts[0]);
|
||||||
|
jsonResponse.posts[0].title = changedTitle;
|
||||||
|
jsonResponse.posts[0].author = changedAuthor;
|
||||||
|
|
||||||
|
request.put(testUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
|
||||||
|
.set('Authorization', 'Bearer ' + authorAccessToken)
|
||||||
|
.send(jsonResponse)
|
||||||
|
.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();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue