DEV: Fix new Rubocop offenses
This commit is contained in:
parent
cd51d8b811
commit
5cd7a79baf
37
Gemfile.lock
37
Gemfile.lock
|
@ -1,11 +1,31 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
|
activesupport (7.1.3.2)
|
||||||
|
base64
|
||||||
|
bigdecimal
|
||||||
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
|
connection_pool (>= 2.2.5)
|
||||||
|
drb
|
||||||
|
i18n (>= 1.6, < 2)
|
||||||
|
minitest (>= 5.1)
|
||||||
|
mutex_m
|
||||||
|
tzinfo (~> 2.0)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
|
base64 (0.2.0)
|
||||||
|
bigdecimal (3.1.6)
|
||||||
|
concurrent-ruby (1.2.3)
|
||||||
|
connection_pool (2.4.1)
|
||||||
|
drb (2.2.1)
|
||||||
|
i18n (1.14.3)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
racc (~> 1.7)
|
||||||
json (2.7.1)
|
json (2.7.1)
|
||||||
language_server-protocol (3.17.0.3)
|
language_server-protocol (3.17.0.3)
|
||||||
|
minitest (5.22.2)
|
||||||
|
mutex_m (0.2.0)
|
||||||
parallel (1.24.0)
|
parallel (1.24.0)
|
||||||
parser (3.3.0.4)
|
parser (3.3.0.5)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
prettier_print (1.2.1)
|
prettier_print (1.2.1)
|
||||||
|
@ -13,7 +33,7 @@ GEM
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
regexp_parser (2.9.0)
|
regexp_parser (2.9.0)
|
||||||
rexml (3.2.6)
|
rexml (3.2.6)
|
||||||
rubocop (1.60.0)
|
rubocop (1.61.0)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
|
@ -24,22 +44,27 @@ GEM
|
||||||
rubocop-ast (>= 1.30.0, < 2.0)
|
rubocop-ast (>= 1.30.0, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
rubocop-ast (1.30.0)
|
rubocop-ast (1.31.1)
|
||||||
parser (>= 3.2.1.0)
|
parser (>= 3.3.0.4)
|
||||||
rubocop-capybara (2.20.0)
|
rubocop-capybara (2.20.0)
|
||||||
rubocop (~> 1.41)
|
rubocop (~> 1.41)
|
||||||
rubocop-discourse (3.6.0)
|
rubocop-discourse (3.7.1)
|
||||||
|
activesupport (>= 6.1)
|
||||||
rubocop (>= 1.59.0)
|
rubocop (>= 1.59.0)
|
||||||
|
rubocop-capybara (>= 2.0.0)
|
||||||
|
rubocop-factory_bot (>= 2.0.0)
|
||||||
rubocop-rspec (>= 2.25.0)
|
rubocop-rspec (>= 2.25.0)
|
||||||
rubocop-factory_bot (2.25.1)
|
rubocop-factory_bot (2.25.1)
|
||||||
rubocop (~> 1.41)
|
rubocop (~> 1.41)
|
||||||
rubocop-rspec (2.26.1)
|
rubocop-rspec (2.27.1)
|
||||||
rubocop (~> 1.40)
|
rubocop (~> 1.40)
|
||||||
rubocop-capybara (~> 2.17)
|
rubocop-capybara (~> 2.17)
|
||||||
rubocop-factory_bot (~> 2.22)
|
rubocop-factory_bot (~> 2.22)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
syntax_tree (6.2.0)
|
syntax_tree (6.2.0)
|
||||||
prettier_print (>= 1.2.0)
|
prettier_print (>= 1.2.0)
|
||||||
|
tzinfo (2.0.6)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
unicode-display_width (2.5.0)
|
unicode-display_width (2.5.0)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
|
|
|
@ -0,0 +1,260 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class OAuth2BasicAuthenticator < Auth::ManagedAuthenticator
|
||||||
|
def name
|
||||||
|
"oauth2_basic"
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_revoke?
|
||||||
|
SiteSetting.oauth2_allow_association_change
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_connect_existing_user?
|
||||||
|
SiteSetting.oauth2_allow_association_change
|
||||||
|
end
|
||||||
|
|
||||||
|
def register_middleware(omniauth)
|
||||||
|
omniauth.provider :oauth2_basic,
|
||||||
|
name: name,
|
||||||
|
setup:
|
||||||
|
lambda { |env|
|
||||||
|
opts = env["omniauth.strategy"].options
|
||||||
|
opts[:client_id] = SiteSetting.oauth2_client_id
|
||||||
|
opts[:client_secret] = SiteSetting.oauth2_client_secret
|
||||||
|
opts[:provider_ignores_state] = SiteSetting.oauth2_disable_csrf
|
||||||
|
opts[:client_options] = {
|
||||||
|
authorize_url: SiteSetting.oauth2_authorize_url,
|
||||||
|
token_url: SiteSetting.oauth2_token_url,
|
||||||
|
token_method: SiteSetting.oauth2_token_url_method.downcase.to_sym,
|
||||||
|
}
|
||||||
|
opts[:authorize_options] = SiteSetting
|
||||||
|
.oauth2_authorize_options
|
||||||
|
.split("|")
|
||||||
|
.map(&:to_sym)
|
||||||
|
|
||||||
|
if SiteSetting.oauth2_authorize_signup_url.present? &&
|
||||||
|
ActionDispatch::Request.new(env).params["signup"].present?
|
||||||
|
opts[:client_options][
|
||||||
|
:authorize_url
|
||||||
|
] = SiteSetting.oauth2_authorize_signup_url
|
||||||
|
end
|
||||||
|
|
||||||
|
if SiteSetting.oauth2_send_auth_header? &&
|
||||||
|
SiteSetting.oauth2_send_auth_body?
|
||||||
|
# For maximum compatibility we include both header and body auth by default
|
||||||
|
# This is a little unusual, and utilising multiple authentication methods
|
||||||
|
# is technically disallowed by the spec (RFC2749 Section 5.2)
|
||||||
|
opts[:client_options][:auth_scheme] = :request_body
|
||||||
|
opts[:token_params] = {
|
||||||
|
headers: {
|
||||||
|
"Authorization" => basic_auth_header,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
elsif SiteSetting.oauth2_send_auth_header?
|
||||||
|
opts[:client_options][:auth_scheme] = :basic_auth
|
||||||
|
else
|
||||||
|
opts[:client_options][:auth_scheme] = :request_body
|
||||||
|
end
|
||||||
|
|
||||||
|
unless SiteSetting.oauth2_scope.blank?
|
||||||
|
opts[:scope] = SiteSetting.oauth2_scope
|
||||||
|
end
|
||||||
|
|
||||||
|
opts[:client_options][:connection_build] = lambda do |builder|
|
||||||
|
if SiteSetting.oauth2_debug_auth && defined?(OAuth2FaradayFormatter)
|
||||||
|
builder.response :logger,
|
||||||
|
Rails.logger,
|
||||||
|
{ bodies: true, formatter: OAuth2FaradayFormatter }
|
||||||
|
end
|
||||||
|
|
||||||
|
builder.request :url_encoded # form-encode POST params
|
||||||
|
builder.adapter FinalDestination::FaradayAdapter # make requests with FinalDestination::HTTP
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def basic_auth_header
|
||||||
|
"Basic " +
|
||||||
|
Base64.strict_encode64("#{SiteSetting.oauth2_client_id}:#{SiteSetting.oauth2_client_secret}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def walk_path(fragment, segments, seg_index = 0)
|
||||||
|
first_seg = segments[seg_index]
|
||||||
|
return if first_seg.blank? || fragment.blank?
|
||||||
|
return nil unless fragment.is_a?(Hash) || fragment.is_a?(Array)
|
||||||
|
first_seg = segments[seg_index].scan(/([\d+])/).length > 0 ? first_seg.split("[")[0] : first_seg
|
||||||
|
if fragment.is_a?(Hash)
|
||||||
|
deref = fragment[first_seg] || fragment[first_seg.to_sym]
|
||||||
|
else
|
||||||
|
array_index = 0
|
||||||
|
if (seg_index > 0)
|
||||||
|
last_index = segments[seg_index - 1].scan(/([\d+])/).flatten() || [0]
|
||||||
|
array_index = last_index.length > 0 ? last_index[0].to_i : 0
|
||||||
|
end
|
||||||
|
if fragment.any? && fragment.length >= array_index - 1
|
||||||
|
deref = fragment[array_index][first_seg]
|
||||||
|
else
|
||||||
|
deref = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if (deref.blank? || seg_index == segments.size - 1)
|
||||||
|
deref
|
||||||
|
else
|
||||||
|
seg_index += 1
|
||||||
|
walk_path(deref, segments, seg_index)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def json_walk(result, user_json, prop, custom_path: nil)
|
||||||
|
path = custom_path || SiteSetting.public_send("oauth2_json_#{prop}_path")
|
||||||
|
if path.present?
|
||||||
|
#this.[].that is the same as this.that, allows for both this[0].that and this.[0].that path styles
|
||||||
|
path = path.gsub(".[].", ".").gsub(".[", "[")
|
||||||
|
segments = parse_segments(path)
|
||||||
|
val = walk_path(user_json, segments)
|
||||||
|
result[prop] = val if val.present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_segments(path)
|
||||||
|
segments = [+""]
|
||||||
|
quoted = false
|
||||||
|
escaped = false
|
||||||
|
|
||||||
|
path
|
||||||
|
.split("")
|
||||||
|
.each do |char|
|
||||||
|
next_char_escaped = false
|
||||||
|
if !escaped && (char == '"')
|
||||||
|
quoted = !quoted
|
||||||
|
elsif !escaped && !quoted && (char == ".")
|
||||||
|
segments.append +""
|
||||||
|
elsif !escaped && (char == '\\')
|
||||||
|
next_char_escaped = true
|
||||||
|
else
|
||||||
|
segments.last << char
|
||||||
|
end
|
||||||
|
escaped = next_char_escaped
|
||||||
|
end
|
||||||
|
|
||||||
|
segments
|
||||||
|
end
|
||||||
|
|
||||||
|
def log(info)
|
||||||
|
Rails.logger.warn("OAuth2 Debugging: #{info}") if SiteSetting.oauth2_debug_auth
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_user_details(token, id)
|
||||||
|
user_json_url = SiteSetting.oauth2_user_json_url.sub(":token", token.to_s).sub(":id", id.to_s)
|
||||||
|
user_json_method = SiteSetting.oauth2_user_json_url_method.downcase.to_sym
|
||||||
|
|
||||||
|
bearer_token = "Bearer #{token}"
|
||||||
|
connection = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
|
||||||
|
headers = { "Authorization" => bearer_token, "Accept" => "application/json" }
|
||||||
|
user_json_response = connection.run_request(user_json_method, user_json_url, nil, headers)
|
||||||
|
|
||||||
|
log <<-LOG
|
||||||
|
user_json request: #{user_json_method} #{user_json_url}
|
||||||
|
|
||||||
|
request headers: #{headers}
|
||||||
|
|
||||||
|
response status: #{user_json_response.status}
|
||||||
|
|
||||||
|
response body:
|
||||||
|
#{user_json_response.body}
|
||||||
|
LOG
|
||||||
|
|
||||||
|
if user_json_response.status == 200
|
||||||
|
user_json = JSON.parse(user_json_response.body)
|
||||||
|
|
||||||
|
log("user_json:\n#{user_json.to_yaml}")
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
if user_json.present?
|
||||||
|
json_walk(result, user_json, :user_id)
|
||||||
|
json_walk(result, user_json, :username)
|
||||||
|
json_walk(result, user_json, :name)
|
||||||
|
json_walk(result, user_json, :email)
|
||||||
|
json_walk(result, user_json, :email_verified)
|
||||||
|
json_walk(result, user_json, :avatar)
|
||||||
|
|
||||||
|
DiscoursePluginRegistry.oauth2_basic_additional_json_paths.each do |detail|
|
||||||
|
prop = "extra:#{detail}"
|
||||||
|
json_walk(result, user_json, prop, custom_path: detail)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def primary_email_verified?(auth)
|
||||||
|
return true if SiteSetting.oauth2_email_verified
|
||||||
|
verified = auth["info"]["email_verified"]
|
||||||
|
verified = true if verified == "true"
|
||||||
|
verified = false if verified == "false"
|
||||||
|
verified
|
||||||
|
end
|
||||||
|
|
||||||
|
def always_update_user_email?
|
||||||
|
SiteSetting.oauth2_overrides_email
|
||||||
|
end
|
||||||
|
|
||||||
|
def after_authenticate(auth, existing_account: nil)
|
||||||
|
log <<-LOG
|
||||||
|
after_authenticate response:
|
||||||
|
|
||||||
|
creds:
|
||||||
|
#{auth["credentials"].to_hash.to_yaml}
|
||||||
|
|
||||||
|
uid: #{auth["uid"]}
|
||||||
|
|
||||||
|
info:
|
||||||
|
#{auth["info"].to_hash.to_yaml}
|
||||||
|
|
||||||
|
extra:
|
||||||
|
#{auth["extra"].to_hash.to_yaml}
|
||||||
|
LOG
|
||||||
|
|
||||||
|
if SiteSetting.oauth2_fetch_user_details? && SiteSetting.oauth2_user_json_url.present?
|
||||||
|
if fetched_user_details = fetch_user_details(auth["credentials"]["token"], auth["uid"])
|
||||||
|
auth["uid"] = fetched_user_details[:user_id] if fetched_user_details[:user_id]
|
||||||
|
auth["info"]["nickname"] = fetched_user_details[:username] if fetched_user_details[
|
||||||
|
:username
|
||||||
|
]
|
||||||
|
auth["info"]["image"] = fetched_user_details[:avatar] if fetched_user_details[:avatar]
|
||||||
|
%w[name email email_verified].each do |property|
|
||||||
|
auth["info"][property] = fetched_user_details[property.to_sym] if fetched_user_details[
|
||||||
|
property.to_sym
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
DiscoursePluginRegistry.oauth2_basic_additional_json_paths.each do |detail|
|
||||||
|
auth["extra"][detail] = fetched_user_details["extra:#{detail}"]
|
||||||
|
end
|
||||||
|
|
||||||
|
DiscoursePluginRegistry.oauth2_basic_required_json_paths.each do |x|
|
||||||
|
if fetched_user_details[x[:path]] != x[:required_value]
|
||||||
|
result = Auth::Result.new
|
||||||
|
result.failed = true
|
||||||
|
result.failed_reason = x[:error_message]
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
result = Auth::Result.new
|
||||||
|
result.failed = true
|
||||||
|
result.failed_reason = I18n.t("login.authenticator_error_fetch_user_details")
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
super(auth, existing_account: existing_account)
|
||||||
|
end
|
||||||
|
|
||||||
|
def enabled?
|
||||||
|
SiteSetting.oauth2_enabled
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,31 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "faraday/logging/formatter"
|
||||||
|
|
||||||
|
class OAuth2FaradayFormatter < Faraday::Logging::Formatter
|
||||||
|
def request(env)
|
||||||
|
warn <<~LOG
|
||||||
|
OAuth2 Debugging: request #{env.method.upcase} #{env.url}
|
||||||
|
|
||||||
|
Headers:
|
||||||
|
#{env.request_headers.to_yaml}
|
||||||
|
|
||||||
|
Body:
|
||||||
|
#{env[:body].to_yaml}
|
||||||
|
LOG
|
||||||
|
end
|
||||||
|
|
||||||
|
def response(env)
|
||||||
|
warn <<~LOG
|
||||||
|
OAuth2 Debugging: response status #{env.status}
|
||||||
|
|
||||||
|
From #{env.method.upcase} #{env.url}
|
||||||
|
|
||||||
|
Headers:
|
||||||
|
#{env.request_headers.to_yaml}
|
||||||
|
|
||||||
|
Body:
|
||||||
|
#{env[:body].to_yaml}
|
||||||
|
LOG
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,37 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class OmniAuth::Strategies::Oauth2Basic < ::OmniAuth::Strategies::OAuth2
|
||||||
|
option :name, "oauth2_basic"
|
||||||
|
|
||||||
|
uid do
|
||||||
|
if path = SiteSetting.oauth2_callback_user_id_path.split(".")
|
||||||
|
recurse(access_token, [*path]) if path.present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
info do
|
||||||
|
if paths = SiteSetting.oauth2_callback_user_info_paths.split("|")
|
||||||
|
result = Hash.new
|
||||||
|
paths.each do |p|
|
||||||
|
segments = p.split(":")
|
||||||
|
if segments.length == 2
|
||||||
|
key = segments.first
|
||||||
|
path = [*segments.last.split(".")]
|
||||||
|
result[key] = recurse(access_token, path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def callback_url
|
||||||
|
Discourse.base_url_no_prefix + script_name + callback_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def recurse(obj, keys)
|
||||||
|
return nil if !obj
|
||||||
|
k = keys.shift
|
||||||
|
result = obj.respond_to?(k) ? obj.send(k) : obj[k]
|
||||||
|
keys.empty? ? result : recurse(result, keys)
|
||||||
|
end
|
||||||
|
end
|
331
plugin.rb
331
plugin.rb
|
@ -9,70 +9,9 @@
|
||||||
|
|
||||||
enabled_site_setting :oauth2_enabled
|
enabled_site_setting :oauth2_enabled
|
||||||
|
|
||||||
class ::OmniAuth::Strategies::Oauth2Basic < ::OmniAuth::Strategies::OAuth2
|
require_relative "lib/omniauth/strategies/oauth2_basic"
|
||||||
option :name, "oauth2_basic"
|
require_relative "lib/oauth2_faraday_formatter"
|
||||||
|
require_relative "lib/oauth2_basic_authenticator"
|
||||||
uid do
|
|
||||||
if path = SiteSetting.oauth2_callback_user_id_path.split(".")
|
|
||||||
recurse(access_token, [*path]) if path.present?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
info do
|
|
||||||
if paths = SiteSetting.oauth2_callback_user_info_paths.split("|")
|
|
||||||
result = Hash.new
|
|
||||||
paths.each do |p|
|
|
||||||
segments = p.split(":")
|
|
||||||
if segments.length == 2
|
|
||||||
key = segments.first
|
|
||||||
path = [*segments.last.split(".")]
|
|
||||||
result[key] = recurse(access_token, path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def callback_url
|
|
||||||
Discourse.base_url_no_prefix + script_name + callback_path
|
|
||||||
end
|
|
||||||
|
|
||||||
def recurse(obj, keys)
|
|
||||||
return nil if !obj
|
|
||||||
k = keys.shift
|
|
||||||
result = obj.respond_to?(k) ? obj.send(k) : obj[k]
|
|
||||||
keys.empty? ? result : recurse(result, keys)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
require "faraday/logging/formatter"
|
|
||||||
class OAuth2FaradayFormatter < Faraday::Logging::Formatter
|
|
||||||
def request(env)
|
|
||||||
warn <<~LOG
|
|
||||||
OAuth2 Debugging: request #{env.method.upcase} #{env.url}
|
|
||||||
|
|
||||||
Headers:
|
|
||||||
#{env.request_headers.to_yaml}
|
|
||||||
|
|
||||||
Body:
|
|
||||||
#{env[:body].to_yaml}
|
|
||||||
LOG
|
|
||||||
end
|
|
||||||
|
|
||||||
def response(env)
|
|
||||||
warn <<~LOG
|
|
||||||
OAuth2 Debugging: response status #{env.status}
|
|
||||||
|
|
||||||
From #{env.method.upcase} #{env.url}
|
|
||||||
|
|
||||||
Headers:
|
|
||||||
#{env.request_headers.to_yaml}
|
|
||||||
|
|
||||||
Body:
|
|
||||||
#{env[:body].to_yaml}
|
|
||||||
LOG
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# You should use this register if you want to add custom paths to traverse the user details JSON.
|
# You should use this register if you want to add custom paths to traverse the user details JSON.
|
||||||
# We'll store the value in the user associated account's extra attribute hash using the full path as the key.
|
# We'll store the value in the user associated account's extra attribute hash using the full path as the key.
|
||||||
|
@ -90,268 +29,6 @@ DiscoursePluginRegistry.define_filtered_register :oauth2_basic_additional_json_p
|
||||||
# }, self)
|
# }, self)
|
||||||
DiscoursePluginRegistry.define_filtered_register :oauth2_basic_required_json_paths
|
DiscoursePluginRegistry.define_filtered_register :oauth2_basic_required_json_paths
|
||||||
|
|
||||||
class ::OAuth2BasicAuthenticator < Auth::ManagedAuthenticator
|
|
||||||
def name
|
|
||||||
"oauth2_basic"
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_revoke?
|
|
||||||
SiteSetting.oauth2_allow_association_change
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_connect_existing_user?
|
|
||||||
SiteSetting.oauth2_allow_association_change
|
|
||||||
end
|
|
||||||
|
|
||||||
def register_middleware(omniauth)
|
|
||||||
omniauth.provider :oauth2_basic,
|
|
||||||
name: name,
|
|
||||||
setup:
|
|
||||||
lambda { |env|
|
|
||||||
opts = env["omniauth.strategy"].options
|
|
||||||
opts[:client_id] = SiteSetting.oauth2_client_id
|
|
||||||
opts[:client_secret] = SiteSetting.oauth2_client_secret
|
|
||||||
opts[:provider_ignores_state] = SiteSetting.oauth2_disable_csrf
|
|
||||||
opts[:client_options] = {
|
|
||||||
authorize_url: SiteSetting.oauth2_authorize_url,
|
|
||||||
token_url: SiteSetting.oauth2_token_url,
|
|
||||||
token_method: SiteSetting.oauth2_token_url_method.downcase.to_sym,
|
|
||||||
}
|
|
||||||
opts[:authorize_options] = SiteSetting
|
|
||||||
.oauth2_authorize_options
|
|
||||||
.split("|")
|
|
||||||
.map(&:to_sym)
|
|
||||||
|
|
||||||
if SiteSetting.oauth2_authorize_signup_url.present? &&
|
|
||||||
ActionDispatch::Request.new(env).params["signup"].present?
|
|
||||||
opts[:client_options][
|
|
||||||
:authorize_url
|
|
||||||
] = SiteSetting.oauth2_authorize_signup_url
|
|
||||||
end
|
|
||||||
|
|
||||||
if SiteSetting.oauth2_send_auth_header? &&
|
|
||||||
SiteSetting.oauth2_send_auth_body?
|
|
||||||
# For maximum compatibility we include both header and body auth by default
|
|
||||||
# This is a little unusual, and utilising multiple authentication methods
|
|
||||||
# is technically disallowed by the spec (RFC2749 Section 5.2)
|
|
||||||
opts[:client_options][:auth_scheme] = :request_body
|
|
||||||
opts[:token_params] = {
|
|
||||||
headers: {
|
|
||||||
"Authorization" => basic_auth_header,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
elsif SiteSetting.oauth2_send_auth_header?
|
|
||||||
opts[:client_options][:auth_scheme] = :basic_auth
|
|
||||||
else
|
|
||||||
opts[:client_options][:auth_scheme] = :request_body
|
|
||||||
end
|
|
||||||
|
|
||||||
unless SiteSetting.oauth2_scope.blank?
|
|
||||||
opts[:scope] = SiteSetting.oauth2_scope
|
|
||||||
end
|
|
||||||
|
|
||||||
opts[:client_options][:connection_build] = lambda do |builder|
|
|
||||||
if SiteSetting.oauth2_debug_auth && defined?(OAuth2FaradayFormatter)
|
|
||||||
builder.response :logger,
|
|
||||||
Rails.logger,
|
|
||||||
{ bodies: true, formatter: OAuth2FaradayFormatter }
|
|
||||||
end
|
|
||||||
|
|
||||||
builder.request :url_encoded # form-encode POST params
|
|
||||||
builder.adapter FinalDestination::FaradayAdapter # make requests with FinalDestination::HTTP
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def basic_auth_header
|
|
||||||
"Basic " +
|
|
||||||
Base64.strict_encode64("#{SiteSetting.oauth2_client_id}:#{SiteSetting.oauth2_client_secret}")
|
|
||||||
end
|
|
||||||
|
|
||||||
def walk_path(fragment, segments, seg_index = 0)
|
|
||||||
first_seg = segments[seg_index]
|
|
||||||
return if first_seg.blank? || fragment.blank?
|
|
||||||
return nil unless fragment.is_a?(Hash) || fragment.is_a?(Array)
|
|
||||||
first_seg = segments[seg_index].scan(/([\d+])/).length > 0 ? first_seg.split("[")[0] : first_seg
|
|
||||||
if fragment.is_a?(Hash)
|
|
||||||
deref = fragment[first_seg] || fragment[first_seg.to_sym]
|
|
||||||
else
|
|
||||||
array_index = 0
|
|
||||||
if (seg_index > 0)
|
|
||||||
last_index = segments[seg_index - 1].scan(/([\d+])/).flatten() || [0]
|
|
||||||
array_index = last_index.length > 0 ? last_index[0].to_i : 0
|
|
||||||
end
|
|
||||||
if fragment.any? && fragment.length >= array_index - 1
|
|
||||||
deref = fragment[array_index][first_seg]
|
|
||||||
else
|
|
||||||
deref = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if (deref.blank? || seg_index == segments.size - 1)
|
|
||||||
deref
|
|
||||||
else
|
|
||||||
seg_index += 1
|
|
||||||
walk_path(deref, segments, seg_index)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def json_walk(result, user_json, prop, custom_path: nil)
|
|
||||||
path = custom_path || SiteSetting.public_send("oauth2_json_#{prop}_path")
|
|
||||||
if path.present?
|
|
||||||
#this.[].that is the same as this.that, allows for both this[0].that and this.[0].that path styles
|
|
||||||
path = path.gsub(".[].", ".").gsub(".[", "[")
|
|
||||||
segments = parse_segments(path)
|
|
||||||
val = walk_path(user_json, segments)
|
|
||||||
result[prop] = val if val.present?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_segments(path)
|
|
||||||
segments = [+""]
|
|
||||||
quoted = false
|
|
||||||
escaped = false
|
|
||||||
|
|
||||||
path
|
|
||||||
.split("")
|
|
||||||
.each do |char|
|
|
||||||
next_char_escaped = false
|
|
||||||
if !escaped && (char == '"')
|
|
||||||
quoted = !quoted
|
|
||||||
elsif !escaped && !quoted && (char == ".")
|
|
||||||
segments.append +""
|
|
||||||
elsif !escaped && (char == '\\')
|
|
||||||
next_char_escaped = true
|
|
||||||
else
|
|
||||||
segments.last << char
|
|
||||||
end
|
|
||||||
escaped = next_char_escaped
|
|
||||||
end
|
|
||||||
|
|
||||||
segments
|
|
||||||
end
|
|
||||||
|
|
||||||
def log(info)
|
|
||||||
Rails.logger.warn("OAuth2 Debugging: #{info}") if SiteSetting.oauth2_debug_auth
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_user_details(token, id)
|
|
||||||
user_json_url = SiteSetting.oauth2_user_json_url.sub(":token", token.to_s).sub(":id", id.to_s)
|
|
||||||
user_json_method = SiteSetting.oauth2_user_json_url_method.downcase.to_sym
|
|
||||||
|
|
||||||
bearer_token = "Bearer #{token}"
|
|
||||||
connection = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
|
|
||||||
headers = { "Authorization" => bearer_token, "Accept" => "application/json" }
|
|
||||||
user_json_response = connection.run_request(user_json_method, user_json_url, nil, headers)
|
|
||||||
|
|
||||||
log <<-LOG
|
|
||||||
user_json request: #{user_json_method} #{user_json_url}
|
|
||||||
|
|
||||||
request headers: #{headers}
|
|
||||||
|
|
||||||
response status: #{user_json_response.status}
|
|
||||||
|
|
||||||
response body:
|
|
||||||
#{user_json_response.body}
|
|
||||||
LOG
|
|
||||||
|
|
||||||
if user_json_response.status == 200
|
|
||||||
user_json = JSON.parse(user_json_response.body)
|
|
||||||
|
|
||||||
log("user_json:\n#{user_json.to_yaml}")
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
if user_json.present?
|
|
||||||
json_walk(result, user_json, :user_id)
|
|
||||||
json_walk(result, user_json, :username)
|
|
||||||
json_walk(result, user_json, :name)
|
|
||||||
json_walk(result, user_json, :email)
|
|
||||||
json_walk(result, user_json, :email_verified)
|
|
||||||
json_walk(result, user_json, :avatar)
|
|
||||||
|
|
||||||
DiscoursePluginRegistry.oauth2_basic_additional_json_paths.each do |detail|
|
|
||||||
prop = "extra:#{detail}"
|
|
||||||
json_walk(result, user_json, prop, custom_path: detail)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
result
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def primary_email_verified?(auth)
|
|
||||||
return true if SiteSetting.oauth2_email_verified
|
|
||||||
verified = auth["info"]["email_verified"]
|
|
||||||
verified = true if verified == "true"
|
|
||||||
verified = false if verified == "false"
|
|
||||||
verified
|
|
||||||
end
|
|
||||||
|
|
||||||
def always_update_user_email?
|
|
||||||
SiteSetting.oauth2_overrides_email
|
|
||||||
end
|
|
||||||
|
|
||||||
def after_authenticate(auth, existing_account: nil)
|
|
||||||
log <<-LOG
|
|
||||||
after_authenticate response:
|
|
||||||
|
|
||||||
creds:
|
|
||||||
#{auth["credentials"].to_hash.to_yaml}
|
|
||||||
|
|
||||||
uid: #{auth["uid"]}
|
|
||||||
|
|
||||||
info:
|
|
||||||
#{auth["info"].to_hash.to_yaml}
|
|
||||||
|
|
||||||
extra:
|
|
||||||
#{auth["extra"].to_hash.to_yaml}
|
|
||||||
LOG
|
|
||||||
|
|
||||||
if SiteSetting.oauth2_fetch_user_details? && SiteSetting.oauth2_user_json_url.present?
|
|
||||||
if fetched_user_details = fetch_user_details(auth["credentials"]["token"], auth["uid"])
|
|
||||||
auth["uid"] = fetched_user_details[:user_id] if fetched_user_details[:user_id]
|
|
||||||
auth["info"]["nickname"] = fetched_user_details[:username] if fetched_user_details[
|
|
||||||
:username
|
|
||||||
]
|
|
||||||
auth["info"]["image"] = fetched_user_details[:avatar] if fetched_user_details[:avatar]
|
|
||||||
%w[name email email_verified].each do |property|
|
|
||||||
auth["info"][property] = fetched_user_details[property.to_sym] if fetched_user_details[
|
|
||||||
property.to_sym
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
DiscoursePluginRegistry.oauth2_basic_additional_json_paths.each do |detail|
|
|
||||||
auth["extra"][detail] = fetched_user_details["extra:#{detail}"]
|
|
||||||
end
|
|
||||||
|
|
||||||
DiscoursePluginRegistry.oauth2_basic_required_json_paths.each do |x|
|
|
||||||
if fetched_user_details[x[:path]] != x[:required_value]
|
|
||||||
result = Auth::Result.new
|
|
||||||
result.failed = true
|
|
||||||
result.failed_reason = x[:error_message]
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
result = Auth::Result.new
|
|
||||||
result.failed = true
|
|
||||||
result.failed_reason = I18n.t("login.authenticator_error_fetch_user_details")
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
super(auth, existing_account: existing_account)
|
|
||||||
end
|
|
||||||
|
|
||||||
def enabled?
|
|
||||||
SiteSetting.oauth2_enabled
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
auth_provider title_setting: "oauth2_button_title", authenticator: OAuth2BasicAuthenticator.new
|
auth_provider title_setting: "oauth2_button_title", authenticator: OAuth2BasicAuthenticator.new
|
||||||
|
|
||||||
load File.expand_path(
|
require_relative "lib/validators/oauth2_basic/oauth2_fetch_user_details_validator"
|
||||||
"../lib/validators/oauth2_basic/oauth2_fetch_user_details_validator.rb",
|
|
||||||
__FILE__,
|
|
||||||
)
|
|
||||||
|
|
Loading…
Reference in New Issue