Add a distributed mutex around user creation via SSO.

* When two SSO requests containing the same email in the payload are
  sent at the same time, it would sometimes result in two users
  being created but one without an email record. Investigations
  points to ActiveRecord not generating the right statements but
  we have no figured out the reproduction steps yet. We should review
  this after upgrading to Rails 5.2.
This commit is contained in:
Guo Xiang Tan 2018-04-12 16:18:49 +08:00
parent 7ff78cc013
commit 6e46f81123

View File

@ -156,49 +156,53 @@ class DiscourseSingleSignOn < SingleSignOn
end
def match_email_or_create_user(ip_address)
unless user = User.find_by_email(email)
try_name = name.presence
try_username = username.presence
# Use a mutex here to counter SSO requests that are sent at the same time w
# the same email payload
DistributedMutex.synchronize("discourse_single_sign_on_#{email}") do
unless user = User.find_by_email(email)
try_name = name.presence
try_username = username.presence
user_params = {
primary_email: UserEmail.new(email: email, primary: true),
name: try_name || User.suggest_name(try_username || email),
username: UserNameSuggester.suggest(try_username || try_name || email),
ip_address: ip_address
}
user_params = {
primary_email: UserEmail.new(email: email, primary: true),
name: try_name || User.suggest_name(try_username || email),
username: UserNameSuggester.suggest(try_username || try_name || email),
ip_address: ip_address
}
user = User.create!(user_params)
user = User.create!(user_params)
if SiteSetting.verbose_sso_logging
Rails.logger.warn("Verbose SSO log: New User (user_id: #{user.id}) Params: #{user_params} User Params: #{user.attributes} User Errors: #{user.errors.full_messages} Email: #{user.primary_email.attributes} Email Error: #{user.primary_email.errors.full_messages}")
if SiteSetting.verbose_sso_logging
Rails.logger.warn("Verbose SSO log: New User (user_id: #{user.id}) Params: #{user_params} User Params: #{user.attributes} User Errors: #{user.errors.full_messages} Email: #{user.primary_email.attributes} Email Error: #{user.primary_email.errors.full_messages}")
end
end
end
if user
if sso_record = user.single_sign_on_record
sso_record.last_payload = unsigned_payload
sso_record.external_id = external_id
else
if avatar_url.present?
Jobs.enqueue(:download_avatar_from_url,
url: avatar_url,
user_id: user.id,
override_gravatar: SiteSetting.sso_overrides_avatar
if user
if sso_record = user.single_sign_on_record
sso_record.last_payload = unsigned_payload
sso_record.external_id = external_id
else
if avatar_url.present?
Jobs.enqueue(:download_avatar_from_url,
url: avatar_url,
user_id: user.id,
override_gravatar: SiteSetting.sso_overrides_avatar
)
end
user.create_single_sign_on_record!(
last_payload: unsigned_payload,
external_id: external_id,
external_username: username,
external_email: email,
external_name: name,
external_avatar_url: avatar_url
)
end
user.create_single_sign_on_record!(
last_payload: unsigned_payload,
external_id: external_id,
external_username: username,
external_email: email,
external_name: name,
external_avatar_url: avatar_url
)
end
end
user
user
end
end
def change_external_attributes_and_override(sso_record, user)