[FEATURE] Disallow ignoring self, admins or moderators users (#7202)

[FEATURE] Disallow ignoring self, admins or moderators users (#7202)

diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 9be7ef9..239388c 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -997,10 +997,9 @@ class UsersController < ApplicationController
 
   def ignore
     raise Discourse::NotFound unless SiteSetting.ignore_user_enabled
+    guardian.ensure_can_ignore_user!(params[:ignored_user_id])
 
-    ::IgnoredUser.find_or_create_by!(
-      user: current_user,
-      ignored_user_id: params[:ignored_user_id])
+    IgnoredUser.find_or_create_by!(user: current_user, ignored_user_id: params[:ignored_user_id])
     render json: success_json
   end
 
diff --git a/app/serializers/basic_post_serializer.rb b/app/serializers/basic_post_serializer.rb
index 5f116c2..0e121f0 100644
--- a/app/serializers/basic_post_serializer.rb
+++ b/app/serializers/basic_post_serializer.rb
@@ -44,8 +44,11 @@ class BasicPostSerializer < ApplicationSerializer
   end
 
   def ignored
-    object.is_first_post? && IgnoredUser.where(user_id: scope.current_user&.id,
-                                               ignored_user_id: object.user_id).exists?
+    object.is_first_post? &&
+      scope.current_user&.id != object.user_id &&
+      !object.user&.staff? &&
+      IgnoredUser.where(user_id: scope.current_user&.id,
+                        ignored_user_id: object.user_id).exists?
   end
 
   def include_name?
diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb
index b4de993..d24a301 100644
--- a/app/serializers/user_serializer.rb
+++ b/app/serializers/user_serializer.rb
@@ -282,7 +282,7 @@ class UserSerializer < BasicUserSerializer
   end
 
   def can_ignore_user
-    SiteSetting.ignore_user_enabled
+    SiteSetting.ignore_user_enabled? && !object.staff? && scope.current_user != object
   end
 
   # Needed because 'send_private_message_to_user' will always return false
diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb
index ef73795..cc7fc1a 100644
--- a/app/services/user_updater.rb
+++ b/app/services/user_updater.rb
@@ -149,7 +149,8 @@ class UserUpdater
 
   def update_muted_users(usernames)
     usernames ||= ""
-    desired_ids = User.where(username: usernames.split(",")).pluck(:id)
+    desired_usernames = usernames.split(",").reject { |username| user.username == username }
+    desired_ids = User.where(username: desired_usernames).pluck(:id)
     if desired_ids.empty?
       MutedUser.where(user_id: user.id).destroy_all
     else
@@ -168,7 +169,8 @@ class UserUpdater
 
   def update_ignored_users(usernames)
     usernames ||= ""
-    desired_ids = User.where(username: usernames.split(",")).pluck(:id)
+    desired_usernames = usernames.split(",").reject { |username| user.username == username }
+    desired_ids = User.where(username: desired_usernames).where(admin: false, moderator: false).pluck(:id)
     if desired_ids.empty?
       IgnoredUser.where(user_id: user.id).destroy_all
     else
diff --git a/lib/guardian.rb b/lib/guardian.rb
index 6e9b00f..69e01dc 100644
--- a/lib/guardian.rb
+++ b/lib/guardian.rb
@@ -390,6 +390,10 @@ class Guardian
     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_ignore_user?(user_id)
+    @user.id != user_id && User.where(id: user_id, admin: false, moderator: false).exists?
+  end
+
   def allow_themes?(theme_ids, include_preview: false)
     return true if theme_ids.blank?
 
diff --git a/lib/topic_view.rb b/lib/topic_view.rb
index 4f8c0ac..08cfa02 100644
--- a/lib/topic_view.rb
+++ b/lib/topic_view.rb
@@ -603,7 +603,18 @@ class TopicView
     @filtered_posts = unfiltered_posts
 
     if SiteSetting.ignore_user_enabled
-      ignored_user_ids = IgnoredUser.where(user_id: @user&.id).pluck(:ignored_user_id)
+
+      sql = <<~SQL
+          SELECT ignored_user_id
+          FROM ignored_users as ig
+          JOIN users as u ON u.id = ig.ignored_user_id
+          WHERE ig.user_id = :current_user_id
+            AND ig.ignored_user_id <> :current_user_id
+            AND NOT u.admin
+            AND NOT u.moderator
+      SQL
+
+      ignored_user_ids = DB.query_single(sql, current_user_id: @user&.id)
 
       if ignored_user_ids.present?
         @filtered_posts = @filtered_posts.where.not("user_id IN (?) AND id <> ?", ignored_user_ids, first_post_id)
diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb
index 8ab84d2..0d44eb0 100644
--- a/spec/components/guardian_spec.rb
+++ b/spec/components/guardian_spec.rb
@@ -2640,6 +2640,32 @@ describe Guardian do
     end
   end
 
+  describe '#can_ignore_user?' do
+    let(:guardian) { Guardian.new(user) }
+
+    context "when ignored user is the same as guardian user" do
+      it 'does not allow ignoring user' do
+        expect(guardian.can_ignore_user?(user.id)).to eq(false)
+      end
+    end
+
+    context "when ignored user is a staff user" do
+      let!(:admin) { Fabricate(:user, admin: true) }
+
+      it 'does not allow ignoring user' do
+        expect(guardian.can_ignore_user?(admin.id)).to eq(false)
+      end
+    end
+
+    context "when ignored user is a normal user" do
+      let!(:another_user) { Fabricate(:user) }
+
+      it 'allows ignoring user' do
+        expect(guardian.can_ignore_user?(another_user.id)).to eq(true)
+      end
+    end
+  end
+
   describe "#allow_themes?" do
     let(:theme) { Fabricate(:theme) }
     let(:theme2) { Fabricate(:theme) }
diff --git a/spec/components/topic_view_spec.rb b/spec/components/topic_view_spec.rb
index a45b981..e304611 100644
--- a/spec/components/topic_view_spec.rb
+++ b/spec/components/topic_view_spec.rb
@@ -44,8 +44,7 @@ describe TopicView do
           SiteSetting.ignore_user_enabled = false
 
           tv = TopicView.new(topic.id, evil_trout)
-          expect(tv.filtered_post_ids.size).to eq(3)
-          expect(tv.filtered_post_ids).to match_array([post.id, post2.id, post3.id])
+          expect(tv.filtered_post_ids).to eq([post.id, post2.id, post3.id])
         end
       end
 
@@ -57,8 +56,7 @@ describe TopicView do
 
         it "filters out ignored user posts" do
           tv = TopicView.new(topic.id, evil_trout)
-          expect(tv.filtered_post_ids.size).to eq(2)
-          expect(tv.filtered_post_ids).to match_array([post.id, post2.id])
+          expect(tv.filtered_post_ids).to eq([post.id, post2.id])
         end
 
         describe "when an ignored user made the original post" do
@@ -66,8 +64,7 @@ describe TopicView do
 
           it "filters out ignored user posts only" do
             tv = TopicView.new(topic.id, evil_trout)
-            expect(tv.filtered_post_ids.size).to eq(2)
-            expect(tv.filtered_post_ids).to match_array([post.id, post2.id])
+            expect(tv.filtered_post_ids).to eq([post.id, post2.id])
           end
         end
 
@@ -77,8 +74,7 @@ describe TopicView do
 
           it "filters out ignored user posts only" do
             tv = TopicView.new(topic.id, evil_trout)
-            expect(tv.filtered_post_ids.size).to eq(3)
-            expect(tv.filtered_post_ids).to match_array([post.id, post2.id, post4.id])
+            expect(tv.filtered_post_ids).to eq([post.id, post2.id, post4.id])
           end
         end
 
@@ -88,8 +84,18 @@ describe TopicView do
 
           it "filters out ignored user posts only" do
             tv = TopicView.new(topic.id, nil)
-            expect(tv.filtered_post_ids.size).to eq(4)
-            expect(tv.filtered_post_ids).to match_array([post.id, post2.id, post3.id, post4.id])
+            expect(tv.filtered_post_ids).to eq([post.id, post2.id, post3.id, post4.id])
+          end
+        end
+
+        describe "when a staff user is ignored" do
+          let!(:admin) { Fabricate(:user, admin: true) }
+          let!(:admin_ignored_user) { Fabricate(:ignored_user, user: evil_trout, ignored_user: admin) }
+          let!(:post4) { Fabricate(:post, topic: topic, user: admin) }
+

[... diff too long, it was truncated ...]

GitHub sha: 3b59ff0d