FIX: Wrong scope used for notification levels user serializer (#13039)

This is a recent regression introduced by https://github.com/discourse/discourse/pull/12937 which makes it so that when looking at a user profile that is not your own, specifically the category and tag notification settings, you would see your own settings instead of the target user. This is only a problem for admins because regular users cannot see these details for other users.

The issue was that we were using `scope` in the serializer, which refers to the current user, rather than using a scope for the target user via `Guardian.new(user)`.

However, on further inspection the `notification_levels_for` method for `TagUser` and `CategoryUser` did not actually need to be accepting an instance of Guardian, all that it was using it for was to check guardian.anonymous? which is just a fancy way of saying user.blank?. Changed this method to just accept a user instead and send the user in from the serializer.
This commit is contained in:
Martin Brennan 2021-05-14 09:45:14 +10:00 committed by GitHub
parent 19182b1386
commit 38742bc208
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 73 additions and 26 deletions

View File

@ -100,7 +100,7 @@ class CategoryList
@categories = @categories.to_a @categories = @categories.to_a
notification_levels = CategoryUser.notification_levels_for(@guardian) notification_levels = CategoryUser.notification_levels_for(@guardian.user)
default_notification_level = CategoryUser.default_notification_level default_notification_level = CategoryUser.default_notification_level
allowed_topic_create = Set.new(Category.topic_create_allowed(@guardian).pluck(:id)) allowed_topic_create = Set.new(Category.topic_create_allowed(@guardian).pluck(:id))

View File

@ -201,10 +201,10 @@ class CategoryUser < ActiveRecord::Base
SiteSetting.mute_all_categories_by_default ? notification_levels[:muted] : notification_levels[:regular] SiteSetting.mute_all_categories_by_default ? notification_levels[:muted] : notification_levels[:regular]
end end
def self.notification_levels_for(guardian) def self.notification_levels_for(user)
# Anonymous users have all default categories set to regular tracking, # Anonymous users have all default categories set to regular tracking,
# except for default muted categories which stay muted. # except for default muted categories which stay muted.
if guardian.anonymous? if user.blank?
notification_levels = [ notification_levels = [
SiteSetting.default_categories_watching.split("|"), SiteSetting.default_categories_watching.split("|"),
SiteSetting.default_categories_tracking.split("|"), SiteSetting.default_categories_tracking.split("|"),
@ -218,7 +218,7 @@ class CategoryUser < ActiveRecord::Base
[id.to_i, self.notification_levels[:muted]] [id.to_i, self.notification_levels[:muted]]
end end
else else
notification_levels = CategoryUser.where(user: guardian.user).pluck(:category_id, :notification_level) notification_levels = CategoryUser.where(user: user).pluck(:category_id, :notification_level)
end end
Hash[*notification_levels.flatten] Hash[*notification_levels.flatten]

View File

@ -55,7 +55,7 @@ class Site
by_id = {} by_id = {}
notification_levels = CategoryUser.notification_levels_for(@guardian) notification_levels = CategoryUser.notification_levels_for(@guardian.user)
default_notification_level = CategoryUser.default_notification_level default_notification_level = CategoryUser.default_notification_level
categories.each do |category| categories.each do |category|

View File

@ -192,10 +192,10 @@ class TagUser < ActiveRecord::Base
auto_track_tag: TopicUser.notification_reasons[:auto_track_tag]) auto_track_tag: TopicUser.notification_reasons[:auto_track_tag])
end end
def self.notification_levels_for(guardian) def self.notification_levels_for(user)
# Anonymous users have all default tags set to regular tracking, # Anonymous users have all default tags set to regular tracking,
# except for default muted tags which stay muted. # except for default muted tags which stay muted.
if guardian.anonymous? if user.blank?
notification_levels = [ notification_levels = [
SiteSetting.default_tags_watching_first_post.split("|"), SiteSetting.default_tags_watching_first_post.split("|"),
SiteSetting.default_tags_watching.split("|"), SiteSetting.default_tags_watching.split("|"),
@ -208,7 +208,7 @@ class TagUser < ActiveRecord::Base
[name, self.notification_levels[:muted]] [name, self.notification_levels[:muted]]
end end
else else
notification_levels = TagUser.where(user: guardian.user).joins(:tag).pluck("tags.name", :notification_level) notification_levels = TagUser.where(user: user).joins(:tag).pluck("tags.name", :notification_level)
end end
Hash[*notification_levels.flatten] Hash[*notification_levels.flatten]

View File

@ -23,6 +23,10 @@ class BasicUserSerializer < ApplicationSerializer
object[:user] || object.try(:user) || object object[:user] || object.try(:user) || object
end end
def user_is_current_user
object.id == scope.user&.id
end
def categories_with_notification_level(lookup_level) def categories_with_notification_level(lookup_level)
category_user_notification_levels.select do |id, level| category_user_notification_levels.select do |id, level|
level == CategoryUser.notification_levels[lookup_level] level == CategoryUser.notification_levels[lookup_level]
@ -30,7 +34,7 @@ class BasicUserSerializer < ApplicationSerializer
end end
def category_user_notification_levels def category_user_notification_levels
@category_user_notification_levels ||= CategoryUser.notification_levels_for(scope) @category_user_notification_levels ||= CategoryUser.notification_levels_for(user)
end end
def tags_with_notification_level(lookup_level) def tags_with_notification_level(lookup_level)
@ -40,6 +44,6 @@ class BasicUserSerializer < ApplicationSerializer
end end
def tag_user_notification_levels def tag_user_notification_levels
@tag_user_notification_levels ||= TagUser.notification_levels_for(scope) @tag_user_notification_levels ||= TagUser.notification_levels_for(user)
end end
end end

View File

@ -225,7 +225,7 @@ class CurrentUserSerializer < BasicUserSerializer
end end
def tracked_tags def tracked_tags
tags_with_notification_level(:tracked) tags_with_notification_level(:tracking)
end end
def watching_first_post_tags def watching_first_post_tags

View File

@ -89,15 +89,15 @@ class UserSerializer < UserCardSerializer
end end
def include_group_users? def include_group_users?
(object.id && object.id == scope.user.try(:id)) || scope.is_admin? user_is_current_user || scope.is_admin?
end end
def include_associated_accounts? def include_associated_accounts?
(object.id && object.id == scope.user.try(:id)) user_is_current_user
end end
def include_second_factor_enabled? def include_second_factor_enabled?
(object&.id == scope.user&.id) || scope.is_admin? user_is_current_user || scope.is_admin?
end end
def second_factor_enabled def second_factor_enabled
@ -105,7 +105,7 @@ class UserSerializer < UserCardSerializer
end end
def include_second_factor_backup_enabled? def include_second_factor_backup_enabled?
object&.id == scope.user&.id user_is_current_user
end end
def second_factor_backup_enabled def second_factor_backup_enabled
@ -113,7 +113,7 @@ class UserSerializer < UserCardSerializer
end end
def include_second_factor_remaining_backup_codes? def include_second_factor_remaining_backup_codes?
(object&.id == scope.user&.id) && object.backup_codes_enabled? user_is_current_user && object.backup_codes_enabled?
end end
def second_factor_remaining_backup_codes def second_factor_remaining_backup_codes
@ -211,7 +211,7 @@ class UserSerializer < UserCardSerializer
end end
def tracked_tags def tracked_tags
tags_with_notification_level(:tracked) tags_with_notification_level(:tracking)
end end
def watching_first_post_tags def watching_first_post_tags

View File

@ -222,7 +222,6 @@ describe CategoryUser do
end end
describe "#notification_levels_for" do describe "#notification_levels_for" do
let(:guardian) { Guardian.new(user) }
let!(:category1) { Fabricate(:category) } let!(:category1) { Fabricate(:category) }
let!(:category2) { Fabricate(:category) } let!(:category2) { Fabricate(:category) }
let!(:category3) { Fabricate(:category) } let!(:category3) { Fabricate(:category) }
@ -239,7 +238,7 @@ describe CategoryUser do
SiteSetting.default_categories_muted = category5.id.to_s SiteSetting.default_categories_muted = category5.id.to_s
end end
it "every category from the default_categories_* site settings get overridden to regular, except for muted" do it "every category from the default_categories_* site settings get overridden to regular, except for muted" do
levels = CategoryUser.notification_levels_for(guardian) levels = CategoryUser.notification_levels_for(user)
expect(levels[category1.id]).to eq(CategoryUser.notification_levels[:regular]) expect(levels[category1.id]).to eq(CategoryUser.notification_levels[:regular])
expect(levels[category2.id]).to eq(CategoryUser.notification_levels[:regular]) expect(levels[category2.id]).to eq(CategoryUser.notification_levels[:regular])
expect(levels[category3.id]).to eq(CategoryUser.notification_levels[:regular]) expect(levels[category3.id]).to eq(CategoryUser.notification_levels[:regular])
@ -259,7 +258,7 @@ describe CategoryUser do
it "gets the category_user notification levels for all categories the user is tracking and does not it "gets the category_user notification levels for all categories the user is tracking and does not
include categories the user is not tracking at all" do include categories the user is not tracking at all" do
category6 = Fabricate(:category) category6 = Fabricate(:category)
levels = CategoryUser.notification_levels_for(guardian) levels = CategoryUser.notification_levels_for(user)
expect(levels[category1.id]).to eq(CategoryUser.notification_levels[:watching]) expect(levels[category1.id]).to eq(CategoryUser.notification_levels[:watching])
expect(levels[category2.id]).to eq(CategoryUser.notification_levels[:tracking]) expect(levels[category2.id]).to eq(CategoryUser.notification_levels[:tracking])
expect(levels[category3.id]).to eq(CategoryUser.notification_levels[:watching_first_post]) expect(levels[category3.id]).to eq(CategoryUser.notification_levels[:watching_first_post])

View File

@ -57,7 +57,7 @@ describe GroupUser do
group.watching_first_post_category_ids = [category5.id] group.watching_first_post_category_ids = [category5.id]
group.save! group.save!
expect { group.add(user) }.to change { CategoryUser.count }.by(5) expect { group.add(user) }.to change { CategoryUser.count }.by(5)
h = CategoryUser.notification_levels_for(Guardian.new(user)) h = CategoryUser.notification_levels_for(user)
expect(h[category1.id]).to eq(levels[:muted]) expect(h[category1.id]).to eq(levels[:muted])
expect(h[category2.id]).to eq(levels[:regular]) expect(h[category2.id]).to eq(levels[:regular])
expect(h[category3.id]).to eq(levels[:tracking]) expect(h[category3.id]).to eq(levels[:tracking])
@ -74,7 +74,7 @@ describe GroupUser do
group.watching_first_post_category_ids = [category2.id, category3.id, category4.id] group.watching_first_post_category_ids = [category2.id, category3.id, category4.id]
group.save! group.save!
group.add(user) group.add(user)
h = CategoryUser.notification_levels_for(Guardian.new(user)) h = CategoryUser.notification_levels_for(user)
expect(h[category1.id]).to eq(levels[:regular]) expect(h[category1.id]).to eq(levels[:regular])
expect(h[category2.id]).to eq(levels[:watching_first_post]) expect(h[category2.id]).to eq(levels[:watching_first_post])
expect(h[category3.id]).to eq(levels[:watching_first_post]) expect(h[category3.id]).to eq(levels[:watching_first_post])
@ -89,7 +89,7 @@ describe GroupUser do
group.tracking_category_ids = [category4.id] group.tracking_category_ids = [category4.id]
group.save! group.save!
group.add(user) group.add(user)
h = CategoryUser.notification_levels_for(Guardian.new(user)) h = CategoryUser.notification_levels_for(user)
expect(h[category1.id]).to eq(levels[:tracking]) expect(h[category1.id]).to eq(levels[:tracking])
expect(h[category2.id]).to eq(levels[:watching]) expect(h[category2.id]).to eq(levels[:watching])
expect(h[category3.id]).to eq(levels[:muted]) expect(h[category3.id]).to eq(levels[:muted])

View File

@ -234,7 +234,6 @@ describe TagUser do
end end
describe "#notification_levels_for" do describe "#notification_levels_for" do
let(:guardian) { Guardian.new(user) }
let!(:tag1) { Fabricate(:tag) } let!(:tag1) { Fabricate(:tag) }
let!(:tag2) { Fabricate(:tag) } let!(:tag2) { Fabricate(:tag) }
let!(:tag3) { Fabricate(:tag) } let!(:tag3) { Fabricate(:tag) }
@ -249,7 +248,7 @@ describe TagUser do
SiteSetting.default_tags_muted = tag4.name SiteSetting.default_tags_muted = tag4.name
end end
it "every tag from the default_tags_* site settings get overridden to watching_first_post, except for muted" do it "every tag from the default_tags_* site settings get overridden to watching_first_post, except for muted" do
levels = TagUser.notification_levels_for(guardian) levels = TagUser.notification_levels_for(user)
expect(levels[tag1.name]).to eq(TagUser.notification_levels[:regular]) expect(levels[tag1.name]).to eq(TagUser.notification_levels[:regular])
expect(levels[tag2.name]).to eq(TagUser.notification_levels[:regular]) expect(levels[tag2.name]).to eq(TagUser.notification_levels[:regular])
expect(levels[tag3.name]).to eq(TagUser.notification_levels[:regular]) expect(levels[tag3.name]).to eq(TagUser.notification_levels[:regular])
@ -268,7 +267,7 @@ describe TagUser do
it "gets the tag_user notification levels for all tags the user is tracking and does not it "gets the tag_user notification levels for all tags the user is tracking and does not
include tags the user is not tracking at all" do include tags the user is not tracking at all" do
tag5 = Fabricate(:tag) tag5 = Fabricate(:tag)
levels = TagUser.notification_levels_for(guardian) levels = TagUser.notification_levels_for(user)
expect(levels[tag1.name]).to eq(TagUser.notification_levels[:watching]) expect(levels[tag1.name]).to eq(TagUser.notification_levels[:watching])
expect(levels[tag2.name]).to eq(TagUser.notification_levels[:tracking]) expect(levels[tag2.name]).to eq(TagUser.notification_levels[:tracking])
expect(levels[tag3.name]).to eq(TagUser.notification_levels[:watching_first_post]) expect(levels[tag3.name]).to eq(TagUser.notification_levels[:watching_first_post])

View File

@ -60,6 +60,7 @@ describe UserSerializer do
end end
context "with a user" do context "with a user" do
let(:admin_user) { Fabricate(:admin) }
let(:scope) { Guardian.new } let(:scope) { Guardian.new }
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
let(:serializer) { UserSerializer.new(user, scope: scope, root: false) } let(:serializer) { UserSerializer.new(user, scope: scope, root: false) }
@ -67,6 +68,50 @@ describe UserSerializer do
fab!(:upload) { Fabricate(:upload) } fab!(:upload) { Fabricate(:upload) }
fab!(:upload2) { Fabricate(:upload) } fab!(:upload2) { Fabricate(:upload) }
context "when the scope user is admin" do
let(:scope) { Guardian.new(admin_user) }
it "returns the user's category notification levels, not the scope user's" do
category1 = Fabricate(:category)
category2 = Fabricate(:category)
category3 = Fabricate(:category)
category4 = Fabricate(:category)
CategoryUser.create(category: category1, user: user, notification_level: CategoryUser.notification_levels[:muted])
CategoryUser.create(category: Fabricate(:category), user: admin_user, notification_level: CategoryUser.notification_levels[:muted])
CategoryUser.create(category: category2, user: user, notification_level: CategoryUser.notification_levels[:tracking])
CategoryUser.create(category: Fabricate(:category), user: admin_user, notification_level: CategoryUser.notification_levels[:tracking])
CategoryUser.create(category: category3, user: user, notification_level: CategoryUser.notification_levels[:watching])
CategoryUser.create(category: Fabricate(:category), user: admin_user, notification_level: CategoryUser.notification_levels[:watching])
CategoryUser.create(category: category4, user: user, notification_level: CategoryUser.notification_levels[:regular])
CategoryUser.create(category: Fabricate(:category), user: admin_user, notification_level: CategoryUser.notification_levels[:regular])
expect(json[:muted_category_ids]).to eq([category1.id])
expect(json[:tracked_category_ids]).to eq([category2.id])
expect(json[:watched_category_ids]).to eq([category3.id])
expect(json[:regular_category_ids]).to eq([category4.id])
end
it "returns the user's tag notification levels, not the scope user's" do
tag1 = Fabricate(:tag)
tag2 = Fabricate(:tag)
tag3 = Fabricate(:tag)
tag4 = Fabricate(:tag)
TagUser.create(tag: tag1, user: user, notification_level: TagUser.notification_levels[:muted])
TagUser.create(tag: Fabricate(:tag), user: admin_user, notification_level: TagUser.notification_levels[:muted])
TagUser.create(tag: tag2, user: user, notification_level: TagUser.notification_levels[:tracking])
TagUser.create(tag: Fabricate(:tag), user: admin_user, notification_level: TagUser.notification_levels[:tracking])
TagUser.create(tag: tag3, user: user, notification_level: TagUser.notification_levels[:watching])
TagUser.create(tag: Fabricate(:tag), user: admin_user, notification_level: TagUser.notification_levels[:watching])
TagUser.create(tag: tag4, user: user, notification_level: TagUser.notification_levels[:watching_first_post])
TagUser.create(tag: Fabricate(:tag), user: admin_user, notification_level: TagUser.notification_levels[:watching_first_post])
expect(json[:muted_tags]).to eq([tag1.name])
expect(json[:tracked_tags]).to eq([tag2.name])
expect(json[:watched_tags]).to eq([tag3.name])
expect(json[:watching_first_post_tags]).to eq([tag4.name])
end
end
context "with `enable_names` true" do context "with `enable_names` true" do
before do before do
SiteSetting.enable_names = true SiteSetting.enable_names = true