FIX: Better and more secure validation of periods for TopicQuery

Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
This commit is contained in:
Robin Ward
2021-07-23 14:24:44 -04:00
co-authored by Martin Brennan
parent c7beb0b9a6
commit 7b45a5ce55
10 changed files with 68 additions and 75 deletions
+15 -9
View File
@@ -266,18 +266,22 @@ class TopicQuery
end
def list_top_for(period)
if !TopTopic.periods.include?(period.to_sym)
raise Discourse::InvalidParameters.new("Invalid period. Valid periods are #{TopTopic.periods.join(", ")}")
end
score = "#{period}_score"
score_column = TopTopic.score_column_for_period(period)
create_list(:top, unordered: true) do |topics|
topics = remove_muted_categories(topics, @user)
topics = topics.joins(:top_topic).where("top_topics.#{score} > 0")
topics = topics.joins(:top_topic).where("top_topics.#{score_column} > 0")
if period == :yearly && @user.try(:trust_level) == TrustLevel[0]
topics.order(TopicQuerySQL.order_top_with_pinned_category_for(score))
topics.order(<<~SQL)
CASE WHEN (
COALESCE(topics.pinned_at, '1900-01-01') > COALESCE(tu.cleared_pinned_at, '1900-01-01')
) THEN 0 ELSE 1 END,
top_topics.#{score_column} DESC,
topics.bumped_at DESC
SQL
else
topics.order(TopicQuerySQL.order_top_for(score))
topics.order(<<~SQL)
COALESCE(top_topics.#{score_column}, 0) DESC, topics.bumped_at DESC
SQL
end
end
end
@@ -676,7 +680,9 @@ class TopicQuery
# If we are sorting by category, actually use the name
if sort_column == 'category_id'
# TODO forces a table scan, slow
return result.references(:categories).order(TopicQuerySQL.order_by_category_sql(sort_dir))
return result.references(:categories).order(<<~SQL)
CASE WHEN categories.id = #{SiteSetting.uncategorized_category_id.to_i} THEN '' ELSE categories.name END #{sort_dir}
SQL
end
if sort_column == 'op_likes'
-60
View File
@@ -1,60 +0,0 @@
# frozen_string_literal: true
#
# SQL fragments used when querying a list of topics.
#
module TopicQuerySQL
class << self
def lowest_date
"1900-01-01"
end
def order_by_category_sql(dir)
-"CASE WHEN categories.id = #{SiteSetting.uncategorized_category_id.to_i} THEN '' ELSE categories.name END #{dir}"
end
# If you've cleared the pin, use bumped_at, otherwise put it at the top
def order_with_pinned_sql
-"CASE
WHEN (COALESCE(topics.pinned_at, '#{lowest_date}') > COALESCE(tu.cleared_pinned_at, '#{lowest_date}'))
THEN topics.pinned_at + interval '9999 years'
ELSE topics.bumped_at
END DESC"
end
# If you've cleared the pin, use bumped_at, otherwise put it at the top
def order_nocategory_with_pinned_sql
-"CASE
WHEN topics.pinned_globally
AND (COALESCE(topics.pinned_at, '#{lowest_date}') > COALESCE(tu.cleared_pinned_at, '#{lowest_date}'))
THEN topics.pinned_at + interval '9999 years'
ELSE topics.bumped_at
END DESC"
end
def order_basic_bumped
"CASE WHEN (topics.pinned_at IS NOT NULL) THEN 0 ELSE 1 END, topics.bumped_at DESC"
end
def order_nocategory_basic_bumped
"CASE WHEN topics.pinned_globally AND (topics.pinned_at IS NOT NULL) THEN 0 ELSE 1 END, topics.bumped_at DESC"
end
def order_top_for(score)
-"COALESCE(top_topics.#{score}, 0) DESC, topics.bumped_at DESC"
end
def order_top_with_pinned_category_for(score)
# display pinned topics first
-"CASE WHEN (COALESCE(topics.pinned_at, '#{lowest_date}') > COALESCE(tu.cleared_pinned_at, '#{lowest_date}')) THEN 0 ELSE 1 END,
top_topics.#{score} DESC,
topics.bumped_at DESC"
end
def order_top_with_notification_levels(score)
-"COALESCE(topic_users.notification_level, 1) DESC, COALESCE(category_users.notification_level, 1) DESC, COALESCE(top_topics.#{score}, 0) DESC, topics.bumped_at DESC"
end
end
end
-1
View File
@@ -11,7 +11,6 @@ module ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector
'pop3_polling_enabled_setting_validator' => 'POP3PollingEnabledSettingValidator',
'regular' => 'Jobs',
'scheduled' => 'Jobs',
'topic_query_sql' => 'TopicQuerySQL',
'version' => 'Discourse',
}