169 lines
4.7 KiB
Ruby
169 lines
4.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_dependency 'has_errors'
|
|
|
|
class Auth::GithubAuthenticator < Auth::Authenticator
|
|
|
|
def name
|
|
"github"
|
|
end
|
|
|
|
def enabled?
|
|
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)
|
|
return 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
|
|
)
|
|
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?
|
|
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 whitelist/blacklist filtering if any is
|
|
# configured). When no whitelist/blacklist 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]
|
|
)
|
|
|
|
retrieve_avatar(user, data)
|
|
end
|
|
|
|
def register_middleware(omniauth)
|
|
omniauth.provider :github,
|
|
setup: lambda { |env|
|
|
strategy = env["omniauth.strategy"]
|
|
strategy.options[:client_id] = SiteSetting.github_client_id
|
|
strategy.options[:client_secret] = SiteSetting.github_client_secret
|
|
},
|
|
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
|