2019-05-03 06:17:27 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-11-28 23:44:16 +08:00
|
|
|
class Auth::ManagedAuthenticator < Auth::Authenticator
|
2020-11-10 18:41:46 +08:00
|
|
|
def is_managed?
|
|
|
|
# Tells core that it can safely assume this authenticator
|
|
|
|
# uses UserAssociatedAccount
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2018-11-28 23:44:16 +08:00
|
|
|
def description_for_user(user)
|
2019-07-17 19:34:02 +08:00
|
|
|
associated_account = UserAssociatedAccount.find_by(provider_name: name, user_id: user.id)
|
|
|
|
return "" if associated_account.nil?
|
|
|
|
description_for_auth_hash(associated_account) || I18n.t("associated_accounts.connected")
|
|
|
|
end
|
|
|
|
|
|
|
|
def description_for_auth_hash(auth_token)
|
|
|
|
return if auth_token&.info.nil?
|
|
|
|
info = auth_token.info
|
|
|
|
info["email"] || info["nickname"] || info["name"]
|
2018-11-28 23:44:16 +08:00
|
|
|
end
|
|
|
|
|
2021-05-21 11:37:17 +08:00
|
|
|
# These three methods are designed to be overridden by child classes
|
2018-11-28 23:44:16 +08:00
|
|
|
def match_by_email
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2022-10-11 18:25:13 +08:00
|
|
|
# Depending on the authenticator, this could be insecure, so it's disabled by default
|
|
|
|
def match_by_username
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2019-03-07 19:31:04 +08:00
|
|
|
def primary_email_verified?(auth_token)
|
|
|
|
# Omniauth providers should only provide verified emails in the :info hash.
|
|
|
|
# This method allows additional checks to be added
|
2023-01-04 15:51:10 +08:00
|
|
|
false
|
2019-03-07 19:31:04 +08:00
|
|
|
end
|
|
|
|
|
2018-11-28 23:44:16 +08:00
|
|
|
def can_revoke?
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def can_connect_existing_user?
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2019-03-14 19:33:30 +08:00
|
|
|
def always_update_user_email?
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2018-11-28 23:44:16 +08:00
|
|
|
def revoke(user, skip_remote: false)
|
|
|
|
association = UserAssociatedAccount.find_by(provider_name: name, user_id: user.id)
|
|
|
|
raise Discourse::NotFound if association.nil?
|
|
|
|
association.destroy!
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def after_authenticate(auth_token, existing_account: nil)
|
|
|
|
# Try and find an association for this account
|
2018-12-10 23:10:06 +08:00
|
|
|
association =
|
|
|
|
UserAssociatedAccount.find_or_initialize_by(
|
|
|
|
provider_name: auth_token[:provider],
|
|
|
|
provider_uid: auth_token[:uid],
|
|
|
|
)
|
2018-11-28 23:44:16 +08:00
|
|
|
|
|
|
|
# Reconnecting to existing account
|
2018-12-10 23:10:06 +08:00
|
|
|
if can_connect_existing_user? && existing_account &&
|
|
|
|
(association.user.nil? || existing_account.id != association.user_id)
|
|
|
|
association.user = existing_account
|
2018-11-28 23:44:16 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# Matching an account by email
|
2020-11-10 18:09:15 +08:00
|
|
|
if match_by_email && association.user.nil? && (user = find_user_by_email(auth_token))
|
2018-11-28 23:44:16 +08:00
|
|
|
UserAssociatedAccount.where(user: user, provider_name: auth_token[:provider]).destroy_all # Destroy existing associations for the new user
|
2018-12-10 23:10:06 +08:00
|
|
|
association.user = user
|
2018-11-28 23:44:16 +08:00
|
|
|
end
|
|
|
|
|
2022-10-11 18:25:13 +08:00
|
|
|
# Matching an account by username
|
|
|
|
if match_by_username && association.user.nil? && SiteSetting.username_change_period.zero? &&
|
|
|
|
(user = find_user_by_username(auth_token))
|
|
|
|
UserAssociatedAccount.where(user: user, provider_name: auth_token[:provider]).destroy_all # Destroy existing associations for the new user
|
|
|
|
association.user = user
|
|
|
|
end
|
|
|
|
|
2018-11-28 23:44:16 +08:00
|
|
|
# Update all the metadata in the association:
|
2018-12-10 23:10:06 +08:00
|
|
|
association.info = auth_token[:info] || {}
|
|
|
|
association.credentials = auth_token[:credentials] || {}
|
|
|
|
association.extra = auth_token[:extra] || {}
|
2018-11-28 23:44:16 +08:00
|
|
|
|
2019-02-27 23:29:26 +08:00
|
|
|
association.last_used = Time.zone.now
|
|
|
|
|
2018-12-10 23:10:06 +08:00
|
|
|
# Save to the DB. Do this even if we don't have a user - it might be linked up later in after_create_account
|
|
|
|
association.save!
|
|
|
|
|
|
|
|
# Update avatar/profile
|
|
|
|
retrieve_avatar(association.user, association.info["image"])
|
|
|
|
retrieve_profile(association.user, association.info)
|
|
|
|
|
|
|
|
# Build the Auth::Result object
|
|
|
|
result = Auth::Result.new
|
|
|
|
info = auth_token[:info]
|
|
|
|
result.email = info[:email]
|
2019-07-24 18:29:18 +08:00
|
|
|
result.name =
|
2023-01-09 20:10:19 +08:00
|
|
|
(
|
2019-07-24 18:29:18 +08:00
|
|
|
if (info[:first_name] && info[:last_name])
|
|
|
|
"#{info[:first_name]} #{info[:last_name]}"
|
2023-01-09 20:10:19 +08:00
|
|
|
else
|
2019-07-24 18:29:18 +08:00
|
|
|
info[:name]
|
2023-01-09 20:10:19 +08:00
|
|
|
end
|
|
|
|
)
|
2020-02-06 00:03:18 +08:00
|
|
|
if result.name.present? && result.name == result.email
|
|
|
|
# Some IDPs send the email address in the name parameter (e.g. Auth0 with default configuration)
|
|
|
|
# We add some generic protection here, so that users don't accidently make their email addresses public
|
|
|
|
result.name = nil
|
|
|
|
end
|
2018-12-10 23:10:06 +08:00
|
|
|
result.username = info[:nickname]
|
2021-02-12 00:26:43 +08:00
|
|
|
result.email_valid = primary_email_verified?(auth_token) if result.email.present?
|
2021-12-23 18:53:17 +08:00
|
|
|
result.overrides_email = always_update_user_email?
|
2018-12-10 23:10:06 +08:00
|
|
|
result.extra_data = { provider: auth_token[:provider], uid: auth_token[:uid] }
|
|
|
|
result.user = association.user
|
2018-11-28 23:44:16 +08:00
|
|
|
|
|
|
|
result
|
|
|
|
end
|
|
|
|
|
2021-12-09 20:30:27 +08:00
|
|
|
def after_create_account(user, auth_result)
|
|
|
|
auth_token = auth_result[:extra_data]
|
2018-12-10 23:10:06 +08:00
|
|
|
association =
|
|
|
|
UserAssociatedAccount.find_or_initialize_by(
|
|
|
|
provider_name: auth_token[:provider],
|
|
|
|
provider_uid: auth_token[:uid],
|
|
|
|
)
|
|
|
|
association.user = user
|
|
|
|
association.save!
|
|
|
|
|
|
|
|
retrieve_avatar(user, association.info["image"])
|
|
|
|
retrieve_profile(user, association.info)
|
2021-12-09 20:30:27 +08:00
|
|
|
|
|
|
|
auth_result.apply_associated_attributes!
|
2018-11-28 23:44:16 +08:00
|
|
|
end
|
|
|
|
|
2020-11-10 18:09:15 +08:00
|
|
|
def find_user_by_email(auth_token)
|
|
|
|
email = auth_token.dig(:info, :email)
|
|
|
|
User.find_by_email(email) if email && primary_email_verified?(auth_token)
|
|
|
|
end
|
|
|
|
|
2022-10-11 18:25:13 +08:00
|
|
|
def find_user_by_username(auth_token)
|
|
|
|
username = auth_token.dig(:info, :nickname)
|
|
|
|
User.find_by_username(username) if username
|
|
|
|
end
|
|
|
|
|
2018-11-28 23:44:16 +08:00
|
|
|
def retrieve_avatar(user, url)
|
|
|
|
return unless user && url
|
|
|
|
return if user.user_avatar.try(:custom_upload_id).present?
|
|
|
|
Jobs.enqueue(:download_avatar_from_url, url: url, user_id: user.id, override_gravatar: false)
|
|
|
|
end
|
2018-12-07 22:04:21 +08:00
|
|
|
|
|
|
|
def retrieve_profile(user, info)
|
|
|
|
return unless user
|
|
|
|
|
2018-12-10 23:10:06 +08:00
|
|
|
bio = info["description"]
|
|
|
|
location = info["location"]
|
2018-12-07 22:04:21 +08:00
|
|
|
|
|
|
|
if bio || location
|
|
|
|
profile = user.user_profile
|
|
|
|
profile.bio_raw = bio unless profile.bio_raw.present?
|
|
|
|
profile.location = location unless profile.location.present?
|
|
|
|
profile.save
|
|
|
|
end
|
|
|
|
end
|
2018-11-28 23:44:16 +08:00
|
|
|
end
|