discourse/spec/models/invite_redeemer_spec.rb
Gerhard Schlager 5c91d9a629
SECURITY: Remove auto approval when redeeming an invite (#16976)
This security fix affects sites which have `SiteSetting.must_approve_users`
enabled. There are intentional and unintentional cases where invited
users can be auto approved and are deemed to have skipped the staff approval process.
Instead of trying to reason about when auto-approval should happen, we have decided that
enabling the `must_approve_users` setting going forward will just mean that all new users
must be explicitly approved by a staff user in the review queue. The only case where users are auto
approved is when the `auto_approve_email_domains` site setting is used.

Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
2022-06-02 16:11:04 +02:00

255 lines
10 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
describe InviteRedeemer do
describe '.create_user_from_invite' do
it "should be created correctly" do
invite = Fabricate(:invite, email: 'walter.white@email.com')
user = InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White')
expect(user.username).to eq('walter')
expect(user.name).to eq('Walter White')
expect(user.email).to eq('walter.white@email.com')
expect(user.approved).to eq(false)
expect(user.active).to eq(false)
end
it "can set the password and ip_address" do
password = 's3cure5tpasSw0rD'
ip_address = '192.168.1.1'
invite = Fabricate(:invite, email: 'walter.white@email.com')
user = InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White', password: password, ip_address: ip_address)
expect(user).to have_password
expect(user.confirm_password?(password)).to eq(true)
expect(user.approved).to eq(false)
expect(user.ip_address).to eq(ip_address)
expect(user.registration_ip_address).to eq(ip_address)
end
it "raises exception with record and errors" do
error = nil
invite = Fabricate(:invite, email: 'walter.white@email.com')
begin
InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White', password: 'aaa')
rescue ActiveRecord::RecordInvalid => e
error = e
end
expect(error).to be_present
expect(error.record.errors[:password]).to be_present
end
it "should unstage user" do
staged_user = Fabricate(:staged, email: 'staged@account.com', active: true, username: 'staged1', name: 'Stage Name')
invite = Fabricate(:invite, email: 'staged@account.com')
user = InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White')
expect(user.id).to eq(staged_user.id)
expect(user.username).to eq('walter')
expect(user.name).to eq('Walter White')
expect(user.staged).to eq(false)
expect(user.email).to eq('staged@account.com')
expect(user.approved).to eq(false)
end
it "activates user invited via email with a token" do
invite = Fabricate(:invite, invited_by: Fabricate(:admin), email: 'walter.white@email.com', emailed_status: Invite.emailed_status_types[:sent])
user = InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White', email_token: invite.email_token)
expect(user.username).to eq('walter')
expect(user.name).to eq('Walter White')
expect(user.email).to eq('walter.white@email.com')
expect(user.approved).to eq(false)
expect(user.active).to eq(true)
end
it "does not activate user invited via email with a wrong token" do
invite = Fabricate(:invite, invited_by: Fabricate(:user), email: 'walter.white@email.com', emailed_status: Invite.emailed_status_types[:sent])
user = InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White', email_token: 'wrong_token')
expect(user.active).to eq(false)
end
it "does not activate user invited via email without a token" do
invite = Fabricate(:invite, invited_by: Fabricate(:user), email: 'walter.white@email.com', emailed_status: Invite.emailed_status_types[:sent])
user = InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White')
expect(user.active).to eq(false)
end
it "does not activate user invited via links" do
invite = Fabricate(:invite, email: 'walter.white@email.com', emailed_status: Invite.emailed_status_types[:not_required])
user = InviteRedeemer.create_user_from_invite(invite: invite, email: invite.email, username: 'walter', name: 'Walter White')
expect(user.username).to eq('walter')
expect(user.name).to eq('Walter White')
expect(user.email).to eq('walter.white@email.com')
expect(user.approved).to eq(false)
expect(user.active).to eq(false)
end
end
describe "#redeem" do
fab!(:invite) { Fabricate(:invite, email: "foobar@example.com") }
let(:name) { 'john snow' }
let(:username) { 'kingofthenorth' }
let(:password) { 'know5nOthiNG' }
let(:invite_redeemer) { InviteRedeemer.new(invite: invite, email: invite.email, username: username, name: name) }
context "when must_approve_users setting is enabled" do
before do
SiteSetting.must_approve_users = true
end
it "should redeem an invite but not approve the user when invite is created by a staff user" do
inviter = invite.invited_by
inviter.update!(admin: true)
user = invite_redeemer.redeem
expect(user.name).to eq(name)
expect(user.username).to eq(username)
expect(user.invited_by).to eq(inviter)
expect(user.approved).to eq(false)
expect(inviter.notifications.count).to eq(1)
end
it "should redeem the invite but not approve the user when invite is created by a regular user" do
inviter = invite.invited_by
user = invite_redeemer.redeem
expect(user.name).to eq(name)
expect(user.username).to eq(username)
expect(user.invited_by).to eq(inviter)
expect(user.approved).to eq(false)
expect(inviter.notifications.count).to eq(1)
end
it "should redeem the invite and approve the user when user email is in auto_approve_email_domains setting" do
SiteSetting.auto_approve_email_domains = "example.com"
user = invite_redeemer.redeem
expect(user.name).to eq(name)
expect(user.username).to eq(username)
expect(user.approved).to eq(true)
expect(user.approved_by).to eq(Discourse.system_user)
end
end
it "should redeem the invite if invited by non staff and approve if staff not required to approve" do
inviter = invite.invited_by
user = invite_redeemer.redeem
expect(user.name).to eq(name)
expect(user.username).to eq(username)
expect(user.invited_by).to eq(inviter)
expect(inviter.notifications.count).to eq(1)
expect(user.approved).to eq(false)
end
it "should delete invite if invited_by user has been removed" do
invite.invited_by.destroy!
expect { invite.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
it "can set password" do
user = InviteRedeemer.new(invite: invite, email: invite.email, username: username, name: name, password: password).redeem
expect(user).to have_password
expect(user.confirm_password?(password)).to eq(true)
expect(user.approved).to eq(false)
end
it "can set custom fields" do
required_field = Fabricate(:user_field)
optional_field = Fabricate(:user_field, required: false)
user_fields = {
required_field.id.to_s => 'value1',
optional_field.id.to_s => 'value2'
}
user = InviteRedeemer.new(invite: invite, email: invite.email, username: username, name: name, password: password, user_custom_fields: user_fields).redeem
expect(user).to be_present
expect(user.custom_fields["user_field_#{required_field.id}"]).to eq('value1')
expect(user.custom_fields["user_field_#{optional_field.id}"]).to eq('value2')
end
it "does not add user to group if inviter does not have permissions" do
group = Fabricate(:group, grant_trust_level: 2)
InvitedGroup.create(group_id: group.id, invite_id: invite.id)
user = InviteRedeemer.new(invite: invite, email: invite.email, username: username, name: name, password: password).redeem
expect(user.group_users.count).to eq(0)
end
it "adds user to group" do
group = Fabricate(:group, grant_trust_level: 2)
InvitedGroup.create(group_id: group.id, invite_id: invite.id)
group.add_owner(invite.invited_by)
user = InviteRedeemer.new(invite: invite, email: invite.email, username: username, name: name, password: password).redeem
expect(user.group_users.count).to eq(4)
expect(user.trust_level).to eq(2)
end
it "only allows one user to be created per invite" do
user = invite_redeemer.redeem
invite.reload
user.email = "john@example.com"
user.save!
another_invite_redeemer = InviteRedeemer.new(invite: invite, email: invite.email, username: username, name: name)
another_user = another_invite_redeemer.redeem
expect(another_user).to eq(nil)
end
it "should correctly update the invite redeemed_at date" do
SiteSetting.invite_expiry_days = 2
invite.update!(created_at: 10.days.ago)
inviter = invite.invited_by
inviter.admin = true
user = invite_redeemer.redeem
invite.reload
expect(user.invited_by).to eq(inviter)
expect(inviter.notifications.count).to eq(1)
expect(invite.invited_users.first).to be_present
end
context 'invite_link' do
fab!(:invite_link) { Fabricate(:invite, email: nil, max_redemptions_allowed: 5, expires_at: 1.month.from_now, emailed_status: Invite.emailed_status_types[:not_required]) }
let(:invite_redeemer) { InviteRedeemer.new(invite: invite_link, email: 'foo@example.com') }
it 'works as expected' do
user = invite_redeemer.redeem
invite_link.reload
expect(user.send_welcome_message).to eq(true)
expect(user.trust_level).to eq(SiteSetting.default_invitee_trust_level)
expect(user.active).to eq(false)
expect(invite_link.redemption_count).to eq(1)
end
it "should not redeem the invite if InvitedUser record already exists for email" do
user = invite_redeemer.redeem
invite_link.reload
another_invite_redeemer = InviteRedeemer.new(invite: invite_link, email: 'foo@example.com')
another_user = another_invite_redeemer.redeem
expect(another_user).to eq(nil)
end
it "should redeem the invite if InvitedUser record does not exists for email" do
user = invite_redeemer.redeem
invite_link.reload
another_invite_redeemer = InviteRedeemer.new(invite: invite_link, email: 'bar@example.com')
another_user = another_invite_redeemer.redeem
expect(another_user.is_a?(User)).to eq(true)
end
end
end
end