mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 02:41:13 +08:00
DEV: Migrate Github authentication to ManagedAuthenticator (#11170)
This commit adds an additional find_user_by_email hook to ManagedAuthenticator so that GitHub login can continue to support secondary email addresses
The github_user_infos table will be dropped in a follow-up commit.
This is the last core authenticator to be migrated to ManagedAuthenticator 🎉
This commit is contained in:
@ -2,7 +2,7 @@
|
||||
|
||||
require_dependency 'has_errors'
|
||||
|
||||
class Auth::GithubAuthenticator < Auth::Authenticator
|
||||
class Auth::GithubAuthenticator < Auth::ManagedAuthenticator
|
||||
|
||||
def name
|
||||
"github"
|
||||
@ -12,146 +12,40 @@ class Auth::GithubAuthenticator < Auth::Authenticator
|
||||
SiteSetting.enable_github_logins
|
||||
end
|
||||
|
||||
def description_for_user(user)
|
||||
info = GithubUserInfo.find_by(user_id: user.id)
|
||||
info&.screen_name || ""
|
||||
end
|
||||
|
||||
def can_revoke?
|
||||
true
|
||||
end
|
||||
|
||||
def revoke(user, skip_remote: false)
|
||||
info = GithubUserInfo.find_by(user_id: user.id)
|
||||
raise Discourse::NotFound if info.nil?
|
||||
info.destroy!
|
||||
true
|
||||
end
|
||||
|
||||
class GithubEmailChecker
|
||||
include ::HasErrors
|
||||
|
||||
def initialize(validator, email)
|
||||
@validator = validator
|
||||
@email = Email.downcase(email)
|
||||
end
|
||||
|
||||
def valid?()
|
||||
@validator.validate_each(self, :email, @email)
|
||||
errors.blank?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def can_connect_existing_user?
|
||||
true
|
||||
end
|
||||
|
||||
def after_authenticate(auth_token, existing_account: nil)
|
||||
result = Auth::Result.new
|
||||
|
||||
data = auth_token[:info]
|
||||
result.username = screen_name = data[:nickname]
|
||||
result.name = data[:name]
|
||||
|
||||
github_user_id = auth_token[:uid]
|
||||
|
||||
result.extra_data = {
|
||||
github_user_id: github_user_id,
|
||||
github_screen_name: screen_name,
|
||||
}
|
||||
|
||||
user_info = GithubUserInfo.find_by(github_user_id: github_user_id)
|
||||
|
||||
if existing_account && (user_info.nil? || existing_account.id != user_info.user_id)
|
||||
user_info.destroy! if user_info
|
||||
user_info = GithubUserInfo.create(
|
||||
user_id: existing_account.id,
|
||||
screen_name: screen_name,
|
||||
github_user_id: github_user_id
|
||||
)
|
||||
result = super
|
||||
return result if result.user
|
||||
# If email domain restrictions are configured,
|
||||
# pick a secondary email which is allowed
|
||||
all_github_emails(auth_token).each do |candidate|
|
||||
next if !EmailValidator.allowed?(candidate[:email])
|
||||
result.email = candidate[:email]
|
||||
result.email_valid = !!candidate[:verified]
|
||||
break
|
||||
end
|
||||
|
||||
if user_info
|
||||
# If there's existing user info with the given GitHub ID, that's all we
|
||||
# need to know.
|
||||
user = user_info.user
|
||||
result.email = data[:email]
|
||||
result.email_valid = data[:email].present?
|
||||
|
||||
# update GitHub screen_name
|
||||
if user_info.screen_name != screen_name
|
||||
user_info.screen_name = screen_name
|
||||
user_info.save!
|
||||
end
|
||||
else
|
||||
# Potentially use *any* of the emails from GitHub to find a match or
|
||||
# register a new user, with preference given to the primary email.
|
||||
all_emails = Array.new(auth_token[:extra][:all_emails])
|
||||
primary = all_emails.detect { |email| email[:primary] && email[:verified] }
|
||||
all_emails.unshift(primary) if primary.present?
|
||||
|
||||
# Only consider verified emails to match an existing user. We don't want
|
||||
# someone to be able to create a GitHub account with an unverified email
|
||||
# in order to access someone else's Discourse account!
|
||||
all_emails.each do |candidate|
|
||||
if !!candidate[:verified] && (user = User.find_by_email(candidate[:email]))
|
||||
result.email = candidate[:email]
|
||||
result.email_valid = !!candidate[:verified]
|
||||
|
||||
GithubUserInfo
|
||||
.where('user_id = ? OR github_user_id = ?', user.id, github_user_id)
|
||||
.destroy_all
|
||||
|
||||
GithubUserInfo.create!(
|
||||
user_id: user.id,
|
||||
screen_name: screen_name,
|
||||
github_user_id: github_user_id
|
||||
)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# If we *still* don't have a user, check to see if there's an email that
|
||||
# passes validation (this includes allowlist/blocklist filtering if any is
|
||||
# configured). When no allowlist/blocklist is in play, this will simply
|
||||
# choose the primary email since it's at the front of the list.
|
||||
if !user
|
||||
validator = EmailValidator.new(attributes: :email)
|
||||
found_email = false
|
||||
all_emails.each do |candidate|
|
||||
checker = GithubEmailChecker.new(validator, candidate[:email])
|
||||
if checker.valid?
|
||||
result.email = candidate[:email]
|
||||
result.email_valid = !!candidate[:verified]
|
||||
found_email = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if !found_email
|
||||
result.failed = true
|
||||
escaped = Rack::Utils.escape_html(screen_name)
|
||||
result.failed_reason = I18n.t("login.authenticator_error_no_valid_email", account: escaped)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
retrieve_avatar(user, data)
|
||||
|
||||
result.user = user
|
||||
result
|
||||
end
|
||||
|
||||
def after_create_account(user, auth)
|
||||
data = auth[:extra_data]
|
||||
GithubUserInfo.create(
|
||||
user_id: user.id,
|
||||
screen_name: data[:github_screen_name],
|
||||
github_user_id: data[:github_user_id]
|
||||
)
|
||||
def find_user_by_email(auth_token)
|
||||
# Use verified secondary emails to find a match
|
||||
all_github_emails(auth_token).each do |candidate|
|
||||
next if !candidate[:verified]
|
||||
if user = User.find_by_email(candidate[:email])
|
||||
return user
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
retrieve_avatar(user, data)
|
||||
def all_github_emails(auth_token)
|
||||
emails = Array.new(auth_token[:extra][:all_emails])
|
||||
primary_email = emails.find { |email| email[:primary] }
|
||||
if primary_email
|
||||
emails.delete(primary_email)
|
||||
emails.unshift(primary_email)
|
||||
end
|
||||
emails
|
||||
end
|
||||
|
||||
def register_middleware(omniauth)
|
||||
@ -163,12 +57,4 @@ class Auth::GithubAuthenticator < Auth::Authenticator
|
||||
},
|
||||
scope: "user:email"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def retrieve_avatar(user, data)
|
||||
return unless data[:image].present? && user && user.user_avatar&.custom_upload_id.blank?
|
||||
|
||||
Jobs.enqueue(:download_avatar_from_url, url: data[:image], user_id: user.id, override_gravatar: false)
|
||||
end
|
||||
end
|
||||
|
Reference in New Issue
Block a user