mirror of
https://github.com/discourse/discourse.git
synced 2026-03-04 01:15:08 +08:00
Silenced users can now no longer like posts or use reactions, which closes a potential griefing vector that was difficult for moderators to monitor. The implementation adds a silenced check to the guardian's post_can_act? method for likes, and introduces a new can_use_reactions? guardian method in the discourse-reactions plugin that delegates to the same logic. This ensures both features share the same authorization path. Additionally, silenced users' custom status is now shadow-banned: visible to themselves and staff, but hidden from other users. A new `can_see_user_status?` guardian method centralizes the visibility logic, used by serializers and MessageBus publishing. Status updates from silenced users are now only broadcast to themselves and staff. Also includes minor CSS fixes for user status spacing and alignment. Chat reactions already had proper silenced user checks in place via the can_react? guardian method, so no changes were needed there. Ref - t/140084
233 lines
6.9 KiB
Ruby
233 lines
6.9 KiB
Ruby
# frozen_string_literal: true
|
||
|
||
RSpec.describe UserStatusController do
|
||
describe "#get" do
|
||
it "requires user to be logged in" do
|
||
get "/user-status.json"
|
||
expect(response.status).to eq(403)
|
||
end
|
||
|
||
it "returns 404 if the feature is disabled" do
|
||
user = Fabricate(:user)
|
||
sign_in(user)
|
||
SiteSetting.enable_user_status = false
|
||
|
||
get "/user-status.json"
|
||
|
||
expect(response.status).to eq(404)
|
||
end
|
||
|
||
describe "when feature is enabled and a user is logged in" do
|
||
fab!(:user)
|
||
|
||
before do
|
||
sign_in(user)
|
||
SiteSetting.enable_user_status = true
|
||
end
|
||
|
||
it "returns user status" do
|
||
status = "off to dentist"
|
||
status_emoji = "tooth"
|
||
ends_at = "2100-01-01T18:00:00Z"
|
||
user.set_status!(status, status_emoji, DateTime.parse(ends_at))
|
||
|
||
get "/user-status.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
expect(response.parsed_body["description"]).to eq(status)
|
||
expect(response.parsed_body["emoji"]).to eq(status_emoji)
|
||
expect(response.parsed_body["ends_at"]).to eq(ends_at)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "#set" do
|
||
it "requires user to be logged in" do
|
||
put "/user-status.json", params: { description: "off to dentist" }
|
||
expect(response.status).to eq(403)
|
||
end
|
||
|
||
it "returns 404 if the feature is disabled" do
|
||
user = Fabricate(:user)
|
||
sign_in(user)
|
||
SiteSetting.enable_user_status = false
|
||
|
||
put "/user-status.json", params: { description: "off" }
|
||
|
||
expect(response.status).to eq(404)
|
||
end
|
||
|
||
describe "feature is enabled and user is logged in" do
|
||
fab!(:user)
|
||
|
||
before do
|
||
sign_in(user)
|
||
SiteSetting.enable_user_status = true
|
||
end
|
||
|
||
it "the description parameter is mandatory" do
|
||
put "/user-status.json", params: { emoji: "tooth" }
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "the emoji parameter is mandatory" do
|
||
put "/user-status.json", params: { description: "off to dentist" }
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "validates emoji" do
|
||
put "/user-status.json",
|
||
params: {
|
||
emoji: "invalid_emoji_name",
|
||
description: "off to dentist",
|
||
}
|
||
expect(response.status).to eq(422)
|
||
end
|
||
|
||
it "limits description’s length" do
|
||
put "/user-status.json",
|
||
params: {
|
||
emoji: "tooth",
|
||
description: "x" * UserStatus::MAX_DESCRIPTION_LENGTH,
|
||
}
|
||
expect(response.status).to eq(200)
|
||
|
||
put "/user-status.json",
|
||
params: {
|
||
emoji: "tooth",
|
||
description: "x" * (UserStatus::MAX_DESCRIPTION_LENGTH + 1),
|
||
}
|
||
expect(response.status).to eq(422)
|
||
end
|
||
|
||
it "sets user status" do
|
||
status = "off to dentist"
|
||
status_emoji = "tooth"
|
||
ends_at = DateTime.parse("2100-01-01 18:00")
|
||
|
||
put "/user-status.json",
|
||
params: {
|
||
description: status,
|
||
emoji: status_emoji,
|
||
ends_at: ends_at,
|
||
}
|
||
|
||
expect(response.status).to eq(200)
|
||
expect(user.user_status.description).to eq(status)
|
||
expect(user.user_status.emoji).to eq(status_emoji)
|
||
expect(user.user_status.ends_at).to eq_time(ends_at)
|
||
end
|
||
|
||
it "following calls update status" do
|
||
status = "off to dentist"
|
||
status_emoji = "tooth"
|
||
ends_at = DateTime.parse("2100-01-01 18:00")
|
||
put "/user-status.json",
|
||
params: {
|
||
description: status,
|
||
emoji: status_emoji,
|
||
ends_at: ends_at,
|
||
}
|
||
expect(response.status).to eq(200)
|
||
|
||
user.reload
|
||
expect(user.user_status.description).to eq(status)
|
||
expect(user.user_status.emoji).to eq(status_emoji)
|
||
expect(user.user_status.ends_at).to eq_time(ends_at)
|
||
|
||
new_status = "surfing"
|
||
new_status_emoji = "man_surfing"
|
||
new_ends_at = DateTime.parse("2100-01-01 18:59")
|
||
put "/user-status.json",
|
||
params: {
|
||
description: new_status,
|
||
emoji: new_status_emoji,
|
||
ends_at: new_ends_at,
|
||
}
|
||
expect(response.status).to eq(200)
|
||
|
||
user.reload
|
||
expect(user.user_status.description).to eq(new_status)
|
||
expect(user.user_status.emoji).to eq(new_status_emoji)
|
||
expect(user.user_status.ends_at).to eq_time(new_ends_at)
|
||
end
|
||
|
||
it "publishes to message bus" do
|
||
status = "off to dentist"
|
||
emoji = "tooth"
|
||
ends_at = "2100-01-01T18:00:00Z"
|
||
|
||
messages =
|
||
MessageBus.track_publish("/user-status") do
|
||
put "/user-status.json", params: { description: status, emoji: emoji, ends_at: ends_at }
|
||
end
|
||
|
||
expect(messages.map(&:channel)).to contain_exactly("/user-status")
|
||
expect(messages[0].channel).to eq("/user-status")
|
||
expect(messages[0].group_ids).to eq([Group::AUTO_GROUPS[:trust_level_0]])
|
||
|
||
expect(messages[0].data[user.id][:description]).to eq(status)
|
||
expect(messages[0].data[user.id][:emoji]).to eq(emoji)
|
||
expect(messages[0].data[user.id][:ends_at]).to eq(ends_at)
|
||
end
|
||
|
||
it "only publishes to the silenced user and staff when silenced" do
|
||
user.update!(silenced_till: 1.year.from_now)
|
||
|
||
messages =
|
||
MessageBus.track_publish("/user-status") do
|
||
put "/user-status.json", params: { description: "off to dentist", emoji: "tooth" }
|
||
end
|
||
|
||
expect(messages.size).to eq(1)
|
||
expect(messages[0].user_ids).to contain_exactly(user.id)
|
||
expect(messages[0].group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "#clear" do
|
||
it "requires you to be logged in" do
|
||
delete "/user-status.json"
|
||
expect(response.status).to eq(403)
|
||
end
|
||
|
||
it "returns 404 if the feature is disabled" do
|
||
user = Fabricate(:user)
|
||
sign_in(user)
|
||
SiteSetting.enable_user_status = false
|
||
|
||
delete "/user-status.json"
|
||
|
||
expect(response.status).to eq(404)
|
||
end
|
||
|
||
describe "feature is enabled and user is logged in" do
|
||
fab!(:user_status) { Fabricate(:user_status, description: "off to dentist") }
|
||
fab!(:user) { Fabricate(:user, user_status: user_status) }
|
||
|
||
before do
|
||
sign_in(user)
|
||
SiteSetting.enable_user_status = true
|
||
end
|
||
|
||
it "clears user status" do
|
||
delete "/user-status.json"
|
||
expect(response.status).to eq(200)
|
||
|
||
user.reload
|
||
expect(user.user_status).to be_nil
|
||
end
|
||
|
||
it "publishes to message bus" do
|
||
messages = MessageBus.track_publish("/user-status") { delete "/user-status.json" }
|
||
|
||
expect(messages.size).to eq(1)
|
||
expect(messages[0].channel).to eq("/user-status")
|
||
expect(messages[0].group_ids).to eq([Group::AUTO_GROUPS[:trust_level_0]])
|
||
|
||
expect(messages[0].data[user.id]).to eq(nil)
|
||
end
|
||
end
|
||
end
|
||
end
|