mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
Backport of #41403 to release/2026.1. --- When a group is visible but its members are hidden, some group payloads still included the exact member count. This PR serializes group member counts when the requester can see group members, and we update the public API schemas to match the conditional field.
356 lines
11 KiB
Ruby
Vendored
356 lines
11 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe ComposerController do
|
|
describe "#mentions" do
|
|
fab!(:current_user, :user)
|
|
fab!(:user)
|
|
|
|
fab!(:group) do
|
|
Fabricate(
|
|
:group,
|
|
messageable_level: Group::ALIAS_LEVELS[:everyone],
|
|
mentionable_level: Group::ALIAS_LEVELS[:everyone],
|
|
)
|
|
end
|
|
fab!(:invisible_group) { Fabricate(:group, visibility_level: Group.visibility_levels[:owners]) }
|
|
fab!(:unmessageable_group) do
|
|
Fabricate(
|
|
:group,
|
|
messageable_level: Group::ALIAS_LEVELS[:nobody],
|
|
mentionable_level: Group::ALIAS_LEVELS[:everyone],
|
|
)
|
|
end
|
|
fab!(:unmentionable_group) do
|
|
Fabricate(
|
|
:group,
|
|
messageable_level: Group::ALIAS_LEVELS[:everyone],
|
|
mentionable_level: Group::ALIAS_LEVELS[:nobody],
|
|
)
|
|
end
|
|
|
|
before { sign_in(current_user) }
|
|
|
|
context "without a topic" do
|
|
it "finds mentions" do
|
|
get "/composer/mentions.json",
|
|
params: {
|
|
names: [
|
|
"invaliduserorgroup",
|
|
user.username,
|
|
group.name,
|
|
invisible_group.name,
|
|
unmessageable_group.name,
|
|
unmentionable_group.name,
|
|
],
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(response.parsed_body["users"]).to contain_exactly(user.username)
|
|
expect(response.parsed_body["user_reasons"]).to eq({})
|
|
|
|
expect(response.parsed_body["groups"]).to eq(
|
|
{
|
|
group.name => {
|
|
"user_count" => group.user_count,
|
|
},
|
|
unmessageable_group.name => {
|
|
"user_count" => unmessageable_group.user_count,
|
|
},
|
|
unmentionable_group.name => {
|
|
"user_count" => unmentionable_group.user_count,
|
|
},
|
|
},
|
|
)
|
|
expect(response.parsed_body["group_reasons"]).to eq(
|
|
{ unmentionable_group.name => "not_mentionable" },
|
|
)
|
|
|
|
expect(response.parsed_body["max_users_notified_per_group_mention"]).to eq(
|
|
SiteSetting.max_users_notified_per_group_mention,
|
|
)
|
|
end
|
|
end
|
|
|
|
context "with a regular topic" do
|
|
fab!(:topic)
|
|
|
|
it "finds mentions" do
|
|
get "/composer/mentions.json",
|
|
params: {
|
|
names: [
|
|
"invaliduserorgroup",
|
|
user.username,
|
|
group.name,
|
|
invisible_group.name,
|
|
unmessageable_group.name,
|
|
unmentionable_group.name,
|
|
],
|
|
topic_id: topic.id,
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(response.parsed_body["users"]).to contain_exactly(user.username)
|
|
expect(response.parsed_body["user_reasons"]).to eq({})
|
|
|
|
expect(response.parsed_body["groups"]).to eq(
|
|
group.name => {
|
|
"user_count" => group.user_count,
|
|
},
|
|
unmessageable_group.name => {
|
|
"user_count" => unmessageable_group.user_count,
|
|
},
|
|
unmentionable_group.name => {
|
|
"user_count" => unmentionable_group.user_count,
|
|
},
|
|
)
|
|
expect(response.parsed_body["group_reasons"]).to eq(
|
|
unmentionable_group.name => "not_mentionable",
|
|
)
|
|
|
|
expect(response.parsed_body["max_users_notified_per_group_mention"]).to eq(
|
|
SiteSetting.max_users_notified_per_group_mention,
|
|
)
|
|
end
|
|
end
|
|
|
|
context "with a private message" do
|
|
fab!(:allowed_user, :user)
|
|
fab!(:topic) { Fabricate(:private_message_topic, user: allowed_user) }
|
|
|
|
it "does not work if topic is not visible" do
|
|
get "/composer/mentions.json",
|
|
params: {
|
|
names: [allowed_user.username],
|
|
topic_id: topic.id,
|
|
}
|
|
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
it "finds mentions" do
|
|
sign_in(allowed_user)
|
|
topic.invite_group(Discourse.system_user, unmentionable_group)
|
|
|
|
get "/composer/mentions.json",
|
|
params: {
|
|
names: [
|
|
"invaliduserorgroup",
|
|
user.username,
|
|
allowed_user.username,
|
|
group.name,
|
|
invisible_group.name,
|
|
unmessageable_group.name,
|
|
unmentionable_group.name,
|
|
],
|
|
topic_id: topic.id,
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(response.parsed_body["users"]).to contain_exactly(
|
|
user.username,
|
|
allowed_user.username,
|
|
)
|
|
expect(response.parsed_body["user_reasons"]).to eq(user.username => "private")
|
|
|
|
expect(response.parsed_body["groups"]).to eq(
|
|
group.name => {
|
|
"user_count" => group.user_count,
|
|
},
|
|
unmessageable_group.name => {
|
|
"user_count" => unmessageable_group.user_count,
|
|
},
|
|
unmentionable_group.name => {
|
|
"user_count" => unmentionable_group.user_count,
|
|
},
|
|
)
|
|
expect(response.parsed_body["group_reasons"]).to eq(
|
|
group.name => "not_allowed",
|
|
unmessageable_group.name => "not_allowed",
|
|
unmentionable_group.name => "not_mentionable",
|
|
)
|
|
|
|
expect(response.parsed_body["max_users_notified_per_group_mention"]).to eq(
|
|
SiteSetting.max_users_notified_per_group_mention,
|
|
)
|
|
end
|
|
|
|
it "returns notified_count" do
|
|
sign_in(allowed_user)
|
|
group.add(user)
|
|
topic.invite_group(Discourse.system_user, group)
|
|
|
|
other_group = Fabricate(:group, mentionable_level: Group::ALIAS_LEVELS[:everyone])
|
|
other_group.add(allowed_user)
|
|
other_group.add(user)
|
|
|
|
# Trying to mention other_group which has not been invited, but two of
|
|
# its members have been (allowed_user directly and user via group).
|
|
get "/composer/mentions.json", params: { names: [other_group.name], topic_id: topic.id }
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(response.parsed_body["groups"]).to eq(other_group.name => { "user_count" => 2 })
|
|
expect(response.parsed_body["group_reasons"]).to be_empty
|
|
|
|
other_group.add(Fabricate(:user))
|
|
|
|
get "/composer/mentions.json", params: { names: [other_group.name], topic_id: topic.id }
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(response.parsed_body["groups"]).to eq(
|
|
other_group.name => {
|
|
"user_count" => 3,
|
|
"notified_count" => 2,
|
|
},
|
|
)
|
|
expect(response.parsed_body["group_reasons"]).to eq(other_group.name => "some_not_allowed")
|
|
end
|
|
end
|
|
|
|
context "with a new private message" do
|
|
fab!(:allowed_user, :user)
|
|
|
|
it "finds mentions" do
|
|
get "/composer/mentions.json",
|
|
params: {
|
|
names: [
|
|
"invaliduserorgroup",
|
|
user.username,
|
|
allowed_user.username,
|
|
group.name,
|
|
invisible_group.name,
|
|
unmessageable_group.name,
|
|
unmentionable_group.name,
|
|
],
|
|
allowed_names: [allowed_user.username, unmentionable_group.name],
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(response.parsed_body["users"]).to contain_exactly(
|
|
user.username,
|
|
allowed_user.username,
|
|
)
|
|
expect(response.parsed_body["user_reasons"]).to eq(user.username => "private")
|
|
|
|
expect(response.parsed_body["groups"]).to eq(
|
|
group.name => {
|
|
"user_count" => group.user_count,
|
|
},
|
|
unmessageable_group.name => {
|
|
"user_count" => unmessageable_group.user_count,
|
|
},
|
|
unmentionable_group.name => {
|
|
"user_count" => unmentionable_group.user_count,
|
|
},
|
|
)
|
|
expect(response.parsed_body["group_reasons"]).to eq(
|
|
group.name => "not_allowed",
|
|
unmessageable_group.name => "not_allowed",
|
|
unmentionable_group.name => "not_mentionable",
|
|
)
|
|
|
|
expect(response.parsed_body["max_users_notified_per_group_mention"]).to eq(
|
|
SiteSetting.max_users_notified_per_group_mention,
|
|
)
|
|
end
|
|
|
|
it "returns notified_count" do
|
|
sign_in(allowed_user)
|
|
group.add(user)
|
|
|
|
other_group = Fabricate(:group, mentionable_level: Group::ALIAS_LEVELS[:everyone])
|
|
other_group.add(allowed_user)
|
|
other_group.add(user)
|
|
other_group.add(Fabricate(:user))
|
|
|
|
# Trying to mention other_group which has not been invited, but two of
|
|
# its members have been (allowed_user directly and user via group).
|
|
get "/composer/mentions.json",
|
|
params: {
|
|
names: [other_group.name],
|
|
allowed_names: [allowed_user.username, group.name],
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(response.parsed_body["groups"]).to eq(
|
|
other_group.name => {
|
|
"user_count" => 3,
|
|
"notified_count" => 2,
|
|
},
|
|
)
|
|
expect(response.parsed_body["group_reasons"]).to eq(other_group.name => "some_not_allowed")
|
|
end
|
|
end
|
|
|
|
context "with a new private message to a group with hidden members" do
|
|
fab!(:alice) { Fabricate(:user, username: "alice") }
|
|
fab!(:bob) { Fabricate(:user, username: "bob") }
|
|
fab!(:hidden_members_group) do
|
|
Fabricate(
|
|
:group,
|
|
messageable_level: Group::ALIAS_LEVELS[:everyone],
|
|
mentionable_level: Group::ALIAS_LEVELS[:everyone],
|
|
members_visibility_level: Group.visibility_levels[:staff],
|
|
)
|
|
end
|
|
|
|
before { hidden_members_group.add(alice) }
|
|
|
|
it "does not leak hidden group membership via user_reasons" do
|
|
get "/composer/mentions.json",
|
|
params: {
|
|
names: [alice.username, bob.username],
|
|
allowed_names: [hidden_members_group.name],
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
user_reasons = response.parsed_body["user_reasons"]
|
|
expect(user_reasons[alice.username]).to eq(user_reasons[bob.username])
|
|
end
|
|
end
|
|
|
|
context "with a group with hidden members" do
|
|
fab!(:hidden_members_group) do
|
|
Fabricate(
|
|
:group,
|
|
mentionable_level: Group::ALIAS_LEVELS[:everyone],
|
|
members_visibility_level: Group.visibility_levels[:owners],
|
|
users: [Fabricate(:user)],
|
|
)
|
|
end
|
|
|
|
it "does not return the member count" do
|
|
get "/composer/mentions.json", params: { names: [hidden_members_group.name] }
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.parsed_body["groups"]).to eq(hidden_members_group.name => {})
|
|
end
|
|
end
|
|
|
|
context "with an invalid topic" do
|
|
it "returns an error" do
|
|
get "/composer/mentions.json",
|
|
params: {
|
|
names: [
|
|
"invaliduserorgroup",
|
|
user.username,
|
|
group.name,
|
|
invisible_group.name,
|
|
unmessageable_group.name,
|
|
unmentionable_group.name,
|
|
],
|
|
topic_id: -1,
|
|
}
|
|
|
|
expect(response.status).to eq(403)
|
|
end
|
|
end
|
|
end
|
|
end
|