SECURITY: Prevent unauthorized access to grouped poll results

This adds access controls for the `/polls/grouped_poll_results`
endpoint, such that only users with appropriate permissions can read
the grouped results of a given poll.
This commit is contained in:
Kelvin Tan 2023-10-01 23:10:35 +08:00 committed by Penar Musaraj
parent 4cb7472376
commit ee084b754e
No known key found for this signature in database
GPG Key ID: E390435D881FF0F7
2 changed files with 127 additions and 2 deletions

View File

@ -198,11 +198,19 @@ class DiscoursePoll::Poll
def self.grouped_poll_results(user, post_id, poll_name, user_field_name)
raise Discourse::InvalidParameters.new(:post_id) if !Post.where(id: post_id).exists?
poll =
Poll.includes(:poll_options).includes(:poll_votes).find_by(post_id: post_id, name: poll_name)
Poll.includes(:poll_options, :poll_votes, post: :topic).find_by(
post_id: post_id,
name: poll_name,
)
raise Discourse::InvalidParameters.new(:poll_name) unless poll
# user must be allowed to post in topic
guardian = Guardian.new(user)
if !guardian.can_create_post?(poll.post.topic)
raise DiscoursePoll::Error.new I18n.t("poll.user_cant_post_in_topic")
end
unless SiteSetting.poll_groupable_user_fields.split("|").include?(user_field_name)
raise Discourse::InvalidParameters.new(:user_field_name)
end

View File

@ -147,6 +147,7 @@ RSpec.describe "DiscoursePoll endpoints" do
let(:option_b) { "e89dec30bbd9bf50fabf6a05b4324edf" }
before do
sign_in(user1)
user_votes = { user_0: option_a, user_1: option_a, user_2: option_b }
[user1, user2, user3].each_with_index do |user, index|
@ -219,5 +220,121 @@ RSpec.describe "DiscoursePoll endpoints" do
expect(response.status).to eq(400)
expect(response.body).to include("user_field_name")
end
context "when topic is in a private category" do
fab!(:admin) { Fabricate(:admin) }
fab!(:group) { Fabricate(:group) }
fab!(:private_category) { Fabricate(:private_category, group: group) }
fab!(:private_topic) { Fabricate(:topic, category: private_category) }
fab!(:private_post) { Fabricate(:post, topic: private_topic, raw: <<~SQL) }
[poll type=multiple public=true min=1 max=2]
- A
- B
[/poll]
SQL
let(:groupable_user_field) { "anything" }
let(:expected_results) do
{
grouped_results: [
{
group: "Value0",
options: [
{ digest: option_a, html: "A", votes: 1 },
{ digest: option_b, html: "B", votes: 0 },
],
},
{
group: "Value1",
options: [
{ digest: option_a, html: "A", votes: 2 },
{ digest: option_b, html: "B", votes: 1 },
],
},
{
group: "Value2",
options: [
{ digest: option_a, html: "A", votes: 0 },
{ digest: option_b, html: "B", votes: 1 },
],
},
],
}
end
before do
user_votes = { user_0: option_a, user_1: option_a, user_2: option_b }
SiteSetting.poll_groupable_user_fields = groupable_user_field
[user1, user2, user3].each_with_index do |user, index|
group.add(user)
DiscoursePoll::Poll.vote(
user,
private_post.id,
DiscoursePoll::DEFAULT_POLL_NAME,
[user_votes["user_#{index}".to_sym]],
)
UserCustomField.create(
user_id: user.id,
name: groupable_user_field,
value: "value#{index}",
)
end
# Add another user to one of the fields to prove it groups users properly
group.add(user4)
DiscoursePoll::Poll.vote(
user4,
private_post.id,
DiscoursePoll::DEFAULT_POLL_NAME,
[option_a, option_b],
)
UserCustomField.create(user_id: user4.id, name: groupable_user_field, value: "value1")
end
it "returns grouped poll results for admin based on user field" do
sign_in(admin)
get "/polls/grouped_poll_results.json",
params: {
post_id: private_post.id,
poll_name: DiscoursePoll::DEFAULT_POLL_NAME,
user_field_name: groupable_user_field,
}
expect(response).to have_http_status :success
expect(response.parsed_body.deep_symbolize_keys).to eq(expected_results)
end
it "returns grouped poll results for user within private group based on user field" do
user = Fabricate(:user)
group.add(user)
sign_in(user)
get "/polls/grouped_poll_results.json",
params: {
post_id: private_post.id,
poll_name: DiscoursePoll::DEFAULT_POLL_NAME,
user_field_name: groupable_user_field,
}
expect(response).to have_http_status :success
expect(response.parsed_body.deep_symbolize_keys).to eq(expected_results)
end
it "returns an error when user does not have access to topic category" do
user = Fabricate(:user)
sign_in(user)
get "/polls/grouped_poll_results.json",
params: {
post_id: private_post.id,
poll_name: DiscoursePoll::DEFAULT_POLL_NAME,
user_field_name: groupable_user_field,
}
expect(response).to have_http_status :unprocessable_entity
expect(response.parsed_body["errors"][0]).to eq(I18n.t("poll.user_cant_post_in_topic"))
end
end
end
end