Skip to content

Commit 5a18c03

Browse files
committed
Enhance message protection during conversation updates and adjust permitted attributes
1 parent 4908afc commit 5a18c03

File tree

3 files changed

+78
-1
lines changed

3 files changed

+78
-1
lines changed

app/controllers/better_together/conversations_controller.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,36 @@ def conversation_params_filtered # rubocop:todo Metrics/AbcSize
233233
permitted_ids = permitted.pluck(:id)
234234
# Always allow the current person (creator/participant) to appear in the list
235235
permitted_ids << helpers.current_person.id if helpers.current_person
236+
236237
cp = conversation_params.dup
238+
239+
# Filter participant_ids to only those the agent may message
237240
if cp[:participant_ids].present?
238241
cp[:participant_ids] = Array(cp[:participant_ids]).map(&:presence).compact & permitted_ids
239242
end
243+
244+
# Protect nested messages on update: only allow creating messages via the create path.
245+
# On update, permit edits only to existing messages that belong to the current person,
246+
# and only allow their content (prevent sender_id spoofing or reassigning other people's messages).
247+
if action_name == 'update' && cp[:messages_attributes].present?
248+
safe_messages = Array(cp[:messages_attributes]).map do |m|
249+
# handle ActionController::Parameters or Hash
250+
attrs = m.respond_to?(:to_h) ? m.to_h : m
251+
msg_id = attrs['id'] || attrs[:id]
252+
next nil unless msg_id
253+
254+
msg = BetterTogether::Message.find_by(id: msg_id)
255+
next nil unless msg && helpers.current_person && msg.sender_id == helpers.current_person.id
256+
257+
# Only allow content edits through this path
258+
{ 'id' => msg_id, 'content' => attrs['content'] || attrs[:content] }
259+
end.compact
260+
261+
# Replace messages_attributes with the vetted set (or nil if none)
262+
cp[:messages_attributes] = safe_messages.presence
263+
end
264+
265+
# On create, leave messages_attributes as-is so nested creation works; controller will set sender.
240266
cp
241267
end
242268

app/models/better_together/message.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Message < ApplicationRecord
1515
# Attributes permitted for strong parameters
1616
def self.permitted_attributes
1717
# include id and _destroy for nested attributes handling
18-
%i[id sender_id].push(:content).push(:_destroy)
18+
%i[id content _destroy]
1919
end
2020
# def content
2121
# super || self[:content]
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe 'Conversation message protection', type: :request do
6+
include RequestSpecHelper
7+
8+
it "prevents a user from altering another user's message via conversation update" do
9+
# Setup: ensure host platform exists and create users with known passwords
10+
configure_host_platform
11+
12+
# Setup: create a manager user (owner of the conversation) and another user
13+
manager_user = create(:user, :confirmed, :platform_manager, email: '[email protected]', password: 'password12345')
14+
other_user = create(:user, :confirmed, email: '[email protected]', password: 'password12345')
15+
16+
# Create a conversation as the manager with a nested message
17+
login(manager_user.email, 'password12345')
18+
19+
post better_together.conversations_path(locale: I18n.default_locale), params: {
20+
conversation: {
21+
title: 'Protected convo',
22+
participant_ids: [manager_user.person.id, other_user.person.id],
23+
messages_attributes: [
24+
{ content: 'Original message' }
25+
]
26+
}
27+
}
28+
29+
expect(response).to have_http_status(:found)
30+
conversation = BetterTogether::Conversation.order(created_at: :desc).first
31+
message = conversation.messages.first
32+
expect(message.content.to_plain_text).to include('Original message')
33+
34+
# Now sign in as other_user and attempt to change manager's message via PATCH
35+
logout
36+
login(other_user.email, 'password12345')
37+
38+
patch better_together.conversation_path(conversation, locale: I18n.default_locale), params: {
39+
conversation: {
40+
title: conversation.title,
41+
messages_attributes: [
42+
{ id: message.id, content: 'Tampered message', sender_id: other_user.person.id }
43+
]
44+
}
45+
}
46+
47+
# Reload message and assert it was not changed
48+
message.reload
49+
expect(message.content.to_plain_text).to include('Original message')
50+
end
51+
end

0 commit comments

Comments
 (0)