mirror of
https://github.com/discourse/discourse.git
synced 2024-11-27 21:43:45 +08:00
115859964d
In certain situations, a logged in user can redeem an invite with an email that either doesn't match the invite's email or does not adhere to the email domain restriction of an invite link. The impact of this flaw is aggrevated when the invite has been configured to add the user that accepts the invite into restricted groups.
203 lines
6.0 KiB
Ruby
203 lines
6.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
InviteRedeemer = Struct.new(:invite, :email, :username, :name, :password, :user_custom_fields, :ip_address, :session, :email_token, keyword_init: true) do
|
|
def redeem
|
|
Invite.transaction do
|
|
if can_redeem_invite? && mark_invite_redeemed
|
|
process_invitation
|
|
invited_user
|
|
end
|
|
end
|
|
end
|
|
|
|
# extracted from User cause it is very specific to invites
|
|
def self.create_user_from_invite(email:, invite:, username: nil, name: nil, password: nil, user_custom_fields: nil, ip_address: nil, session: nil, email_token: nil)
|
|
if username && UsernameValidator.new(username).valid_format? && User.username_available?(username, email)
|
|
available_username = username
|
|
else
|
|
available_username = UserNameSuggester.suggest(email)
|
|
end
|
|
|
|
user = User.where(staged: true).with_email(email.strip.downcase).first
|
|
user.unstage! if user
|
|
user ||= User.new
|
|
|
|
user.attributes = {
|
|
email: email,
|
|
username: available_username,
|
|
name: name || available_username,
|
|
active: false,
|
|
trust_level: SiteSetting.default_invitee_trust_level,
|
|
ip_address: ip_address,
|
|
registration_ip_address: ip_address
|
|
}
|
|
|
|
if (!SiteSetting.must_approve_users && SiteSetting.invite_only) ||
|
|
(SiteSetting.must_approve_users? && EmailValidator.can_auto_approve_user?(user.email))
|
|
|
|
ReviewableUser.set_approved_fields!(user, Discourse.system_user)
|
|
end
|
|
|
|
user_fields = UserField.all
|
|
if user_custom_fields.present? && user_fields.present?
|
|
field_params = user_custom_fields || {}
|
|
fields = user.custom_fields
|
|
|
|
user_fields.each do |f|
|
|
field_val = field_params[f.id.to_s]
|
|
fields["#{User::USER_FIELD_PREFIX}#{f.id}"] = field_val[0...UserField.max_length] unless field_val.blank?
|
|
end
|
|
user.custom_fields = fields
|
|
end
|
|
|
|
user.moderator = true if invite.moderator? && invite.invited_by.staff?
|
|
|
|
if password
|
|
user.password = password
|
|
user.password_required!
|
|
end
|
|
|
|
authenticator = UserAuthenticator.new(user, session, require_password: false)
|
|
|
|
if !authenticator.has_authenticator? && !SiteSetting.enable_local_logins
|
|
raise ActiveRecord::RecordNotSaved.new(I18n.t("login.incorrect_username_email_or_password"))
|
|
end
|
|
|
|
authenticator.start
|
|
|
|
if authenticator.email_valid? && !authenticator.authenticated?
|
|
raise ActiveRecord::RecordNotSaved.new(I18n.t("login.incorrect_username_email_or_password"))
|
|
end
|
|
|
|
user.save!
|
|
authenticator.finish
|
|
|
|
if invite.emailed_status != Invite.emailed_status_types[:not_required] && email == invite.email && invite.email_token.present? && email_token == invite.email_token
|
|
user.activate
|
|
end
|
|
|
|
User.find(user.id)
|
|
end
|
|
|
|
private
|
|
|
|
def can_redeem_invite?
|
|
# Invite has already been redeemed
|
|
if !invite.is_invite_link? && InvitedUser.exists?(invite_id: invite.id)
|
|
return false
|
|
end
|
|
|
|
validate_invite_email!
|
|
|
|
existing_user = get_existing_user
|
|
|
|
if existing_user.present? && InvitedUser.exists?(user_id: existing_user.id, invite_id: invite.id)
|
|
return false
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
def validate_invite_email!
|
|
return if email.blank?
|
|
|
|
if invite.email.present? && email.downcase != invite.email.downcase
|
|
raise ActiveRecord::RecordNotSaved.new(I18n.t('invite.not_matching_email'))
|
|
end
|
|
|
|
if invite.domain.present?
|
|
username, domain = email.split('@')
|
|
|
|
if domain.present? && invite.domain != domain
|
|
raise ActiveRecord::RecordNotSaved.new(I18n.t('invite.domain_not_allowed'))
|
|
end
|
|
end
|
|
end
|
|
|
|
def invited_user
|
|
@invited_user ||= get_invited_user
|
|
end
|
|
|
|
def process_invitation
|
|
add_to_private_topics_if_invited
|
|
add_user_to_groups
|
|
send_welcome_message
|
|
notify_invitee
|
|
end
|
|
|
|
def mark_invite_redeemed
|
|
@invited_user_record = InvitedUser.create!(invite_id: invite.id, redeemed_at: Time.zone.now)
|
|
|
|
if @invited_user_record.present?
|
|
Invite.increment_counter(:redemption_count, invite.id)
|
|
delete_duplicate_invites
|
|
end
|
|
|
|
@invited_user_record.present?
|
|
end
|
|
|
|
def get_invited_user
|
|
result = get_existing_user
|
|
|
|
result ||= InviteRedeemer.create_user_from_invite(
|
|
email: email,
|
|
invite: invite,
|
|
username: username,
|
|
name: name,
|
|
password: password,
|
|
user_custom_fields: user_custom_fields,
|
|
ip_address: ip_address,
|
|
session: session,
|
|
email_token: email_token
|
|
)
|
|
result.send_welcome_message = false
|
|
result
|
|
end
|
|
|
|
def get_existing_user
|
|
User.where(admin: false, staged: false).find_by_email(email)
|
|
end
|
|
|
|
def add_to_private_topics_if_invited
|
|
topic_ids = Topic.where(archetype: Archetype::private_message).includes(:invites).where(invites: { email: email }).pluck(:id)
|
|
topic_ids.each do |id|
|
|
TopicAllowedUser.create!(user_id: invited_user.id, topic_id: id) unless TopicAllowedUser.exists?(user_id: invited_user.id, topic_id: id)
|
|
end
|
|
end
|
|
|
|
def add_user_to_groups
|
|
guardian = Guardian.new(invite.invited_by)
|
|
new_group_ids = invite.groups.pluck(:id) - invited_user.group_users.pluck(:group_id)
|
|
new_group_ids.each do |id|
|
|
group = Group.find_by(id: id)
|
|
if guardian.can_edit_group?(group)
|
|
invited_user.group_users.create!(group_id: group.id)
|
|
DiscourseEvent.trigger(:user_added_to_group, invited_user, group, automatic: false)
|
|
end
|
|
end
|
|
end
|
|
|
|
def send_welcome_message
|
|
@invited_user_record.update!(user_id: invited_user.id)
|
|
invited_user.send_welcome_message = true
|
|
end
|
|
|
|
def notify_invitee
|
|
if inviter = invite.invited_by
|
|
inviter.notifications.create!(
|
|
notification_type: Notification.types[:invitee_accepted],
|
|
data: { display_username: invited_user.username }.to_json
|
|
)
|
|
end
|
|
end
|
|
|
|
def delete_duplicate_invites
|
|
Invite
|
|
.where('invites.max_redemptions_allowed = 1')
|
|
.joins("LEFT JOIN invited_users ON invites.id = invited_users.invite_id")
|
|
.where('invited_users.user_id IS NULL')
|
|
.where('invites.email = ? AND invites.id != ?', email, invite.id)
|
|
.delete_all
|
|
end
|
|
end
|