From 117a254cc8938f79dc68739abbfcc18b234044a5 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Tue, 10 Nov 2015 12:45:42 -0600 Subject: [PATCH] Patch exploit that would allow users to auto-delete messages --- .../chat/POST-groups_id_chat_id_flag.test.js | 60 +++++++++++++++ .../chat/POST-groups_id_chat_id_like.test.js | 60 +++++++++++++++ website/src/controllers/api-v2/groups.js | 76 +++++++++---------- 3 files changed, 158 insertions(+), 38 deletions(-) diff --git a/test/api/v2/groups/chat/POST-groups_id_chat_id_flag.test.js b/test/api/v2/groups/chat/POST-groups_id_chat_id_flag.test.js index 6557486353..dc204e940d 100644 --- a/test/api/v2/groups/chat/POST-groups_id_chat_id_flag.test.js +++ b/test/api/v2/groups/chat/POST-groups_id_chat_id_flag.test.js @@ -111,6 +111,66 @@ describe('POST /groups/:id/chat/:id/flag', () => { }); }); + context('group with multiple messages', () => { + let admin, author, group, member, message, user; + + beforeEach(() => { + return generateUser().then((user) => { + author = user; + + return createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'public', + chat: [ + { id: 'message-to-be-flagged', uuid: author._id, flagCount: 0, flags: {} }, + { id: '1-flag-message', uuid: author._id, flagCount: 1, flags: { 'id1': true } }, + { id: '2-flag-message', uuid: author._id, flagCount: 2, flags: { 'id1': true, 'id2': true } }, + { id: 'no-flags', uuid: author._id, flagCount: 0, flags: {} }, + ], + }, + members: 1, + }); + }).then((res) => { + group = res.group; + user = res.leader; + member = res.members[0]; + return generateUser({ + 'contributor.admin': true, + }); + }).then((user) => { + admin = user; + }); + }); + + it('changes only the message that is flagged', () => { + let api = requester(user); + + return api.post(`/groups/${group._id}/chat/message-to-be-flagged/flag`).then((messages) => { + return requester(admin).get(`/groups/${group._id}/chat`); + }).then((messages) => { + expect(messages).to.have.lengthOf(4); + + let messageThatWasFlagged = messages[0]; + let messageWith1Flag = messages[1]; + let messageWith2Flag = messages[2]; + let messageWithoutFlags = messages[3]; + + expect(messageThatWasFlagged.flagCount).to.eql(1); + expect(messageThatWasFlagged.flags).to.have.property(user._id, true); + + expect(messageWith1Flag.flagCount).to.eql(1); + expect(messageWith1Flag.flags).to.have.property('id1', true); + + expect(messageWith2Flag.flagCount).to.eql(2); + expect(messageWith2Flag.flags).to.have.property('id1', true); + + expect(messageWithoutFlags.flagCount).to.eql(0); + expect(messageWithoutFlags.flags).to.eql({}); + }); + }); + }); + context('admin flagging a message', () => { let group, member, message, user; diff --git a/test/api/v2/groups/chat/POST-groups_id_chat_id_like.test.js b/test/api/v2/groups/chat/POST-groups_id_chat_id_like.test.js index a3604a0443..a89a83742f 100644 --- a/test/api/v2/groups/chat/POST-groups_id_chat_id_like.test.js +++ b/test/api/v2/groups/chat/POST-groups_id_chat_id_like.test.js @@ -82,6 +82,66 @@ describe('POST /groups/:id/chat/:id/like', () => { }); }); + context('group with multiple messages', () => { + let admin, author, group, member, message, user; + + beforeEach(() => { + return generateUser().then((user) => { + author = user; + + return createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'public', + chat: [ + { id: 'message-to-be-liked', likes: {}, uuid: author._id, flagCount: 0, flags: {} }, + { id: '1-like-message', likes: { 'id': true }, uuid: author._id, flagCount: 1, flags: { 'id1': true } }, + { id: '2-like-message', likes: { 'id': true, 'id2': true }, uuid: author._id, flagCount: 2, flags: { 'id1': true, 'id2': true } }, + { id: 'no-likes', likes: {}, uuid: author._id, flagCount: 0, flags: {} }, + ], + }, + members: 1, + }); + }).then((res) => { + group = res.group; + user = res.leader; + member = res.members[0]; + return generateUser({ + 'contributor.admin': true, + }); + }).then((user) => { + admin = user; + }); + }); + + it('changes only the message that is liked', () => { + let api = requester(user); + + return api.post(`/groups/${group._id}/chat/message-to-be-liked/like`).then((messages) => { + return requester(admin).get(`/groups/${group._id}/chat`); + }).then((messages) => { + expect(messages).to.have.lengthOf(4); + + let messageThatWasLiked = messages[0]; + let messageWith1Like = messages[1]; + let messageWith2Like = messages[2]; + let messageWithoutLike = messages[3]; + + expect(messageThatWasLiked.likes).to.have.property(user._id, true); + + expect(messageWith1Like.flagCount).to.eql(1); + expect(messageWith1Like.flags).to.have.property('id1', true); + + expect(messageWith2Like.flagCount).to.eql(2); + expect(messageWith2Like.flags).to.have.property('id1', true); + expect(messageWith2Like.flags).to.have.property('id2', true); + + expect(messageWithoutLike.flagCount).to.eql(0); + expect(messageWithoutLike.flags).to.eql({}); + }); + }); + }); + context('nonexistant message', () => { let api, group, message, user; diff --git a/website/src/controllers/api-v2/groups.js b/website/src/controllers/api-v2/groups.js index f1b1b8a0fc..c30dc0ea6d 100644 --- a/website/src/controllers/api-v2/groups.js +++ b/website/src/controllers/api-v2/groups.js @@ -361,44 +361,45 @@ api.flagChatMessage = function(req, res, next){ message.flagCount++ } - group.markModified('chat'); - group.save(function(err,_saved){ - if(err) return next(err); - var addressesToSendTo = nconf.get('FLAG_REPORT_EMAIL'); - addressesToSendTo = (typeof addressesToSendTo == 'string') ? JSON.parse(addressesToSendTo) : addressesToSendTo; + Group.update({_id: group._id, 'chat.id': message.id}, {'$set': { + 'chat.$.flags': message.flags, + 'chat.$.flagCount': message.flagCount, + }}, function(err) { + if (err) return next(err); + var addressesToSendTo = nconf.get('FLAG_REPORT_EMAIL'); + addressesToSendTo = (typeof addressesToSendTo == 'string') ? JSON.parse(addressesToSendTo) : addressesToSendTo; - if(Array.isArray(addressesToSendTo)){ - addressesToSendTo = addressesToSendTo.map(function(email){ - return {email: email, canSend: true} - }); - }else{ - addressesToSendTo = {email: addressesToSendTo} - } + if(Array.isArray(addressesToSendTo)){ + addressesToSendTo = addressesToSendTo.map(function(email){ + return {email: email, canSend: true} + }); + }else{ + addressesToSendTo = {email: addressesToSendTo} + } - utils.txnEmail(addressesToSendTo, 'flag-report-to-mods', [ - {name: "MESSAGE_TIME", content: (new Date(message.timestamp)).toString()}, - {name: "MESSAGE_TEXT", content: message.text}, + utils.txnEmail(addressesToSendTo, 'flag-report-to-mods', [ + {name: "MESSAGE_TIME", content: (new Date(message.timestamp)).toString()}, + {name: "MESSAGE_TEXT", content: message.text}, - {name: "REPORTER_USERNAME", content: user.profile.name}, - {name: "REPORTER_UUID", content: user._id}, - {name: "REPORTER_EMAIL", content: user.auth.local ? user.auth.local.email : ((user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0]) ? user.auth.facebook.emails[0].value : null)}, - {name: "REPORTER_MODAL_URL", content: "/static/front/#?memberId=" + user._id}, + {name: "REPORTER_USERNAME", content: user.profile.name}, + {name: "REPORTER_UUID", content: user._id}, + {name: "REPORTER_EMAIL", content: user.auth.local ? user.auth.local.email : ((user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0]) ? user.auth.facebook.emails[0].value : null)}, + {name: "REPORTER_MODAL_URL", content: "/static/front/#?memberId=" + user._id}, - {name: "AUTHOR_USERNAME", content: message.user}, - {name: "AUTHOR_UUID", content: message.uuid}, - {name: "AUTHOR_EMAIL", content: author.auth.local ? author.auth.local.email : ((author.auth.facebook && author.auth.facebook.emails && author.auth.facebook.emails[0]) ? author.auth.facebook.emails[0].value : null)}, - {name: "AUTHOR_MODAL_URL", content: "/static/front/#?memberId=" + message.uuid}, + {name: "AUTHOR_USERNAME", content: message.user}, + {name: "AUTHOR_UUID", content: message.uuid}, + {name: "AUTHOR_EMAIL", content: author.auth.local ? author.auth.local.email : ((author.auth.facebook && author.auth.facebook.emails && author.auth.facebook.emails[0]) ? author.auth.facebook.emails[0].value : null)}, + {name: "AUTHOR_MODAL_URL", content: "/static/front/#?memberId=" + message.uuid}, - {name: "GROUP_NAME", content: group.name}, - {name: "GROUP_TYPE", content: group.type}, - {name: "GROUP_ID", content: group._id}, - {name: "GROUP_URL", content: group._id == 'habitrpg' ? '/#/options/groups/tavern' : (group.type === 'guild' ? ('/#/options/groups/guilds/' + group._id) : 'party')}, - ]); + {name: "GROUP_NAME", content: group.name}, + {name: "GROUP_TYPE", content: group.type}, + {name: "GROUP_ID", content: group._id}, + {name: "GROUP_URL", content: group._id == 'habitrpg' ? '/#/options/groups/tavern' : (group.type === 'guild' ? ('/#/options/groups/guilds/' + group._id) : 'party')}, + ]); return res.send(204); }); }); - } api.clearFlagCount = function(req, res, next){ @@ -416,10 +417,9 @@ api.clearFlagCount = function(req, res, next){ if(err) return next(err); return res.send(204); }); - }else{ + } else { return res.json(401, {err: shared.i18n.t('messageGroupChatAdminClearFlagCount')}) } - } api.seenMessage = function(req,res,next){ @@ -437,6 +437,7 @@ api.likeChatMessage = function(req, res, next) { var user = res.locals.user; var group = res.locals.group; var message = _.find(group.chat, {id: req.params.mid}); + if (!message) return res.json(404, {err: shared.i18n.t('messageGroupChatNotFound')}); if (message.uuid == user._id) return res.json(401, {err: shared.i18n.t('messageGroupChatLikeOwnMessage')}); if (!message.likes) message.likes = {}; @@ -445,14 +446,13 @@ api.likeChatMessage = function(req, res, next) { } else { message.likes[user._id] = true; } - group.markModified('chat'); - group.save(function(err,_saved){ + + Group.update({_id: group._id, 'chat.id': message.id}, {'$set': { + 'chat.$.likes': message.likes + }}, function(err) { if (err) return next(err); - // @TODO: We're sending back the entire array of chats back - // Should we just send back the object of the single chat message? - // If not, should we update the group chat when a chat is liked? - return res.send(_saved.chat); - }) + return res.send(group.chat); + }); } api.join = function(req, res, next) {