mirror of
https://github.com/discourse/discourse.git
synced 2024-12-23 23:23:53 +08:00
e82e255531
### Why? Before, all flags were static. Therefore, they were stored in class variables and serialized by SiteSerializer. Recently, we added an option for admins to add their own flags or disable existing flags. Therefore, the class variable had to be dropped because it was unsafe for a multisite environment. However, it started causing performance problems. ### Solution When a new Flag system is used, instead of using PostActionType, we can serialize Flags and use fragment cache for performance reasons. At the same time, we are still supporting deprecated `replace_flags` API call. When it is used, we fall back to the old solution and the admin cannot add custom flags. In a couple of months, we will be able to drop that API function and clean that code properly. However, because it may still be used, redis cache was introduced to improve performance. To test backward compatibility you can add this code to any plugin ```ruby replace_flags do |flag_settings| flag_settings.add( 4, :inappropriate, topic_type: true, notify_type: true, auto_action_type: true, ) flag_settings.add(1001, :trolling, topic_type: true, notify_type: true, auto_action_type: true) end ```
434 lines
13 KiB
Ruby
434 lines
13 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# mixin for all guardian methods dealing with post permissions
|
|
module PostGuardian
|
|
def unrestricted_link_posting?
|
|
authenticated? && (is_staff? || @user.in_any_groups?(SiteSetting.post_links_allowed_groups_map))
|
|
end
|
|
|
|
def link_posting_access
|
|
if unrestricted_link_posting?
|
|
"full"
|
|
elsif SiteSetting.allowed_link_domains.present?
|
|
"limited"
|
|
else
|
|
"none"
|
|
end
|
|
end
|
|
|
|
def can_post_link?(host: nil)
|
|
return false if host.blank?
|
|
|
|
unrestricted_link_posting? || SiteSetting.allowed_link_domains.split("|").include?(host)
|
|
end
|
|
|
|
# Can the user act on the post in a particular way.
|
|
# taken_actions = the list of actions the user has already taken
|
|
def post_can_act?(post, action_key, opts: {}, can_see_post: nil)
|
|
return false if !(can_see_post.nil? && can_see_post?(post)) && !can_see_post
|
|
|
|
# no warnings except for staff
|
|
if action_key == :notify_user &&
|
|
(
|
|
post.user.blank? ||
|
|
(!is_staff? && opts[:is_warning].present? && opts[:is_warning] == "true")
|
|
)
|
|
return false
|
|
end
|
|
|
|
taken = opts[:taken_actions].try(:keys).to_a
|
|
post_action_type_view = opts[:post_action_type_view] || PostActionTypeView.new
|
|
is_flag =
|
|
if (opts[:notify_flag_types] && opts[:additional_message_types])
|
|
opts[:notify_flag_types][action_key] || opts[:additional_message_types][action_key]
|
|
else
|
|
post_action_type_view.notify_flag_types[action_key] ||
|
|
post_action_type_view.additional_message_types[action_key]
|
|
end
|
|
already_taken_this_action =
|
|
taken.any? && taken.include?(post_action_type_view.types[action_key])
|
|
already_did_flagging =
|
|
taken.any? && (taken & post_action_type_view.notify_flag_types.values).any?
|
|
|
|
result =
|
|
if authenticated? && post
|
|
# Allow anonymous users to like if feature is enabled and short-circuit otherwise
|
|
return SiteSetting.allow_anonymous_likes? && (action_key == :like) if @user.anonymous?
|
|
|
|
# Silenced users can't flag
|
|
return false if is_flag && @user.silenced?
|
|
|
|
# Hidden posts can't be flagged
|
|
return false if is_flag && post.hidden?
|
|
|
|
# post made by staff, but we don't allow staff flags
|
|
return false if is_flag && (!SiteSetting.allow_flagging_staff?) && post&.user&.staff?
|
|
|
|
if is_flag && post_action_type_view.disabled_flag_types.keys.include?(action_key)
|
|
return false
|
|
end
|
|
|
|
if action_key == :notify_user &&
|
|
!@user.in_any_groups?(SiteSetting.personal_message_enabled_groups_map)
|
|
# The modifier below is used to add additional permissions for notifying users.
|
|
# In core the only method of notifying a user is personal messages so we check if the
|
|
# user can PM. Plugins can extend the behavior of how users are notifier via `notify_user`
|
|
# post action, and this allows extension for that use case.
|
|
can_notify = false
|
|
can_notify =
|
|
DiscoursePluginRegistry.apply_modifier(
|
|
:post_guardian_can_notify_user,
|
|
can_notify,
|
|
self,
|
|
post,
|
|
)
|
|
return can_notify
|
|
end
|
|
|
|
# we allow flagging for trust level 1 and higher
|
|
# always allowed for private messages
|
|
(
|
|
is_flag && not(already_did_flagging) &&
|
|
(
|
|
@user.in_any_groups?(SiteSetting.flag_post_allowed_groups_map) ||
|
|
post.topic.private_message?
|
|
)
|
|
) ||
|
|
# not a flagging action, and haven't done it already
|
|
not(is_flag || already_taken_this_action) &&
|
|
# nothing except flagging on archived topics
|
|
not(post.topic&.archived?) &&
|
|
# nothing except flagging on deleted posts
|
|
not(post.trashed?) &&
|
|
# don't like your own stuff
|
|
not(action_key == :like && (post.user.blank? || is_my_own?(post)))
|
|
end
|
|
|
|
!!result
|
|
end
|
|
|
|
def can_lock_post?(post)
|
|
can_see_post?(post) && is_staff?
|
|
end
|
|
|
|
# Can we see who acted on a post in a particular way?
|
|
def can_see_post_actors?(topic, post_action_type_id)
|
|
return true if is_admin?
|
|
return false unless topic
|
|
|
|
post_action_type_view = PostActionTypeView.new
|
|
type_symbol = post_action_type_view.types[post_action_type_id]
|
|
|
|
return false if type_symbol == :bookmark
|
|
return false if type_symbol == :notify_user && !is_moderator?
|
|
|
|
return can_see_flags?(topic) if post_action_type_view.is_flag?(type_symbol)
|
|
|
|
true
|
|
end
|
|
|
|
def can_delete_all_posts?(user)
|
|
is_staff? && user && !user.admin? &&
|
|
(
|
|
is_admin? ||
|
|
(
|
|
(
|
|
user.first_post_created_at.nil? ||
|
|
user.first_post_created_at >= SiteSetting.delete_user_max_post_age.days.ago
|
|
) && user.post_count <= SiteSetting.delete_all_posts_max.to_i
|
|
)
|
|
)
|
|
end
|
|
|
|
def can_create_post?(topic)
|
|
return can_create_post_in_topic?(topic) if !topic
|
|
|
|
key = topic_memoize_key(topic)
|
|
@can_create_post ||= {}
|
|
|
|
@can_create_post.fetch(key) { @can_create_post[key] = can_create_post_in_topic?(topic) }
|
|
end
|
|
|
|
def can_edit_post?(post)
|
|
return false if Discourse.static_doc_topic_ids.include?(post.topic_id) && !is_admin?
|
|
|
|
return true if is_admin?
|
|
|
|
# Must be staff to edit a locked post
|
|
return false if post.locked? && !is_staff?
|
|
|
|
if (is_staff? || is_in_edit_post_groups? || is_category_group_moderator?(post.topic&.category))
|
|
return can_create_post?(post.topic)
|
|
end
|
|
|
|
return false if post.topic&.archived? || post.user_deleted || post.deleted_at
|
|
|
|
# Editing a shared draft.
|
|
if (
|
|
can_see_post?(post) && can_create_post?(post.topic) &&
|
|
post.topic.category_id == SiteSetting.shared_drafts_category.to_i &&
|
|
can_see_category?(post.topic.category) && can_see_shared_draft?
|
|
)
|
|
return true
|
|
end
|
|
|
|
if post.wiki && @user.in_any_groups?(SiteSetting.edit_wiki_post_allowed_groups_map)
|
|
return can_create_post?(post.topic)
|
|
end
|
|
|
|
return false if !trusted_with_post_edits?
|
|
|
|
if is_my_own?(post)
|
|
return false if @user.silenced?
|
|
|
|
return can_edit_hidden_post?(post) if post.hidden?
|
|
|
|
if post.is_first_post? && post.topic.category_allows_unlimited_owner_edits_on_first_post?
|
|
return true
|
|
end
|
|
|
|
return !post.edit_time_limit_expired?(@user)
|
|
end
|
|
|
|
if post.is_category_description?
|
|
return true if can_edit_category_description?(post.topic.category)
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
def is_in_edit_post_groups?
|
|
SiteSetting.edit_all_post_groups.present? &&
|
|
user.in_any_groups?(SiteSetting.edit_all_post_groups.to_s.split("|").map(&:to_i))
|
|
end
|
|
|
|
def can_edit_hidden_post?(post)
|
|
return false if post.nil?
|
|
post.hidden_at.nil? ||
|
|
post.hidden_at < SiteSetting.cooldown_minutes_after_hiding_posts.minutes.ago
|
|
end
|
|
|
|
def can_delete_post_or_topic?(post)
|
|
post.is_first_post? ? post.topic && can_delete_topic?(post.topic) : can_delete_post?(post)
|
|
end
|
|
|
|
def can_delete_post?(post)
|
|
return false if !can_see_post?(post)
|
|
|
|
# Can't delete the first post
|
|
return false if post.is_first_post?
|
|
|
|
return true if is_staff? || is_category_group_moderator?(post.topic&.category)
|
|
|
|
return true if user.in_any_groups?(SiteSetting.delete_all_posts_and_topics_allowed_groups_map)
|
|
|
|
# Can't delete posts in archived topics unless you are staff
|
|
return false if post.topic&.archived?
|
|
|
|
# You can delete your own posts
|
|
if is_my_own?(post)
|
|
if (
|
|
SiteSetting.max_post_deletions_per_minute < 1 ||
|
|
SiteSetting.max_post_deletions_per_day < 1
|
|
)
|
|
return false
|
|
end
|
|
return true if !post.user_deleted?
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
def can_permanently_delete_post?(post)
|
|
return false if !SiteSetting.can_permanently_delete
|
|
return false if !post
|
|
return false if post.is_first_post?
|
|
return false if !is_admin? || !can_edit_post?(post)
|
|
return false if !post.deleted_at
|
|
if post.deleted_by_id == @user.id && post.deleted_at >= Post::PERMANENT_DELETE_TIMER.ago
|
|
return false
|
|
end
|
|
true
|
|
end
|
|
|
|
def can_recover_post?(post)
|
|
return false unless post
|
|
|
|
# PERF, vast majority of the time topic will not be deleted
|
|
topic = (post.topic || Topic.with_deleted.find(post.topic_id)) if post.topic_id
|
|
return true if can_moderate_topic?(topic) && !!post.deleted_at
|
|
|
|
if is_my_own?(post)
|
|
if (
|
|
SiteSetting.max_post_deletions_per_minute < 1 ||
|
|
SiteSetting.max_post_deletions_per_day < 1
|
|
)
|
|
return false
|
|
end
|
|
return true if post.user_deleted && !post.deleted_at
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
def can_delete_post_action?(post_action)
|
|
return false unless is_my_own?(post_action) && !post_action.is_private_message?
|
|
|
|
ok_to_delete =
|
|
post_action.created_at > SiteSetting.post_undo_action_window_mins.minutes.ago &&
|
|
!post_action.post&.topic&.archived?
|
|
|
|
# NOTE: This looks strange...but we are checking if someone is posting anonymously
|
|
# as a AnonymousUser model, _not_ as Guardian::AnonymousUser which is a different thing
|
|
# used when !authenticated?
|
|
if authenticated? && is_anonymous?
|
|
return(
|
|
ok_to_delete && SiteSetting.allow_anonymous_likes? && post_action.is_like? &&
|
|
is_my_own?(post_action)
|
|
)
|
|
end
|
|
|
|
ok_to_delete
|
|
end
|
|
|
|
def can_receive_post_notifications?(post)
|
|
return false if !authenticated?
|
|
|
|
if is_admin? && SiteSetting.suppress_secured_categories_from_admin
|
|
topic = post.topic
|
|
if !topic.private_message? && topic.category.read_restricted
|
|
return secure_category_ids.include?(topic.category_id)
|
|
end
|
|
end
|
|
can_see_post?(post)
|
|
end
|
|
|
|
def can_see_post?(post)
|
|
return false if post.blank?
|
|
return true if is_admin?
|
|
return false unless can_see_post_topic?(post)
|
|
unless post.user == @user || Topic.visible_post_types(@user).include?(post.post_type)
|
|
return false
|
|
end
|
|
return true if is_moderator? || is_category_group_moderator?(post.topic.category)
|
|
if (!post.trashed? || can_see_deleted_post?(post)) &&
|
|
(!post.hidden? || can_see_hidden_post?(post))
|
|
return true
|
|
end
|
|
false
|
|
end
|
|
|
|
def can_see_deleted_post?(post)
|
|
return false if !post.trashed?
|
|
return false if @user.anonymous?
|
|
return true if is_staff?
|
|
post.deleted_by_id == @user.id && @user.has_trust_level?(TrustLevel[4])
|
|
end
|
|
|
|
def can_see_hidden_post?(post)
|
|
if SiteSetting.hidden_post_visible_groups_map.include?(Group::AUTO_GROUPS[:everyone])
|
|
return true
|
|
end
|
|
return false if anonymous?
|
|
return true if is_staff?
|
|
post.user_id == @user.id || @user.in_any_groups?(SiteSetting.hidden_post_visible_groups_map)
|
|
end
|
|
|
|
def can_view_edit_history?(post)
|
|
return false unless post
|
|
|
|
if !post.hidden
|
|
return true if post.wiki || SiteSetting.edit_history_visible_to_public
|
|
end
|
|
|
|
authenticated? && (is_staff? || @user.id == post.user_id) && can_see_post?(post)
|
|
end
|
|
|
|
def can_change_post_owner?
|
|
return true if is_admin?
|
|
|
|
SiteSetting.moderators_change_post_ownership && is_staff?
|
|
end
|
|
|
|
def can_change_post_timestamps?
|
|
is_staff?
|
|
end
|
|
|
|
def can_wiki?(post)
|
|
return false unless authenticated?
|
|
return true if is_staff? || @user.has_trust_level?(TrustLevel[4])
|
|
|
|
if @user.in_any_groups?(SiteSetting.self_wiki_allowed_groups_map) && is_my_own?(post)
|
|
return false if post.hidden?
|
|
return !post.edit_time_limit_expired?(@user)
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
def can_change_post_type?
|
|
is_staff?
|
|
end
|
|
|
|
def can_rebake?
|
|
is_staff? || @user.has_trust_level?(TrustLevel[4])
|
|
end
|
|
|
|
def can_see_flagged_posts?
|
|
is_staff?
|
|
end
|
|
|
|
def can_see_deleted_posts?(category = nil)
|
|
is_staff? || is_category_group_moderator?(category) ||
|
|
@user.in_any_groups?(SiteSetting.delete_all_posts_and_topics_allowed_groups_map)
|
|
end
|
|
|
|
def can_view_raw_email?(post)
|
|
post && @user.in_any_groups?(SiteSetting.view_raw_email_allowed_groups_map)
|
|
end
|
|
|
|
def can_unhide?(post)
|
|
post.try(:hidden) && is_staff?
|
|
end
|
|
|
|
def can_skip_bump?
|
|
is_staff? || @user.has_trust_level?(TrustLevel[4])
|
|
end
|
|
|
|
def trusted_with_post_edits?
|
|
is_staff? || @user.in_any_groups?(SiteSetting.edit_post_allowed_groups_map)
|
|
end
|
|
|
|
private
|
|
|
|
def can_create_post_in_topic?(topic)
|
|
if !SiteSetting.enable_system_message_replies? && topic.try(:subtype) == "system_message"
|
|
return false
|
|
end
|
|
|
|
(
|
|
!SpamRule::AutoSilence.prevent_posting?(@user) ||
|
|
(!!topic.try(:private_message?) && topic.allowed_users.include?(@user))
|
|
) &&
|
|
(
|
|
!topic || !topic.category ||
|
|
Category.post_create_allowed(self).where(id: topic.category.id).count == 1
|
|
)
|
|
end
|
|
|
|
def topic_memoize_key(topic)
|
|
# Milliseconds precision on Topic#updated_at so that we don't use memoized results after topic has been updated.
|
|
"#{topic.id}-#{(topic.updated_at.to_f * 1000).to_i}"
|
|
end
|
|
|
|
def can_see_post_topic?(post)
|
|
topic = post.topic
|
|
return false if !topic
|
|
|
|
key = topic_memoize_key(topic)
|
|
@can_see_post_topic ||= {}
|
|
|
|
@can_see_post_topic.fetch(key) { @can_see_post_topic[key] = can_see_topic?(topic) }
|
|
end
|
|
end
|