discourse/lib/auth/github_authenticator.rb
Sam Saffron 30990006a9 DEV: enable frozen string literal on all files
This reduces chances of errors where consumers of strings mutate inputs
and reduces memory usage of the app.

Test suite passes now, but there may be some stuff left, so we will run
a few sites on a branch prior to merging
2019-05-13 09:31:32 +08:00

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