# frozen_string_literal: true require "guardian/bookmark_guardian" require "guardian/category_guardian" require "guardian/ensure_magic" require "guardian/group_guardian" require "guardian/post_guardian" require "guardian/post_revision_guardian" require "guardian/sidebar_guardian" require "guardian/tag_guardian" require "guardian/topic_guardian" require "guardian/user_guardian" # The guardian is responsible for confirming access to various site resources and operations class Guardian include BookmarkGuardian include CategoryGuardian include EnsureMagic include GroupGuardian include PostGuardian include PostRevisionGuardian include SidebarGuardian include TagGuardian include TopicGuardian include UserGuardian class AnonymousUser def blank? true end def admin? false end def staff? false end def moderator? false end def anonymous? true end def approved? false end def staged? false end def silenced? false end def is_system_user? false end def bot? false end def secure_category_ids [] end def topic_create_allowed_category_ids [] end def groups [] end def has_trust_level?(level) false end def has_trust_level_or_staff?(level) false end def email nil end def whisperer? false end def in_any_groups?(group_ids) false end end attr_reader :request def initialize(user = nil, request = nil) @user = user.presence || AnonymousUser.new @request = request end def user @user.presence end alias current_user user def anonymous? !authenticated? end def authenticated? @user.present? end def is_admin? @user.admin? end def is_staff? @user.staff? end def is_moderator? @user.moderator? end def is_whisperer? @user.whisperer? end def is_category_group_moderator?(category) return false if !category return false if !category_group_moderation_allowed? reviewable_by_group_id = category.reviewable_by_group_id return false if reviewable_by_group_id.blank? @category_group_moderator_groups ||= {} if @category_group_moderator_groups.key?(reviewable_by_group_id) @category_group_moderator_groups[reviewable_by_group_id] else @category_group_moderator_groups[ reviewable_by_group_id ] = category_group_moderator_scope.exists?("categories.id": category.id) end end def is_silenced? @user.silenced? end def is_developer? @user && is_admin? && ( Rails.env.development? || Developer.user_ids.include?(@user.id) || ( Rails.configuration.respond_to?(:developer_emails) && Rails.configuration.developer_emails.include?(@user.email) ) ) end def is_staged? @user.staged? end def is_anonymous? @user.anonymous? end # Can the user see the object? def can_see?(obj) if obj see_method = method_name_for :see, obj see_method && public_send(see_method, obj) end end def can_create?(klass, parent = nil) return false unless authenticated? && klass # If no parent is provided, we look for a can_create_klass? # custom method. # # If a parent is provided, we look for a method called # can_create_klass_on_parent? target = klass.name.underscore if parent.present? return false unless can_see?(parent) target << "_on_#{parent.class.name.underscore}" end create_method = :"can_create_#{target}?" return public_send(create_method, parent) if respond_to?(create_method) true end def can_enable_safe_mode? SiteSetting.enable_safe_mode? || is_staff? end # Can the user edit the obj def can_edit?(obj) can_do?(:edit, obj) end # Can we delete the object def can_delete?(obj) can_do?(:delete, obj) end def can_permanently_delete?(obj) can_do?(:permanently_delete, obj) end def can_moderate?(obj) obj && authenticated? && !is_silenced? && ( is_staff? || (obj.is_a?(Topic) && @user.has_trust_level?(TrustLevel[4]) && can_see_topic?(obj)) ) end alias can_see_flags? can_moderate? def can_tag?(topic) return false if topic.blank? topic.private_message? ? can_tag_pms? : can_tag_topics? end def can_see_tags?(topic) SiteSetting.tagging_enabled && topic.present? && (!topic.private_message? || can_tag_pms?) end def can_send_activation_email?(user) user && is_staff? && !SiteSetting.must_approve_users? end def can_grant_badges?(_user) SiteSetting.enable_badges && is_staff? end def can_delete_reviewable_queued_post?(reviewable) return false if reviewable.blank? return false if !authenticated? return true if is_api? && is_admin? reviewable.created_by_id == @user.id end def can_see_group?(group) group.present? && can_see_groups?([group]) end def can_see_group_members?(group) return false if group.blank? return true if is_admin? || group.members_visibility_level == Group.visibility_levels[:public] return true if is_staff? && group.members_visibility_level == Group.visibility_levels[:staff] return true if is_staff? && group.members_visibility_level == Group.visibility_levels[:members] if authenticated? && group.members_visibility_level == Group.visibility_levels[:logged_on_users] return true end return false if user.blank? return false unless membership = GroupUser.find_by(group_id: group.id, user_id: user.id) return true if membership.owner return false if group.members_visibility_level == Group.visibility_levels[:owners] return false if group.members_visibility_level == Group.visibility_levels[:staff] true end def can_see_groups?(groups) return false if groups.blank? if is_admin? || groups.all? { |g| g.visibility_level == Group.visibility_levels[:public] } return true end if is_staff? && groups.all? { |g| g.visibility_level == Group.visibility_levels[:staff] } return true end if is_staff? && groups.all? { |g| g.visibility_level == Group.visibility_levels[:members] } return true end if authenticated? && groups.all? { |g| g.visibility_level == Group.visibility_levels[:logged_on_users] } return true end return false if user.blank? memberships = GroupUser.where(group: groups, user_id: user.id).pluck(:owner) return false if memberships.size < groups.size return true if memberships.all? # owner of all groups return false if groups.all? { |g| g.visibility_level == Group.visibility_levels[:owners] } return false if groups.all? { |g| g.visibility_level == Group.visibility_levels[:staff] } true end def can_see_groups_members?(groups) return false if groups.blank? requested_group_ids = groups.map(&:id) # Can't use pluck, groups could be a regular array matching_group_ids = Group.where(id: requested_group_ids).members_visible_groups(user).pluck(:id) matching_group_ids.sort == requested_group_ids.sort end # Can we impersonate this user? def can_impersonate?(target) GlobalSetting.allow_impersonation && target && # You must be an admin to impersonate is_admin? && # You may not impersonate other admins unless you are a dev (!target.admin? || is_developer?) # Additionally, you may not impersonate yourself; # but the two tests for different admin statuses # make it impossible to be the same user. end def can_view_action_logs?(target) target.present? && is_staff? end # Can we approve it? def can_approve?(target) is_staff? && target && target.active? && !target.approved? end def can_activate?(target) is_staff? && target && not(target.active?) end def can_suspend?(user) user && is_staff? && user.regular? end alias can_deactivate? can_suspend? def can_revoke_admin?(admin) can_administer_user?(admin) && admin.admin? end def can_grant_admin?(user) can_administer_user?(user) && !user.admin? end def can_revoke_moderation?(moderator) can_administer?(moderator) && moderator.moderator? end def can_grant_moderation?(user) can_administer?(user) && !user.moderator? end def can_grant_title?(user, title = nil) return true if user && is_staff? return false if title.nil? return true if title.empty? # A title set to '(none)' in the UI is an empty string return false if user != @user if user .badges .where(allow_title: true) .pluck(:name) .any? { |name| Badge.display_name(name) == title } return true end user.groups.where(title: title).exists? end def can_use_primary_group?(user, group_id = nil) return false if !user || !group_id group = Group.find_by(id: group_id.to_i) user.group_ids.include?(group_id.to_i) && (group ? !group.automatic : false) end def can_use_flair_group?(user, group_id = nil) return false if !user || !group_id || !user.group_ids.include?(group_id.to_i) flair_icon, flair_upload_id = Group.where(id: group_id.to_i).pick(:flair_icon, :flair_upload_id) flair_icon.present? || flair_upload_id.present? end def can_change_primary_group?(user, group) user && can_edit_group?(group) end def can_change_trust_level?(user) user && is_staff? end # Support sites that have to approve users def can_access_forum? return true unless SiteSetting.must_approve_users? return false if anonymous? # Staff can't lock themselves out of a site return true if is_staff? @user.approved? end def can_see_invite_details?(user) is_staff? || is_me?(user) end def can_see_invite_emails?(user) is_staff? || is_me?(user) end def can_invite_to_forum?(groups = nil) authenticated? && (is_staff? || SiteSetting.max_invites_per_day.to_i.positive?) && (is_staff? || @user.has_trust_level?(SiteSetting.min_trust_level_to_allow_invite.to_i)) && (is_admin? || groups.blank? || groups.all? { |g| can_edit_group?(g) }) end def can_invite_to?(object, groups = nil) return false if !authenticated? return false if !object.is_a?(Topic) || !can_see?(object) return false if groups.present? if object.is_a?(Topic) if object.private_message? return true if is_admin? return false if !@user.in_any_groups?(SiteSetting.personal_message_enabled_groups_map) return false if object.reached_recipients_limit? && !is_staff? end if (category = object.category) && category.read_restricted return category.groups&.where(automatic: false).any? { |g| can_edit_group?(g) } end end true end def can_invite_via_email?(object) return false if !can_invite_to_forum? return false if !can_invite_to?(object) (SiteSetting.enable_local_logins || SiteSetting.enable_discourse_connect) && (!SiteSetting.must_approve_users? || is_staff?) end def can_bulk_invite_to_forum?(user) user.admin? end def can_resend_all_invites?(user) user.staff? end def can_destroy_all_invites?(user) user.staff? end def can_see_private_messages?(user_id) is_admin? || (authenticated? && @user.id == user_id) end def can_invite_group_to_private_message?(group, topic) can_see_topic?(topic) && can_send_private_message?(group) end ## # This should be used as a general, but not definitive, check for whether # the user can send private messages _generally_, which is mostly useful # for changing the UI. # # Please otherwise use can_send_private_message?(target, notify_moderators) # to check if a single target can be messaged. def can_send_private_messages?(notify_moderators: false) from_system = @user.is_system_user? from_bot = @user.bot? # User is authenticated authenticated? && # User can send PMs, this can be covered by trust levels as well via AUTO_GROUPS ( is_staff? || from_bot || from_system || (@user.in_any_groups?(SiteSetting.personal_message_enabled_groups_map)) || notify_moderators ) end ## # This should be used as a final check for when a user is sending a message # to a target user or group. def can_send_private_message?(target, notify_moderators: false) target_is_user = target.is_a?(User) target_is_group = target.is_a?(Group) from_system = @user.is_system_user? # Must be a valid target return false if !(target_is_group || target_is_user) # Users can send messages to certain groups with the `everyone` messageable_level # even if they are not in personal_message_enabled_groups group_is_messageable = target_is_group && Group.messageable(@user).where(id: target.id).exists? # User is authenticated and can send PMs, this can be covered by trust levels as well via AUTO_GROUPS (can_send_private_messages?(notify_moderators: notify_moderators) || group_is_messageable) && # User disabled private message (is_staff? || target_is_group || target.user_option.allow_private_messages) && # Can't send PMs to suspended users (is_staff? || target_is_group || !target.suspended?) && # Check group messageable level (from_system || target_is_user || group_is_messageable || notify_moderators) && # Silenced users can only send PM to staff (!is_silenced? || target.staff?) end def can_send_private_messages_to_email? # Staged users must be enabled to create a temporary user. return false if !SiteSetting.enable_staged_users # User is authenticated return false if !authenticated? # User is trusted enough @user.in_any_groups?(SiteSetting.personal_message_enabled_groups_map) && @user.has_trust_level_or_staff?(SiteSetting.min_trust_to_send_email_messages) end def can_export_entity?(entity) return false if anonymous? return true if is_admin? return entity != "user_list" if is_moderator? # Regular users can only export their archives return false unless entity == "user_archive" UserExport.where( user_id: @user.id, created_at: (Time.zone.now.beginning_of_day..Time.zone.now.end_of_day), ).count == 0 end def can_mute_user?(target_user) can_mute_users? && @user.id != target_user.id && !target_user.staff? end def can_mute_users? return false if anonymous? @user.staff? || @user.trust_level >= TrustLevel.levels[:basic] end def can_ignore_user?(target_user) can_ignore_users? && @user.id != target_user.id && !target_user.staff? end def can_ignore_users? return false if anonymous? @user.staff? || @user.has_trust_level?(SiteSetting.min_trust_level_to_allow_ignore.to_i) end def allowed_theme_repo_import?(repo) return false if !@user.admin? allowed_repos = GlobalSetting.allowed_theme_repos if !allowed_repos.blank? urls = allowed_repos.split(",").map(&:strip) return urls.include?(repo) end true end def allow_themes?(theme_ids, include_preview: false) return true if theme_ids.blank? if allowed_theme_ids = Theme.allowed_remote_theme_ids return false if (theme_ids - allowed_theme_ids).present? end return true if include_preview && is_staff? && (theme_ids - Theme.theme_ids).blank? parent = theme_ids.first components = theme_ids[1..-1] || [] Theme.user_theme_ids.include?(parent) && (components - Theme.components_for(parent)).empty? end def can_publish_page?(topic) return false if !SiteSetting.enable_page_publishing? return false if SiteSetting.secure_uploads? return false if topic.blank? return false if topic.private_message? return false unless can_see_topic?(topic) is_staff? end def can_see_about_stats? true end def can_see_site_contact_details? !SiteSetting.login_required? || authenticated? end def auth_token return if !request token = Auth::DefaultCurrentUserProvider.find_v0_auth_cookie(request).presence if !token cookie = Auth::DefaultCurrentUserProvider.find_v1_auth_cookie(request.env) token = cookie[:token] if cookie end UserAuthToken.hash_token(token) if token end def can_mention_here? return false if SiteSetting.here_mention.blank? return false if SiteSetting.max_here_mentioned < 1 return false if !authenticated? return false if User.where(username_lower: SiteSetting.here_mention).exists? @user.has_trust_level_or_staff?(SiteSetting.min_trust_level_for_here_mention) end def is_me?(other) other && authenticated? && other.is_a?(User) && @user == other end private def is_my_own?(obj) if anonymous? return( SiteSetting.allow_anonymous_likes? && obj.class == PostAction && obj.is_like? && obj.user_id == @user.id ) end return obj.user_id == @user.id if obj.respond_to?(:user_id) && obj.user_id && @user.id return obj.user == @user if obj.respond_to?(:user) false end def is_not_me?(other) @user.blank? || !is_me?(other) end def can_administer?(obj) is_admin? && obj.present? && obj.id&.positive? end def can_administer_user?(other_user) can_administer?(other_user) && is_not_me?(other_user) end def method_name_for(action, obj) method_name = :"can_#{action}_#{obj.class.name.underscore}?" return method_name if respond_to?(method_name) end def can_do?(action, obj) if obj && authenticated? action_method = method_name_for action, obj (action_method ? public_send(action_method, obj) : true) else false end end def is_api? @user && request&.env&.dig(Auth::DefaultCurrentUserProvider::API_KEY_ENV) end protected def category_group_moderation_allowed? authenticated? && SiteSetting.enable_category_group_moderation end def category_group_moderator_scope Category.joins( "INNER JOIN group_users ON group_users.group_id = categories.reviewable_by_group_id", ).where("group_users.user_id = ?", user.id) end end