FIX: Allow admins to change user ignore list (#16129)

Previously, if an admin user tried to add/remove
users to another user's ignored list, it would
be added to their own ignore list because the
controller used current_user. Now for admins only
a source_user_id parameter can be passed through,
which will be used to ignore the target user for
that source user.
This commit is contained in:
Martin Brennan 2022-03-09 14:51:30 +10:00 committed by GitHub
parent efd8bb9008
commit ca93e5e68b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 100 additions and 29 deletions

View File

@ -11,7 +11,10 @@ export default Component.extend({
this.items.removeObject(item); this.items.removeObject(item);
User.findByUsername(item).then((user) => { User.findByUsername(item).then((user) => {
user user
.updateNotificationLevel("normal") .updateNotificationLevel({
level: "normal",
actingUser: this.model,
})
.catch(popupAjaxError) .catch(popupAjaxError)
.finally(() => this.set("saved", true)); .finally(() => this.set("saved", true));
}); });

View File

@ -20,7 +20,11 @@ export default Controller.extend(ModalFunctionality, {
this.set("loading", true); this.set("loading", true);
User.findByUsername(this.ignoredUsername).then((user) => { User.findByUsername(this.ignoredUsername).then((user) => {
user user
.updateNotificationLevel("ignore", this.ignoredUntil) .updateNotificationLevel({
level: "ignore",
expiringAt: this.ignoredUntil,
actingUser: this.model,
})
.then(() => { .then(() => {
this.onUserIgnored(this.ignoredUsername); this.onUserIgnored(this.ignoredUsername);
this.send("closeModal"); this.send("closeModal");

View File

@ -17,7 +17,10 @@ export default Controller.extend(ModalFunctionality, {
} }
this.set("loading", true); this.set("loading", true);
this.model this.model
.updateNotificationLevel("ignore", this.ignoredUntil) .updateNotificationLevel({
level: "ignored",
expiringAt: this.ignoredUntil,
})
.then(() => { .then(() => {
this.set("model.ignored", true); this.set("model.ignored", true);
this.set("model.muted", false); this.set("model.muted", false);

View File

@ -1,5 +1,5 @@
import { action, computed } from "@ember/object"; import { action, computed } from "@ember/object";
import { alias, and, or } from "@ember/object/computed"; import { alias, and } from "@ember/object/computed";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { makeArray } from "discourse-common/lib/helpers"; import { makeArray } from "discourse-common/lib/helpers";
@ -13,7 +13,10 @@ export default Controller.extend({
return trustLevel >= this.siteSettings.min_trust_level_to_allow_ignore; return trustLevel >= this.siteSettings.min_trust_level_to_allow_ignore;
}, },
ignoredEnabled: or("userCanIgnore", "model.staff"), @discourseComputed("userCanIgnore", "model.staff")
ignoredEnabled(userCanIgnore, userIsStaff) {
return this.currentUser.staff || userCanIgnore || userIsStaff;
},
allowPmUsersEnabled: and( allowPmUsersEnabled: and(
"model.user_option.enable_allowed_pm_users", "model.user_option.enable_allowed_pm_users",

View File

@ -250,8 +250,7 @@ export default Controller.extend(CanCheckEmails, {
}, },
updateNotificationLevel(level) { updateNotificationLevel(level) {
const user = this.model; return this.model.updateNotificationLevel({ level });
return user.updateNotificationLevel(level);
}, },
}, },
}); });

View File

@ -780,18 +780,25 @@ const User = RestModel.extend({
} }
}, },
updateNotificationLevel(level, expiringAt) { updateNotificationLevel({ level, expiringAt = null, actingUser = null }) {
if (!actingUser) {
actingUser = User.current();
}
return ajax(`${userPath(this.username)}/notification_level.json`, { return ajax(`${userPath(this.username)}/notification_level.json`, {
type: "PUT", type: "PUT",
data: { notification_level: level, expiring_at: expiringAt }, data: {
notification_level: level,
expiring_at: expiringAt,
acting_user_id: actingUser.id,
},
}).then(() => { }).then(() => {
const currentUser = User.current(); if (!actingUser.ignored_users) {
if (currentUser) { actingUser.ignored_users = [];
if (level === "normal" || level === "mute") { }
currentUser.ignored_users.removeObject(this.username); if (level === "normal" || level === "mute") {
} else if (level === "ignore") { actingUser.ignored_users.removeObject(this.username);
currentUser.ignored_users.addObject(this.username); } else if (level === "ignore") {
} actingUser.ignored_users.addObject(this.username);
} }
}); });
}, },

View File

@ -49,10 +49,10 @@ export default DropdownSelectBox.extend({
}), }),
changeToNormal() { changeToNormal() {
this.updateNotificationLevel("normal").catch(popupAjaxError); this.updateNotificationLevel({ level: "normal" }).catch(popupAjaxError);
}, },
changeToMuted() { changeToMuted() {
this.updateNotificationLevel("mute").catch(popupAjaxError); this.updateNotificationLevel({ level: "mute" }).catch(popupAjaxError);
}, },
changeToIgnored() { changeToIgnored() {
showModal("ignore-duration", { showModal("ignore-duration", {

View File

@ -1329,26 +1329,42 @@ class UsersController < ApplicationController
end end
def notification_level def notification_level
user = fetch_user_from_params target_user = fetch_user_from_params
acting_user = current_user
# the admin should be able to change notification levels
# on behalf of other users, so we cannot rely on current_user
# for this case
if params[:acting_user_id].present? &&
params[:acting_user_id].to_i != current_user.id
if current_user.staff?
acting_user = User.find(params[:acting_user_id])
else
@error_message = "error"
raise Discourse::InvalidAccess
end
end
if params[:notification_level] == "ignore" if params[:notification_level] == "ignore"
@error_message = "ignore_error" @error_message = "ignore_error"
guardian.ensure_can_ignore_user!(user) guardian.ensure_can_ignore_user!(target_user)
MutedUser.where(user: current_user, muted_user: user).delete_all MutedUser.where(user: acting_user, muted_user: target_user).delete_all
ignored_user = IgnoredUser.find_by(user: current_user, ignored_user: user) ignored_user = IgnoredUser.find_by(user: acting_user, ignored_user: target_user)
if ignored_user.present? if ignored_user.present?
ignored_user.update(expiring_at: DateTime.parse(params[:expiring_at])) ignored_user.update(expiring_at: DateTime.parse(params[:expiring_at]))
else else
IgnoredUser.create!(user: current_user, ignored_user: user, expiring_at: Time.parse(params[:expiring_at])) IgnoredUser.create!(user: acting_user, ignored_user: target_user, expiring_at: Time.parse(params[:expiring_at]))
end end
elsif params[:notification_level] == "mute" elsif params[:notification_level] == "mute"
@error_message = "mute_error" @error_message = "mute_error"
guardian.ensure_can_mute_user!(user) guardian.ensure_can_mute_user!(target_user)
IgnoredUser.where(user: current_user, ignored_user: user).delete_all IgnoredUser.where(user: acting_user, ignored_user: target_user).delete_all
MutedUser.find_or_create_by!(user: current_user, muted_user: user) MutedUser.find_or_create_by!(user: acting_user, muted_user: target_user)
elsif params[:notification_level] == "normal" elsif params[:notification_level] == "normal"
MutedUser.where(user: current_user, muted_user: user).delete_all MutedUser.where(user: acting_user, muted_user: target_user).delete_all
IgnoredUser.where(user: current_user, ignored_user: user).delete_all IgnoredUser.where(user: acting_user, ignored_user: target_user).delete_all
end end
render json: success_json render json: success_json

View File

@ -5153,6 +5153,7 @@ en:
notification_level: notification_level:
ignore_error: "Sorry, you can't ignore that user." ignore_error: "Sorry, you can't ignore that user."
mute_error: "Sorry, you can't mute that user." mute_error: "Sorry, you can't mute that user."
error: "Sorry, you cannot change the notification level for that user."
discord: discord:
not_in_allowed_guild: "Authentication failed. You are not a member of a permitted Discord guild." not_in_allowed_guild: "Authentication failed. You are not a member of a permitted Discord guild."

View File

@ -2925,11 +2925,46 @@ describe UsersController do
context 'when changing notification level to ignore' do context 'when changing notification level to ignore' do
it 'changes notification level to ignore' do it 'changes notification level to ignore' do
put "/u/#{another_user.username}/notification_level.json", params: { notification_level: "ignore" } put "/u/#{another_user.username}/notification_level.json", params: {
notification_level: "ignore",
expiring_at: 3.days.from_now
}
expect(response.status).to eq(200)
expect(MutedUser.count).to eq(0) expect(MutedUser.count).to eq(0)
expect(IgnoredUser.find_by(user_id: user.id, ignored_user_id: another_user.id)).to be_present expect(IgnoredUser.find_by(user_id: user.id, ignored_user_id: another_user.id)).to be_present
end end
it "allows admin to change the ignore status for a source user" do
ignored_user.destroy!
sign_in(Fabricate(:user, admin: true))
put "/u/#{another_user.username}/notification_level.json", params: {
notification_level: "ignore",
acting_user_id: user.id,
expiring_at: 3.days.from_now
}
expect(response.status).to eq(200)
expect(IgnoredUser.find_by(user_id: user.id, ignored_user_id: another_user.id)).to be_present
end
it "does not allow a regular user to change the ignore status for anyone but themself" do
ignored_user.destroy!
acting_user = Fabricate(:user)
put "/u/#{another_user.username}/notification_level.json", params: {
notification_level: "ignore",
acting_user_id: acting_user.id,
expiring_at: 3.days.from_now
}
expect(response.status).to eq(422)
expect(IgnoredUser.find_by(user_id: acting_user.id, ignored_user_id: another_user.id)).to eq(nil)
put "/u/#{another_user.username}/notification_level.json", params: {
notification_level: "ignore",
expiring_at: 3.days.from_now
}
expect(response.status).to eq(200)
expect(IgnoredUser.find_by(user_id: user.id, ignored_user_id: another_user.id)).to be_present
end
context 'when expiring_at param is set' do context 'when expiring_at param is set' do
it 'changes notification level to ignore' do it 'changes notification level to ignore' do
freeze_time(Time.now) do freeze_time(Time.now) do