# frozen_string_literal: true #mixin for all guardian methods dealing with topic permissions module TopicGuardian def can_remove_allowed_users?(topic, target_user = nil) is_staff? || (topic.user == @user && @user.has_trust_level?(TrustLevel[2])) || ( topic.allowed_users.count > 1 && topic.user != target_user && !!(target_user && user == target_user) ) end def can_review_topic?(topic) return false if anonymous? || topic.nil? return true if is_staff? is_category_group_moderator?(topic.category) end def can_moderate_topic?(topic) return false if anonymous? || topic.nil? return true if is_staff? can_perform_action_available_to_group_moderators?(topic) end def can_create_shared_draft? SiteSetting.shared_drafts_enabled? && can_see_shared_draft? end def can_see_shared_draft? @user.in_any_groups?(SiteSetting.shared_drafts_allowed_groups_map) end def can_create_whisper? @user.whisperer? end def can_see_whispers?(_topic = nil) @user.whisperer? end def can_publish_topic?(topic, category) can_see_shared_draft? && can_see?(topic) && can_create_topic_on_category?(category) end # Creating Methods def can_create_topic?(parent) is_staff? || ( user && user.trust_level >= SiteSetting.min_trust_to_create_topic.to_i && can_create_post?(parent) && Category.topic_create_allowed(self).limit(1).count == 1 ) end def can_create_topic_on_category?(category) # allow for category to be a number as well category_id = Category === category ? category.id : category can_create_topic?(nil) && (!category || Category.topic_create_allowed(self).where(id: category_id).count == 1) end def can_move_topic_to_category?(category) category = ( if Category === category category else Category.find(category || SiteSetting.uncategorized_category_id) end ) is_staff? || (can_create_topic_on_category?(category) && !category.require_topic_approval?) end def can_create_post_on_topic?(topic) # No users can create posts on deleted topics return false if topic.blank? return false if topic.trashed? return true if is_admin? trusted = (authenticated? && user.has_trust_level?(TrustLevel[4])) || is_moderator? || can_perform_action_available_to_group_moderators?(topic) (!(topic.closed? || topic.archived?) || trusted) && can_create_post?(topic) end # Editing Method def can_edit_topic?(topic) return false if Discourse.static_doc_topic_ids.include?(topic.id) && !is_admin? return false unless can_see?(topic) first_post = topic.first_post return false if first_post&.locked? && !is_staff? return true if is_admin? return true if is_moderator? && can_create_post?(topic) return true if is_category_group_moderator?(topic.category) # can't edit topics in secured categories where you don't have permission to create topics # except for a tiny edge case where the topic is uncategorized and you are trying # to fix it but uncategorized is disabled if ( SiteSetting.allow_uncategorized_topics || topic.category_id != SiteSetting.uncategorized_category_id ) return false if !can_create_topic_on_category?(topic.category) end # Editing a shared draft. if ( !topic.archived && !topic.private_message? && topic.category_id == SiteSetting.shared_drafts_category.to_i && can_see_category?(topic.category) && can_see_shared_draft? && can_create_post?(topic) ) return true end if ( is_in_edit_post_groups? && topic.archived && !topic.private_message? && can_create_post?(topic) ) return true end if ( is_in_edit_topic_groups? && !topic.archived && !topic.private_message? && can_create_post?(topic) ) return true end return false if topic.archived is_my_own?(topic) && !topic.edit_time_limit_expired?(user) && !first_post&.locked? && (!first_post&.hidden? || can_edit_hidden_post?(first_post)) end def is_in_edit_topic_groups? SiteSetting.edit_all_topic_groups.present? && user.in_any_groups?(SiteSetting.edit_all_topic_groups.to_s.split("|").map(&:to_i)) end def can_recover_topic?(topic) if is_staff? || (topic&.category && is_category_group_moderator?(topic.category)) || (SiteSetting.tl4_delete_posts_and_topics && user&.has_trust_level?(TrustLevel[4])) !!(topic && topic.deleted_at) else topic && can_recover_post?(topic.ordered_posts.first) end end def can_delete_topic?(topic) !topic.trashed? && ( is_staff? || ( is_my_own?(topic) && topic.posts_count <= 1 && topic.created_at && topic.created_at > 24.hours.ago ) || is_category_group_moderator?(topic.category) || (SiteSetting.tl4_delete_posts_and_topics && user.has_trust_level?(TrustLevel[4])) ) && !topic.is_category_topic? && !Discourse.static_doc_topic_ids.include?(topic.id) end def can_permanently_delete_topic?(topic) return false if !SiteSetting.can_permanently_delete return false if !topic # Ensure that all posts (including small actions) are at least soft # deleted. return false if topic.posts_count > 0 # All other posts that were deleted still must be permanently deleted # before the topic can be deleted with the exception of small action # posts that will be deleted right before the topic is. all_posts_count = Post .with_deleted .where(topic_id: topic.id) .where( post_type: [Post.types[:regular], Post.types[:moderator_action], Post.types[:whisper]], ) .count return false if all_posts_count > 1 return false if !is_admin? || !can_see_topic?(topic) return false if !topic.deleted_at if topic.deleted_by_id == @user.id && topic.deleted_at >= Post::PERMANENT_DELETE_TIMER.ago return false end true end def can_toggle_topic_visibility?(topic) can_moderate?(topic) || can_perform_action_available_to_group_moderators?(topic) end alias can_create_unlisted_topic? can_toggle_topic_visibility? def can_convert_topic?(topic) return false if topic.blank? return false if topic.trashed? return false if topic.is_category_topic? return true if is_admin? return false if !@user.in_any_groups?(SiteSetting.personal_message_enabled_groups_map) is_moderator? && can_create_post?(topic) end def can_reply_as_new_topic?(topic) authenticated? && topic && @user.has_trust_level?(TrustLevel[1]) end def can_see_deleted_topics?(category) is_staff? || is_category_group_moderator?(category) || (SiteSetting.tl4_delete_posts_and_topics && user&.has_trust_level?(TrustLevel[4])) end # Accepts an array of `Topic#id` and returns an array of `Topic#id` which the user can see. def can_see_topic_ids(topic_ids: [], hide_deleted: true) topic_ids = topic_ids.compact return topic_ids if is_admin? return [] if topic_ids.blank? default_scope = Topic.unscoped.where(id: topic_ids) # When `hide_deleted` is `true`, hide deleted topics if user is not staff or category moderator if hide_deleted && !is_staff? if category_group_moderation_allowed? default_scope = default_scope.where(<<~SQL) ( deleted_at IS NULL OR ( deleted_at IS NOT NULL AND topics.category_id IN (#{category_group_moderator_scope.select(:id).to_sql}) ) ) SQL else default_scope = default_scope.where("deleted_at IS NULL") end end # Filter out topics with shared drafts if user cannot see shared drafts if !can_see_shared_draft? default_scope = default_scope.left_outer_joins(:shared_draft).where("shared_drafts.id IS NULL") end all_topics_scope = if authenticated? Topic.unscoped.merge( secured_regular_topic_scope(default_scope, topic_ids: topic_ids).or( private_message_topic_scope(default_scope), ), ) else Topic.unscoped.merge(secured_regular_topic_scope(default_scope, topic_ids: topic_ids)) end all_topics_scope.pluck(:id) end def can_see_topic?(topic, hide_deleted = true) return false unless topic return true if is_admin? return false if hide_deleted && topic.deleted_at && !can_see_deleted_topics?(topic.category) if topic.private_message? return authenticated? && topic.all_allowed_users.where(id: @user.id).exists? end return false if topic.shared_draft && !can_see_shared_draft? category = topic.category can_see_category?(category) && ( !category.read_restricted || !is_staged? || secure_category_ids.include?(category.id) || topic.user == user ) end def can_see_unlisted_topics? is_staff? || @user.has_trust_level?(TrustLevel[4]) end def can_get_access_to_topic?(topic) topic&.access_topic_via_group.present? && authenticated? end def filter_allowed_categories(records, category_id_column: "topics.category_id") return records if is_admin? && !SiteSetting.suppress_secured_categories_from_admin records = if allowed_category_ids.size == 0 records.where("#{category_id_column} IS NULL") else records.where( "#{category_id_column} IS NULL or #{category_id_column} IN (?)", allowed_category_ids, ) end records.references(:categories) end def can_edit_featured_link?(category_id) return false unless SiteSetting.topic_featured_link_enabled return false if @user.trust_level == TrustLevel.levels[:newuser] Category.where( id: category_id || SiteSetting.uncategorized_category_id, topic_featured_link_allowed: true, ).exists? end def can_update_bumped_at? is_staff? || @user.has_trust_level?(TrustLevel[4]) end def can_banner_topic?(topic) topic && authenticated? && !topic.private_message? && is_staff? end def can_edit_tags?(topic) return false unless can_tag_topics? return false if topic.private_message? && !can_tag_pms? return true if can_edit_topic?(topic) if topic&.first_post&.wiki && @user.in_any_groups?(SiteSetting.edit_wiki_post_allowed_groups_map) return can_create_post?(topic) end false end def can_perform_action_available_to_group_moderators?(topic) return false if anonymous? || topic.nil? return true if is_staff? return true if @user.has_trust_level?(TrustLevel[4]) is_category_group_moderator?(topic.category) end alias can_archive_topic? can_perform_action_available_to_group_moderators? alias can_close_topic? can_perform_action_available_to_group_moderators? alias can_open_topic? can_perform_action_available_to_group_moderators? alias can_split_merge_topic? can_perform_action_available_to_group_moderators? alias can_edit_staff_notes? can_perform_action_available_to_group_moderators? alias can_pin_unpin_topic? can_perform_action_available_to_group_moderators? def can_move_posts?(topic) return false if is_silenced? can_perform_action_available_to_group_moderators?(topic) end def affected_by_slow_mode?(topic) topic&.slow_mode_seconds.to_i > 0 && @user.human? && !is_staff? end private def private_message_topic_scope(scope) pm_scope = scope.private_messages_for_user(user) pm_scope = pm_scope.or(scope.where(<<~SQL)) if is_moderator? topics.subtype = '#{TopicSubtype.moderator_warning}' OR topics.id IN (#{Topic.has_flag_scope.select(:topic_id).to_sql}) SQL pm_scope end def secured_regular_topic_scope(scope, topic_ids:) secured_scope = Topic.unscoped.secured(self) # Staged users are allowed to see their own topics in read restricted categories when Category#email_in and # Category#email_in_allow_strangers has been configured. if is_staged? sql = <<~SQL topics.id IN ( SELECT topics.id FROM topics INNER JOIN categories ON categories.id = topics.category_id WHERE categories.read_restricted AND categories.email_in IS NOT NULL AND categories.email_in_allow_strangers AND topics.user_id = :user_id AND topics.id IN (:topic_ids) ) SQL secured_scope = secured_scope.or(Topic.unscoped.where(sql, user_id: user.id, topic_ids: topic_ids)) end scope.listable_topics.merge(secured_scope) end end