SECURITY: Improve topic permission checks

This commit is contained in:
David Taylor 2020-07-17 12:07:45 +01:00
parent 68bebd77f6
commit 4630dab7ac
No known key found for this signature in database
GPG Key ID: 46904C18B1D3F434
6 changed files with 43 additions and 15 deletions

View File

@ -83,6 +83,8 @@ module DiscourseAssign
end end
def assigned def assigned
raise Discourse::InvalidAccess unless current_user&.admin?
offset = (params[:offset] || 0).to_i offset = (params[:offset] || 0).to_i
limit = (params[:limit] || 100).to_i limit = (params[:limit] || 100).to_i

View File

@ -35,8 +35,11 @@ class PendingAssignsReminder
end end
def assigned_topics(user, order:) def assigned_topics(user, order:)
secure = Topic.listable_topics.secured(Guardian.new(user)).or(Topic.private_messages_for_user(user))
Topic.joins(:_custom_fields).select(:slug, :id, :title, :fancy_title, 'topic_custom_fields.created_at AS assigned_at') Topic.joins(:_custom_fields).select(:slug, :id, :title, :fancy_title, 'topic_custom_fields.created_at AS assigned_at')
.where('topic_custom_fields.name = ? AND topic_custom_fields.value = ?', TopicAssigner::ASSIGNED_TO_ID, user.id.to_s) .where('topic_custom_fields.name = ? AND topic_custom_fields.value = ?', TopicAssigner::ASSIGNED_TO_ID, user.id.to_s)
.merge(secure)
.order("topic_custom_fields.created_at #{order}") .order("topic_custom_fields.created_at #{order}")
.limit(3) .limit(3)
end end

View File

@ -136,19 +136,7 @@ class ::TopicAssigner
def can_be_assigned?(assign_to) def can_be_assigned?(assign_to)
return false unless allowed_user_ids.include?(assign_to.id) return false unless allowed_user_ids.include?(assign_to.id)
return true if (!@topic.private_message? || assign_to.admin?) Guardian.new(assign_to).can_see_topic?(@topic)
results = DB.query_single(<<~SQL
SELECT 1
FROM topics
LEFT OUTER JOIN topic_allowed_users tau ON tau.topic_id = topics.id
LEFT OUTER JOIN topic_allowed_groups tag ON tag.topic_id = topics.id
LEFT OUTER JOIN group_users gu ON gu.group_id = tag.group_id
WHERE topics.id = #{@topic.id} AND (gu.user_id = #{assign_to.id} OR tau.user_id = #{assign_to.id})
SQL
)
results.present?
end end
def assign(assign_to, silent: false) def assign(assign_to, silent: false)

View File

@ -31,14 +31,19 @@ RSpec.describe PendingAssignsReminder do
before do before do
add_to_assign_allowed_group(user) add_to_assign_allowed_group(user)
secure_category = Fabricate(:private_category, group: Fabricate(:group))
@post1 = Fabricate(:post) @post1 = Fabricate(:post)
@post2 = Fabricate(:post) @post2 = Fabricate(:post)
@post2.topic.update_column(:fancy_title, nil) @post2.topic.update_column(:fancy_title, nil)
@post3 = Fabricate(:post) @post3 = Fabricate(:post)
@post4 = Fabricate(:post)
TopicAssigner.new(@post1.topic, user).assign(user) TopicAssigner.new(@post1.topic, user).assign(user)
TopicAssigner.new(@post2.topic, user).assign(user) TopicAssigner.new(@post2.topic, user).assign(user)
TopicAssigner.new(@post3.topic, user).assign(user) TopicAssigner.new(@post3.topic, user).assign(user)
TopicAssigner.new(@post4.topic, user).assign(user)
@post3.topic.trash! @post3.topic.trash!
@post4.topic.update(category: secure_category)
end end
it 'creates a reminder for a particular user and sets the timestamp of the last reminder' do it 'creates a reminder for a particular user and sets the timestamp of the last reminder' do
@ -57,11 +62,13 @@ RSpec.describe PendingAssignsReminder do
expect(topic.title).to eq(I18n.t( expect(topic.title).to eq(I18n.t(
'pending_assigns_reminder.title', 'pending_assigns_reminder.title',
pending_assignments: 2 pending_assignments: 3
)) ))
expect(post.raw).to include(@post1.topic.fancy_title) expect(post.raw).to include(@post1.topic.fancy_title)
expect(post.raw).to include(@post2.topic.fancy_title) expect(post.raw).to include(@post2.topic.fancy_title)
expect(post.raw).to_not include(@post3.topic.fancy_title)
expect(post.raw).to_not include(@post4.topic.fancy_title)
expect( expect(
user.reload.custom_fields[described_class::REMINDED_AT].to_datetime user.reload.custom_fields[described_class::REMINDED_AT].to_datetime

View File

@ -32,6 +32,8 @@ RSpec.describe TopicAssigner do
context "assigning and unassigning" do context "assigning and unassigning" do
let(:post) { Fabricate(:post) } let(:post) { Fabricate(:post) }
let(:topic) { post.topic } let(:topic) { post.topic }
let(:secure_category) { Fabricate(:private_category, group: Fabricate(:group)) }
let(:secure_topic) { Fabricate(:post).topic.tap { |t| t.update(category: secure_category) } }
let(:moderator) { Fabricate(:moderator, groups: [assign_allowed_group]) } let(:moderator) { Fabricate(:moderator, groups: [assign_allowed_group]) }
let(:moderator2) { Fabricate(:moderator, groups: [assign_allowed_group]) } let(:moderator2) { Fabricate(:moderator, groups: [assign_allowed_group]) }
let(:assigner) { TopicAssigner.new(topic, moderator2) } let(:assigner) { TopicAssigner.new(topic, moderator2) }
@ -189,13 +191,20 @@ RSpec.describe TopicAssigner do
fab!(:admin) { Fabricate(:admin) } fab!(:admin) { Fabricate(:admin) }
it 'fails to assign when the assigned user cannot view the topic' do it 'fails to assign when the assigned user cannot view the pm' do
assign = TopicAssigner.new(pm, admin).assign(moderator) assign = TopicAssigner.new(pm, admin).assign(moderator)
expect(assign[:success]).to eq(false) expect(assign[:success]).to eq(false)
expect(assign[:reason]).to eq(:forbidden_assign_to) expect(assign[:reason]).to eq(:forbidden_assign_to)
end end
it 'fails to assign when the assigned user cannot view the topic' do
assign = TopicAssigner.new(secure_topic, admin).assign(moderator)
expect(assign[:success]).to eq(false)
expect(assign[:reason]).to eq(:forbidden_assign_to)
end
it "assigns the PM to the moderator when it's included in the list of allowed users" do it "assigns the PM to the moderator when it's included in the list of allowed users" do
pm.allowed_users << moderator pm.allowed_users << moderator

View File

@ -175,6 +175,25 @@ RSpec.describe DiscourseAssign::AssignController do
get '/assign/assigned.json', params: { offset: 2 } get '/assign/assigned.json', params: { offset: 2 }
expect(JSON.parse(response.body)['topics'].map { |t| t['id'] }).to match_array([post1.topic_id]) expect(JSON.parse(response.body)['topics'].map { |t| t['id'] }).to match_array([post1.topic_id])
end end
context "with custom allowed groups" do
let(:custom_allowed_group) { Fabricate(:group, name: 'mygroup') }
let(:other_user) { Fabricate(:user, groups: [custom_allowed_group]) }
before do
SiteSetting.assign_allowed_on_groups += "|#{custom_allowed_group.id}"
end
it 'works for admins' do
get '/assign/assigned.json'
expect(response.status).to eq(200)
end
it 'does not work for other groups' do
sign_in(other_user)
get '/assign/assigned.json'
expect(response.status).to eq(403)
end
end
end end
end end