FIX: Prevent column name conflicts in reviewable code (#9753)

We were getting errors like this in Reviewables in some cases:

```
ActiveRecord::StatementInvalid (PG::AmbiguousColumn: ERROR:  column reference "category_id" is ambiguous
LINE 4: ...TRUE) OR (reviewable_by_group_id IN (NULL))) AND (category_i...
```

The problem that was making everything go boom is that plugins can add their own custom filters for Reviewables. If one is doing an INNER JOIN on topics, which has its own category_id column, we would get the above AmbiguousColumn error. The solution here is to just make all references to the reviewable columns in the list_for and viewable_by code prefixed by the table name e.g. reviewables.category_id.
This commit is contained in:
Martin Brennan 2020-05-13 09:05:56 +10:00 committed by GitHub
parent e990d8adce
commit 9981fa4466
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 43 additions and 15 deletions

View File

@ -406,7 +406,7 @@ class Reviewable < ActiveRecord::Base
def self.viewable_by(user, order: nil, preload: true) def self.viewable_by(user, order: nil, preload: true)
return none unless user.present? return none unless user.present?
result = self.order(order || 'score desc, created_at desc') result = self.order(order || 'reviewables.score desc, reviewables.created_at desc')
if preload if preload
result = result.includes( result = result.includes(
@ -422,10 +422,10 @@ class Reviewable < ActiveRecord::Base
group_ids = SiteSetting.enable_category_group_review? ? user.group_users.pluck(:group_id) : [] group_ids = SiteSetting.enable_category_group_review? ? user.group_users.pluck(:group_id) : []
result.where( result.where(
'(reviewable_by_moderator AND :staff) OR (reviewable_by_group_id IN (:group_ids))', '(reviewables.reviewable_by_moderator AND :staff) OR (reviewables.reviewable_by_group_id IN (:group_ids))',
staff: user.staff?, staff: user.staff?,
group_ids: group_ids group_ids: group_ids
).where("category_id IS NULL OR category_id IN (?)", Guardian.new(user).allowed_category_ids) ).where("reviewables.category_id IS NULL OR reviewables.category_id IN (?)", Guardian.new(user).allowed_category_ids)
end end
def self.pending_count(user) def self.pending_count(user)
@ -451,13 +451,13 @@ class Reviewable < ActiveRecord::Base
order = case sort_order order = case sort_order
when 'priority_asc' when 'priority_asc'
'score ASC, created_at DESC' 'reviewables.score ASC, reviewables.created_at DESC'
when 'created_at' when 'created_at'
'created_at DESC, score DESC' 'reviewables.created_at DESC, reviewables.score DESC'
when 'created_at_asc' when 'created_at_asc'
'created_at ASC, score DESC' 'reviewables.created_at ASC, reviewables.score DESC'
else else
'score DESC, created_at DESC' 'reviewables.score DESC, reviewables.created_at DESC'
end end
if username.present? if username.present?
@ -469,19 +469,19 @@ class Reviewable < ActiveRecord::Base
result = viewable_by(user, order: order) result = viewable_by(user, order: order)
result = by_status(result, status) result = by_status(result, status)
result = result.where(type: type) if type result = result.where('reviewables.type = ?', type) if type
result = result.where(category_id: category_id) if category_id result = result.where('reviewables.category_id = ?', category_id) if category_id
result = result.where(topic_id: topic_id) if topic_id result = result.where('reviewables.topic_id = ?', topic_id) if topic_id
result = result.where("created_at >= ?", from_date) if from_date result = result.where("reviewables.created_at >= ?", from_date) if from_date
result = result.where("created_at <= ?", to_date) if to_date result = result.where("reviewables.created_at <= ?", to_date) if to_date
if min_score > 0 && status == :pending && type.nil? if min_score > 0 && status == :pending && type.nil?
result = result.where( result = result.where(
"score >= ? OR type IN (?)", "reviewables.score >= ? OR reviewables.type IN (?)",
min_score, [ReviewableQueuedPost.name, ReviewableUser.name] min_score, [ReviewableQueuedPost.name, ReviewableUser.name]
) )
elsif min_score > 0 elsif min_score > 0
result = result.where("score >= ?", min_score) result = result.where("reviewables.score >= ?", min_score)
end end
if !custom_filters.empty? if !custom_filters.empty?
@ -497,7 +497,8 @@ class Reviewable < ActiveRecord::Base
# If a reviewable doesn't have a target, allow us to filter on who created that reviewable. # If a reviewable doesn't have a target, allow us to filter on who created that reviewable.
if user_id if user_id
result = result.where( result = result.where(
"(target_created_by_id IS NULL AND created_by_id = :user_id) OR (target_created_by_id = :user_id)", "(reviewables.target_created_by_id IS NULL AND reviewables.created_by_id = :user_id)
OR (reviewables.target_created_by_id = :user_id)",
user_id: user_id user_id: user_id
) )
end end

View File

@ -479,6 +479,33 @@ RSpec.describe Reviewable, type: :model do
expect(results.size).to eq(1) expect(results.size).to eq(1)
expect(results.first).to eq first_reviewable expect(results.first).to eq first_reviewable
end end
context "when listing for a moderator with a custom filter that joins tables with same named columns" do
it "should not error" do
first_reviewable = Fabricate(:reviewable)
second_reviewable = Fabricate(:reviewable)
custom_filter = [
:troublemaker,
Proc.new do |results, value|
results.joins(<<~SQL
INNER JOIN posts p ON p.id = target_id
INNER JOIN topics t ON t.id = p.topic_id
INNER JOIN topic_custom_fields tcf ON tcf.topic_id = t.id
INNER JOIN users u ON u.id = tcf.value::integer
SQL
)
.where(target_type: Post.name)
.where('tcf.name = ?', 'troublemaker_user_id')
.where('u.username = ?', value)
end
]
Reviewable.add_custom_filter(custom_filter)
mod = Fabricate(:moderator)
results = Reviewable.list_for(mod, additional_filters: { troublemaker: 'badguy' })
expect { results.first }.not_to raise_error
end
end
end end
describe '.by_status' do describe '.by_status' do