0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-09 21:45:25 +08:00
discourse/spec/lib/guardian/invite_guardian_spec.rb
Keegan George abe5700838
FEATURE: Allow inviting new users directly as admins (#41748)
**Previously**, getting a second admin onto a site took many steps:
create an invite, wait for the person to sign up, find their profile,
grant admin, and confirm via email.

**In this update**, a redesigned invite modal (gated behind the
`enable_admin_invites` upcoming change) lets admins invite someone as an
admin by email in one step. The invitee becomes a moderator on signup
and an admin once the inviter confirms via the existing admin
confirmation email. The admin onboarding panel's "Invite collaborators"
step now opens this flow with the admin option preselected.

| Invite members | Advanced options | Email delivery |
| --- | --- | --- |
|
![invite-members.png](https://github.com/discourse/discourse/releases/download/_gh-attach-assets/invite-members.png)
|
![invite-members-advanced.png](https://github.com/discourse/discourse/releases/download/_gh-attach-assets/invite-members-advanced.png)
|
![invite-members-email.png](https://github.com/discourse/discourse/releases/download/_gh-attach-assets/invite-members-email.png)
|

| Invite admins | Advanced options | Invite sent |
| --- | --- | --- |
|
![invite-admins.png](https://github.com/discourse/discourse/releases/download/_gh-attach-assets/invite-admins.png)
|
![invite-admins-advanced.png](https://github.com/discourse/discourse/releases/download/_gh-attach-assets/invite-admins-advanced.png)
|
![admin-invite-sent.png](https://github.com/discourse/discourse/releases/download/_gh-attach-assets/admin-invite-sent.png)
|

| Link created | Email invitation sent | Onboarding panel |
| --- | --- | --- |
|
![invite-created.png](https://github.com/discourse/discourse/releases/download/_gh-attach-assets/invite-created.png)
|
![invitation-sent.png](https://github.com/discourse/discourse/releases/download/_gh-attach-assets/invitation-sent.png)
|
![onboarding-banner.png](https://github.com/discourse/discourse/releases/download/_gh-attach-assets/onboarding-banner.png)
|
2026-07-16 14:59:24 -07:00

327 lines
12 KiB
Ruby
Vendored

# frozen_string_literal: true
RSpec.describe InviteGuardian do
fab!(:user)
fab!(:admin)
fab!(:moderator)
fab!(:trust_level_1)
fab!(:trust_level_2)
fab!(:group)
fab!(:another_group, :group)
fab!(:automatic_group) { Fabricate(:group, automatic: true) }
fab!(:topic) { Fabricate(:topic, user: user) }
###### VISIBILITY ######
describe "#can_see_invite_details?" do
it "is false without a logged in user" do
expect(Guardian.new(nil).can_see_invite_details?(user)).to be_falsey
end
it "is false without a user to look at" do
expect(Guardian.new(user).can_see_invite_details?(nil)).to be_falsey
end
it "is true when looking at your own invites" do
expect(Guardian.new(user).can_see_invite_details?(user)).to be_truthy
end
end
describe "#can_see_invite_emails?" do
it "is false without a logged in user" do
expect(Guardian.new(nil).can_see_invite_emails?(user)).to be_falsey
end
it "is false without a user to look at" do
expect(Guardian.new(user).can_see_invite_emails?(nil)).to be_falsey
end
it "is true when looking at your own invites" do
expect(Guardian.new(user).can_see_invite_emails?(user)).to be_truthy
end
end
###### INVITING ######
describe "#can_create_admin_invite?" do
it "is true only for admins when enable_invite_modal_with_roles is enabled" do
SiteSetting.enable_invite_modal_with_roles = true
expect(Guardian.new(admin).can_create_admin_invite?).to eq(true)
expect(Guardian.new(moderator).can_create_admin_invite?).to eq(false)
expect(Guardian.new(user).can_create_admin_invite?).to eq(false)
expect(Guardian.new(nil).can_create_admin_invite?).to eq(false)
end
it "is false for admins when enable_invite_modal_with_roles is disabled" do
SiteSetting.enable_invite_modal_with_roles = false
expect(Guardian.new(admin).can_create_admin_invite?).to eq(false)
end
end
describe "#can_invite_to_forum?" do
it "returns true if user has sufficient trust level" do
SiteSetting.invite_allowed_groups = Group::AUTO_GROUPS[:trust_level_2]
expect(Guardian.new(trust_level_2).can_invite_to_forum?).to be_truthy
expect(Guardian.new(moderator).can_invite_to_forum?).to be_truthy
end
it "returns false if user trust level does not have sufficient trust level" do
SiteSetting.invite_allowed_groups = Group::AUTO_GROUPS[:trust_level_2]
expect(Guardian.new(trust_level_1).can_invite_to_forum?).to be_falsey
end
it "doesn't allow anonymous users to invite" do
expect(Guardian.new.can_invite_to_forum?).to be_falsey
end
it "returns true when the site requires approving users" do
SiteSetting.must_approve_users = true
expect(Guardian.new(trust_level_2).can_invite_to_forum?).to be_truthy
end
it "returns false when max_invites_per_day is 0" do
# let's also break it while here
SiteSetting.max_invites_per_day = "a"
expect(Guardian.new(user).can_invite_to_forum?).to be_falsey
# staff should be immune to max_invites_per_day setting
expect(Guardian.new(moderator).can_invite_to_forum?).to be_truthy
end
context "with groups" do
let(:groups) { [group, another_group] }
before do
user.change_trust_level!(TrustLevel[2])
group.add_owner(user)
end
it "returns false when user is not allowed to edit a group" do
expect(Guardian.new(user).can_invite_to_forum?(groups)).to eq(false)
expect(Guardian.new(admin).can_invite_to_forum?(groups)).to eq(true)
end
it "returns true when user is allowed to edit groups" do
another_group.add_owner(user)
expect(Guardian.new(user).can_invite_to_forum?(groups)).to eq(true)
end
end
end
describe "#can_invite_to?" do
describe "regular topics" do
before do
SiteSetting.invite_allowed_groups = Group::AUTO_GROUPS[:trust_level_2]
user.update!(trust_level: 2)
end
fab!(:category) { Fabricate(:category, read_restricted: true) }
fab!(:topic)
fab!(:private_topic) { Fabricate(:topic, category: category) }
fab!(:user) { topic.user }
let(:private_category) { Fabricate(:private_category, group: group) }
let(:group_private_topic) { Fabricate(:topic, category: private_category) }
let(:group_owner) { group_private_topic.user.tap { |u| group.add_owner(u) } }
it "handles invitation correctly" do
expect(Guardian.new(nil).can_invite_to?(topic)).to be_falsey
expect(Guardian.new(moderator).can_invite_to?(nil)).to be_falsey
expect(Guardian.new(moderator).can_invite_to?(topic)).to be_truthy
expect(Guardian.new(trust_level_1).can_invite_to?(topic)).to be_truthy
SiteSetting.max_invites_per_day = 0
expect(Guardian.new(user).can_invite_to?(topic)).to be_truthy
# staff should be immune to max_invites_per_day setting
expect(Guardian.new(moderator).can_invite_to?(topic)).to be_truthy
end
it "returns false for normal user on private topic" do
expect(Guardian.new(user).can_invite_to?(private_topic)).to be_falsey
end
it "returns false for admin on private topic" do
expect(Guardian.new(admin).can_invite_to?(private_topic)).to be(false)
end
it "returns true for a group owner" do
group_owner.update!(trust_level: 2)
expect(Guardian.new(group_owner).can_invite_to?(group_private_topic)).to be_truthy
end
it "return true for normal users even if must_approve_users" do
SiteSetting.must_approve_users = true
expect(Guardian.new(user).can_invite_to?(topic)).to be_truthy
expect(Guardian.new(admin).can_invite_to?(topic)).to be_truthy
end
describe "for a private category for automatic and non-automatic group" do
let(:category) do
Fabricate(:category, read_restricted: true).tap do |category|
category.groups << automatic_group
category.groups << group
end
end
let(:topic) { Fabricate(:topic, category: category) }
it "should return true for an admin user" do
expect(Guardian.new(admin).can_invite_to?(topic)).to eq(true)
end
it "should return true for a group owner" do
group_owner.update!(trust_level: 2)
expect(Guardian.new(group_owner).can_invite_to?(topic)).to eq(true)
end
it "should return false for a normal user" do
expect(Guardian.new(user).can_invite_to?(topic)).to eq(false)
end
end
describe "for a private category for automatic groups" do
let(:category) do
Fabricate(:private_category, group: automatic_group, read_restricted: true)
end
let(:group_owner) { Fabricate(:user).tap { |user| automatic_group.add_owner(user) } }
let(:topic) { Fabricate(:topic, category: category) }
it "should return false for all type of users" do
expect(Guardian.new(admin).can_invite_to?(topic)).to eq(false)
expect(Guardian.new(group_owner).can_invite_to?(topic)).to eq(false)
expect(Guardian.new(user).can_invite_to?(topic)).to eq(false)
end
end
end
describe "private messages" do
fab!(:user)
fab!(:pm) { Fabricate(:private_message_topic, user: user) }
before do
user.change_trust_level!(TrustLevel[2])
moderator.change_trust_level!(TrustLevel[2])
end
context "when private messages are disabled" do
it "allows an admin to invite to the pm" do
expect(Guardian.new(admin).can_invite_to?(pm)).to be_truthy
expect(Guardian.new(user).can_invite_to?(pm)).to be_truthy
end
end
context "when user does not belong to personal_message_enabled_groups" do
before { SiteSetting.personal_message_enabled_groups = Group::AUTO_GROUPS[:staff] }
it "doesn't allow a regular user to invite" do
expect(Guardian.new(admin).can_invite_to?(pm)).to be_truthy
expect(Guardian.new(user).can_invite_to?(pm)).to be_falsey
end
end
context "when PM has reached the maximum number of recipients" do
before { SiteSetting.max_allowed_message_recipients = 2 }
it "doesn't allow a regular user to invite" do
expect(Guardian.new(user).can_invite_to?(pm)).to be_falsey
end
it "allows staff to invite" do
expect(Guardian.new(admin).can_invite_to?(pm)).to be_truthy
pm.grant_permission_to_user(moderator.email)
expect(Guardian.new(moderator).can_invite_to?(pm)).to be_truthy
end
end
end
end
describe "#can_invite_via_email?" do
it "returns true for all (tl2 and above) users when sso is disabled, local logins are enabled, user approval is not required" do
expect(Guardian.new(trust_level_2).can_invite_via_email?(topic)).to be_truthy
expect(Guardian.new(moderator).can_invite_via_email?(topic)).to be_truthy
expect(Guardian.new(admin).can_invite_via_email?(topic)).to be_truthy
end
it "returns true for all users when sso is enabled" do
SiteSetting.discourse_connect_url = "https://www.example.com/sso"
SiteSetting.discourse_connect_secret = "x" * 10
SiteSetting.enable_discourse_connect = true
expect(Guardian.new(trust_level_2).can_invite_via_email?(topic)).to be_truthy
expect(Guardian.new(moderator).can_invite_via_email?(topic)).to be_truthy
expect(Guardian.new(admin).can_invite_via_email?(topic)).to be_truthy
end
it "returns false for all users when local logins are disabled" do
SiteSetting.enable_local_logins = false
expect(Guardian.new(trust_level_2).can_invite_via_email?(topic)).to be_falsey
expect(Guardian.new(moderator).can_invite_via_email?(topic)).to be_falsey
expect(Guardian.new(admin).can_invite_via_email?(topic)).to be_falsey
end
it "returns correct values when user approval is required" do
SiteSetting.must_approve_users = true
expect(Guardian.new(trust_level_2).can_invite_via_email?(topic)).to be_falsey
expect(Guardian.new(moderator).can_invite_via_email?(topic)).to be_truthy
expect(Guardian.new(admin).can_invite_via_email?(topic)).to be_truthy
end
end
describe "#can_bulk_invite_to_forum?" do
it "returns true for admin users" do
expect(Guardian.new(admin).can_bulk_invite_to_forum?).to be_truthy
end
it "returns false for moderators" do
expect(Guardian.new(moderator).can_bulk_invite_to_forum?).to be_falsey
end
it "returns false for regular users" do
expect(Guardian.new(user).can_bulk_invite_to_forum?).to be_falsey
end
end
###### ACTIONS ######
describe "#can_resend_all_invites?" do
it "returns true for admin users" do
expect(Guardian.new(admin).can_resend_all_invites?).to be_truthy
end
it "returns true for moderators" do
expect(Guardian.new(moderator).can_resend_all_invites?).to be_truthy
end
it "returns false for regular users" do
expect(Guardian.new(user).can_resend_all_invites?).to be_falsey
end
end
###### DELETION ######
describe "#can_destroy_all_invites?" do
it "returns true for admin users for any target user" do
expect(Guardian.new(admin).can_destroy_all_invites?(user)).to be_truthy
end
it "returns true for moderators for their own invites" do
expect(Guardian.new(moderator).can_destroy_all_invites?(moderator)).to be_truthy
end
it "returns false for moderators for another user's invites" do
expect(Guardian.new(moderator).can_destroy_all_invites?(user)).to be_falsey
end
it "returns false for regular users" do
expect(Guardian.new(user).can_destroy_all_invites?(user)).to be_falsey
end
end
end