FIX: Improved 'discovery' error handling, with tests
This commit is contained in:
parent
0008d9bc6f
commit
78a792b5b6
|
@ -0,0 +1,12 @@
|
|||
# We want to use the KVM-based system, so require sudo
|
||||
sudo: required
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- git clone https://github.com/discourse/discourse-plugin-ci
|
||||
|
||||
install: true # Prevent travis doing bundle install
|
||||
|
||||
script:
|
||||
- discourse-plugin-ci/script.sh
|
|
@ -5,5 +5,4 @@ en:
|
|||
openid_connect_client_secret: "OpenID Connect client secret"
|
||||
openid_connect_authorize_scope: "The scopes sent to the authorize endpoint. This must include 'openid'."
|
||||
openid_connect_token_scope: "The scopes sent when requesting the token endpoint. The official specification does not require this."
|
||||
openid_connect_use_userinfo: "Contact the userinfo endpoint for user metadata. If left blank, the 'id_token' will be used instead."
|
||||
openid_connect_error_redirects: "If the callback error_reason contains the first parameter, the user will be redirected to the URL in the second parameter"
|
|
@ -9,8 +9,6 @@ plugins:
|
|||
default: "openid"
|
||||
openid_connect_token_scope:
|
||||
default: ""
|
||||
openid_connect_use_userinfo:
|
||||
default: true
|
||||
openid_connect_error_redirects:
|
||||
default: ''
|
||||
type: list
|
||||
|
|
|
@ -1,30 +1,47 @@
|
|||
require 'omniauth-oauth2'
|
||||
|
||||
module ::OmniAuth
|
||||
module OpenIDConnect
|
||||
class DiscoveryError < Error; end
|
||||
end
|
||||
|
||||
module Strategies
|
||||
class OpenIDConnect < OmniAuth::Strategies::OAuth2
|
||||
option :scope, "openid"
|
||||
option :discovery, true
|
||||
option :use_userinfo, true
|
||||
option :cache, lambda { |key, &blk| blk.call } # Default no-op cache
|
||||
option :error_handler, lambda { |error, message| nil } # Default no-op handler
|
||||
option :authorize_options, [:p]
|
||||
option :token_options, [:p]
|
||||
option :passthrough_authorize_options, [:p]
|
||||
option :passthrough_token_options, [:p]
|
||||
|
||||
option :client_options,
|
||||
site: 'https://op.com/',
|
||||
authorize_url: 'authorize',
|
||||
token_url: 'token',
|
||||
userinfo_endpoint: 'userinfo',
|
||||
discovery_document: nil,
|
||||
site: nil,
|
||||
authorize_url: nil,
|
||||
token_url: nil,
|
||||
userinfo_endpoint: nil,
|
||||
auth_scheme: :basic_auth
|
||||
|
||||
def discover!
|
||||
discovery_document = options.cache.call("openid_discovery_#{options[:client_options][:discovery_document]}") do
|
||||
client.request(:get, options[:client_options][:discovery_document], parse: :json).parsed
|
||||
end
|
||||
options[:client_options][:authorize_url] = discovery_document["authorization_endpoint"].to_s
|
||||
options[:client_options][:token_url] = discovery_document["token_endpoint"].to_s
|
||||
options[:client_options][:userinfo_endpoint] = discovery_document["userinfo_endpoint"].to_s
|
||||
options[:client_options][:site] = discovery_document["issuer"].to_s
|
||||
|
||||
{
|
||||
authorize_url: "authorization_endpoint",
|
||||
token_url: "token_endpoint",
|
||||
site: "issuer"
|
||||
}.each do |internal_key, external_key|
|
||||
val = discovery_document[external_key].to_s
|
||||
raise ::OmniAuth::OpenIDConnect::DiscoveryError.new("missing discovery parameter #{external_key}") if val.nil? || val.empty?
|
||||
options[:client_options][internal_key] = val
|
||||
end
|
||||
|
||||
userinfo_endpoint = options[:client_options][:userinfo_endpoint] = discovery_document["userinfo_endpoint"].to_s
|
||||
if userinfo_endpoint.nil? || userinfo_endpoint.empty?
|
||||
options.use_userinfo = false
|
||||
end
|
||||
end
|
||||
|
||||
def request_phase
|
||||
|
@ -34,14 +51,14 @@ module ::OmniAuth
|
|||
|
||||
def authorize_params
|
||||
super.tap do |params|
|
||||
options[:authorize_options].each do |k|
|
||||
options[:passthrough_authorize_options].each do |k|
|
||||
params[k] = request.params[k.to_s] unless [nil, ''].include?(request.params[k.to_s])
|
||||
end
|
||||
|
||||
params[:scope] = options[:scope]
|
||||
session['omniauth.nonce'] = params[:nonce] = SecureRandom.hex(32)
|
||||
|
||||
options[:token_options].each do |k|
|
||||
options[:passthrough_token_options].each do |k|
|
||||
session["omniauth.param.#{k}"] = request.params[k.to_s] unless [nil, ''].include?(request.params[k.to_s])
|
||||
end
|
||||
end
|
||||
|
@ -95,8 +112,15 @@ module ::OmniAuth
|
|||
if request.params["error"] && request.params["error_description"] && response = options.error_handler.call(request.params["error"], request.params["error_description"])
|
||||
return redirect(response)
|
||||
end
|
||||
discover! if options[:discovery]
|
||||
|
||||
begin
|
||||
discover! if options[:discovery]
|
||||
rescue ::OmniAuth::OpenIDConnect::DiscoveryError => e
|
||||
fail!(:openid_connect_discovery_error, e)
|
||||
end
|
||||
|
||||
oauth2_callback_phase = super
|
||||
|
||||
return oauth2_callback_phase if env['omniauth.error']
|
||||
|
||||
if id_token_info["nonce"].empty? || id_token_info["nonce"] != session.delete("omniauth.nonce")
|
||||
|
@ -113,7 +137,7 @@ module ::OmniAuth
|
|||
|
||||
def token_params
|
||||
params = {}
|
||||
options[:token_options].each do |k|
|
||||
options[:passthrough_token_options].each do |k|
|
||||
val = session.delete("omniauth.param.#{k}")
|
||||
params[k] = val unless [nil, ''].include?(val)
|
||||
end
|
||||
|
|
|
@ -83,7 +83,6 @@ class OpenIDConnectAuthenticator < Auth::ManagedAuthenticator
|
|||
setup: lambda { |env|
|
||||
opts = env['omniauth.strategy'].options
|
||||
opts.deep_merge!(
|
||||
use_userinfo: SiteSetting.openid_connect_use_userinfo,
|
||||
client_id: SiteSetting.openid_connect_client_id,
|
||||
client_secret: SiteSetting.openid_connect_client_secret,
|
||||
client_options: {
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../lib/omniauth_open_id_connect'
|
||||
|
||||
require 'webmock/rspec'
|
||||
WebMock.disable_net_connect!
|
||||
|
||||
describe OmniAuth::Strategies::OpenIDConnect do
|
||||
# let(:request) { double('Request', params: {}, cookies: {}, env: {}) }
|
||||
let(:app) do
|
||||
lambda do
|
||||
[200, {}, ['Hello.']]
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:get, "https://id.example.com/.well-known/openid-configuration").
|
||||
to_return(status: 200, body: {
|
||||
"issuer": "https://id.example.com/",
|
||||
"authorization_endpoint": "https://id.example.com/authorize",
|
||||
"token_endpoint": "https://id.example.com/token",
|
||||
"userinfo_endpoint": "https://id.example.com/userinfo",
|
||||
}.to_json)
|
||||
end
|
||||
|
||||
subject do
|
||||
OmniAuth::Strategies::OpenIDConnect.new(app, 'appid', 'secret',
|
||||
client_options: {
|
||||
discovery_document: "https://id.example.com/.well-known/openid-configuration"
|
||||
}
|
||||
|
||||
).tap do |strategy|
|
||||
# allow(strategy).to receive(:request) do
|
||||
# request
|
||||
# end
|
||||
end
|
||||
end
|
||||
|
||||
before { OmniAuth.config.test_mode = true }
|
||||
|
||||
after { OmniAuth.config.test_mode = false }
|
||||
|
||||
it "throws error for on invalid discovery document" do
|
||||
stub_request(:get, "https://id.example.com/.well-known/openid-configuration").
|
||||
to_return(status: 200, body: {
|
||||
"issuer": "https://id.example.com/",
|
||||
"token_endpoint": "https://id.example.com/token",
|
||||
"userinfo_endpoint": "https://id.example.com/userinfo",
|
||||
}.to_json)
|
||||
|
||||
expect { subject.discover! }.to raise_error(::OmniAuth::OpenIDConnect::DiscoveryError)
|
||||
end
|
||||
|
||||
it "disables userinfo if not included in discovery document" do
|
||||
stub_request(:get, "https://id.example.com/.well-known/openid-configuration").
|
||||
to_return(status: 200, body: {
|
||||
"issuer": "https://id.example.com/",
|
||||
"authorization_endpoint": "https://id.example.com/authorize",
|
||||
"token_endpoint": "https://id.example.com/token",
|
||||
}.to_json)
|
||||
|
||||
subject.discover!
|
||||
expect(subject.options.use_userinfo).to eq(false)
|
||||
end
|
||||
|
||||
context 'with valid document' do
|
||||
before do
|
||||
stub_request(:get, "https://id.example.com/.well-known/openid-configuration").
|
||||
to_return(status: 200, body: {
|
||||
"issuer": "https://id.example.com/",
|
||||
"authorization_endpoint": "https://id.example.com/authorize",
|
||||
"token_endpoint": "https://id.example.com/token",
|
||||
"userinfo_endpoint": "https://id.example.com/userinfo",
|
||||
}.to_json)
|
||||
end
|
||||
|
||||
it "discovers correctly" do
|
||||
subject.discover!
|
||||
expect(subject.options.client_options.site).to eq("https://id.example.com/")
|
||||
expect(subject.options.client_options.authorize_url).to eq("https://id.example.com/authorize")
|
||||
expect(subject.options.client_options.token_url).to eq("https://id.example.com/token")
|
||||
expect(subject.options.client_options.userinfo_endpoint).to eq("https://id.example.com/userinfo")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue