diff --git a/core/server/models/comment.js b/core/server/models/comment.js index 70c386f161..125bf0deed 100644 --- a/core/server/models/comment.js +++ b/core/server/models/comment.js @@ -5,7 +5,8 @@ const tpl = require('@tryghost/tpl'); const messages = { commentNotFound: 'Comment could not be found', - notYourComment: 'You may only edit your own comments' + notYourCommentToEdit: 'You may only edit your own comments', + notYourCommentToDestroy: 'You may only delete your own comments' }; const Comment = ghostBookshelf.Model.extend({ @@ -40,6 +41,23 @@ const Comment = ghostBookshelf.Model.extend({ model.emitChange('added', options); } }, { + destroy: function destroy(unfilteredOptions) { + let options = this.filterOptions(unfilteredOptions, 'destroy', {extraAllowedProperties: ['id']}); + + const softDelete = () => { + return ghostBookshelf.Model.edit.call(this, {status: 'deleted'}, options); + }; + + if (!options.transacting) { + return ghostBookshelf.transaction((transacting) => { + options.transacting = transacting; + return softDelete(); + }); + } + + return softDelete(); + }, + async permissible(commentModelOrId, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasApiKeyPermission, hasMemberPermission) { const self = this; @@ -64,9 +82,15 @@ const Comment = ghostBookshelf.Model.extend({ }); } - if ((action === 'edit' || action === 'destroy') && commentModelOrId.get('member_id') !== context.member.id) { + if (action === 'edit' && commentModelOrId.get('member_id') !== context.member.id) { return Promise.reject(new errors.NoPermissionError({ - message: tpl(messages.notYourComment) + message: tpl(messages.notYourCommentToEdit) + })); + } + + if (action === 'destroy' && commentModelOrId.get('member_id') !== context.member.id) { + return Promise.reject(new errors.NoPermissionError({ + message: tpl(messages.notYourCommentToDestroy) })); } diff --git a/test/unit/server/models/comment.test.js b/test/unit/server/models/comment.test.js new file mode 100644 index 0000000000..bf4f1438c8 --- /dev/null +++ b/test/unit/server/models/comment.test.js @@ -0,0 +1,95 @@ +const sinon = require('sinon'); +const models = require('../../../../core/server/models'); +const testUtils = require('../../../utils'); + +describe('Unit: models/comment', function () { + before(function () { + models.init(); + }); + + afterEach(function () { + sinon.restore(); + }); + + describe('permissible', function () { + function getCommentModel(id, memberId) { + const obj = { + id: id, + member_id: memberId + }; + + return { + id: obj.id, + get: sinon.stub().callsFake((prop) => { + return obj[prop]; + }) + }; + } + + it('user can do all', async function () { + const comment = getCommentModel(1, 'member_123'); + const context = {user: 1}; + + const response = await models.Comment.permissible(comment, 'destroy', context, {}, testUtils.permissions.owner, true, true, true); + response.should.eql(true); + }); + + it('can only edit own comments', async function () { + const comment = getCommentModel(1, 'member_123'); + const context = { + member: { + id: 'other_member' + } + }; + + try { + const response = await models.Comment.permissible(comment, 'edit', context, {}, null, false, true, true); + response.should.eql(true); + } catch (err) { + err.message.should.eql('You may only edit your own comments'); + return; + } + throw new Error('Should throw'); + }); + + it('can edit own comments', async function () { + const comment = getCommentModel(1, 'member_123'); + const context = { + member: { + id: 'member_123' + } + }; + + await models.Comment.permissible(comment, 'edit', context, {}, null, false, true, true); + }); + + it('can only destroy own comments', async function () { + const comment = getCommentModel(1, 'member_123'); + const context = { + member: { + id: 'other_member' + } + }; + + try { + const response = await models.Comment.permissible(comment, 'destroy', context, {}, null, false, true, true); + response.should.eql(true); + } catch (err) { + err.message.should.eql('You may only delete your own comments'); + return; + } + throw new Error('Should throw'); + }); + + it('can edit destroy comments', async function () { + const comment = getCommentModel(1, 'member_123'); + const context = { + member: { + id: 'member_123' + } + }; + + await models.Comment.permissible(comment, 'destroy', context, {}, null, false, true, true); + }); + }); +});