mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
## Summary Prevent unauthorized access to private message excerpts in reviewable responses by ensuring companion conversations are only serialized when the viewer has permission to see the underlying topic. The patch also ensures category moderators receive legitimate access to post-voting flag conversations by including the relevant moderation groups in the private message recipient list. ## Source - Patch Triage: https://patch.discourse.org/patch-triage/1282 Co-authored-by: discourse-patch-triage <272280883+discourse-patch-triage[bot]@users.noreply.github.com>
147 lines
4.2 KiB
Ruby
Vendored
147 lines
4.2 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
class ReviewableScoreSerializer < ApplicationSerializer
|
|
REASONS_AND_SETTINGS = {
|
|
post_count: "approve_post_count",
|
|
trust_level: "approve_unless_trust_level",
|
|
group: "approve_unless_allowed_groups",
|
|
new_topics_unless_trust_level: "approve_new_topics_unless_trust_level",
|
|
new_topics_unless_allowed_groups: "approve_new_topics_unless_allowed_groups",
|
|
fast_typer: "first_post_typing_time",
|
|
auto_silence_regex: "auto_silence_first_post_regex",
|
|
staged: "approve_unless_staged",
|
|
must_approve_users: "must_approve_users",
|
|
invite_only: "invite_only",
|
|
email_spam: "email_in_spam_header",
|
|
suspect_user: "approve_suspect_users",
|
|
contains_media: "skip_media_review_groups",
|
|
}
|
|
|
|
attributes :id,
|
|
:score,
|
|
:agree_stats,
|
|
:reason,
|
|
:reason_type,
|
|
:reason_data,
|
|
:created_at,
|
|
:reviewed_at
|
|
|
|
attribute :status_for_database, key: :status
|
|
|
|
has_one :user, serializer: BasicUserSerializer, root: "users"
|
|
has_one :score_type, serializer: ReviewableScoreTypeSerializer
|
|
has_one :reviewable_conversation, serializer: ReviewableConversationSerializer
|
|
has_one :reviewed_by, serializer: BasicUserSerializer, root: "users"
|
|
|
|
def include_reviewable_conversation?
|
|
object.meta_topic.present? && scope&.can_see?(object.meta_topic)
|
|
end
|
|
|
|
def agree_stats
|
|
{
|
|
agreed: user.user_stat.flags_agreed,
|
|
disagreed: user.user_stat.flags_disagreed,
|
|
ignored: user.user_stat.flags_ignored,
|
|
}
|
|
end
|
|
|
|
def reason
|
|
return unless object.reason
|
|
|
|
link_text = setting_name_for_reason(object.reason)
|
|
link_text = I18n.t("reviewables.reasons.links.#{object.reason}", default: nil) if link_text.nil?
|
|
|
|
if link_text
|
|
link = build_link_for(object.reason, link_text)
|
|
|
|
if object.reason == "watched_word"
|
|
text = watched_word_reason(link)
|
|
else
|
|
text = I18n.t("reviewables.reasons.#{object.reason}", link: link, default: object.reason)
|
|
end
|
|
else
|
|
text = I18n.t("reviewables.reasons.#{object.reason}", default: object.reason)
|
|
end
|
|
|
|
text
|
|
end
|
|
|
|
def reason_type
|
|
object.reason
|
|
end
|
|
|
|
def reason_data
|
|
watched_words_found if object.reason == "watched_word"
|
|
end
|
|
|
|
def include_reason?
|
|
reason.present?
|
|
end
|
|
|
|
def setting_name_for_reason(reason)
|
|
setting_name = REASONS_AND_SETTINGS[reason.to_sym]
|
|
|
|
if setting_name.nil?
|
|
plugin_options = DiscoursePluginRegistry.reviewable_score_links
|
|
option = plugin_options.detect { |o| o[:reason] == reason.to_sym }
|
|
|
|
setting_name = option[:setting] if option
|
|
end
|
|
|
|
setting_name
|
|
end
|
|
|
|
private
|
|
|
|
def watched_word_reason(link)
|
|
words = watched_words_found
|
|
|
|
if words.nil? || words.empty?
|
|
I18n.t("reviewables.reasons.no_context.watched_word", link: link, default: "watched_word")
|
|
else
|
|
I18n.t(
|
|
"reviewables.reasons.watched_word",
|
|
link: link,
|
|
words: words.map { |w| CGI.escapeHTML(w) }.join(", "),
|
|
count: words.length,
|
|
default: "watched_word",
|
|
)
|
|
end
|
|
end
|
|
|
|
def watched_words_found
|
|
if object.context.nil?
|
|
# If the words weren't recorded, try to guess them based on current settings.
|
|
if object.reviewable.respond_to?(:post)
|
|
s = object.reviewable.post.raw.clone
|
|
s << " #{object.reviewable.post.topic.title}" if object.reviewable.post.post_number == 1
|
|
elsif object.reviewable.respond_to?(:payload)
|
|
s = object.reviewable.payload["raw"].clone
|
|
s << " #{object.reviewable.payload["title"]}" if object.reviewable.payload.key?("title")
|
|
end
|
|
|
|
words = WordWatcher.new(s).word_matches_across_all_actions
|
|
else
|
|
words = object.context.split(",")
|
|
end
|
|
|
|
words.map(&:downcase).uniq
|
|
end
|
|
|
|
def url_for(reason, text)
|
|
case reason
|
|
when "watched_word"
|
|
"#{Discourse.base_url}/admin/customize/watched_words"
|
|
when "category"
|
|
"#{Discourse.base_url}/c/#{object.reviewable.category&.slug}/edit/settings"
|
|
else
|
|
"#{Discourse.base_url}/admin/site_settings/category/all_results?filter=#{text}"
|
|
end
|
|
end
|
|
|
|
def build_link_for(reason, text)
|
|
return text.gsub("_", " ") unless scope.is_staff?
|
|
|
|
"<a href=\"#{url_for(reason, text)}\">#{text.gsub("_", " ")}</a>"
|
|
end
|
|
end
|