diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2b3e41d5..f6ea3068 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2"] + ruby: ["2.7", "3.0", "3.1", "3.2", "3.3"] name: Ruby ${{ matrix.ruby }} steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index ce3ff51c..b31511f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,29 @@ -# v0.6.1 (22.02.2023) +# Unreleased + +# v0.8.0 (2024-07-04) + +- Add `send_state` parameter to disable sending of state (https://github.com/omniauth/omniauth_openid_connect/pull/182) + +# v0.7.1 (2023-04-26) + +- Fix handling of JWKS response (https://github.com/omniauth/omniauth_openid_connect/pull/157) + +# v0.7.0 (2023-04-25) + +- Update openid_connect to 2.2 (https://github.com/omniauth/omniauth_openid_connect/pull/153) +- Drop Ruby 2.5 and 2.6 CI support (https://github.com/omniauth/omniauth_openid_connect/pull/154) +- Improvements to README (https://github.com/omniauth/omniauth_openid_connect/pull/152, https://github.com/omniauth/omniauth_openid_connect/pull/151) +- Add option `logout_path` (https://github.com/omniauth/omniauth_openid_connect/pull/143) + +# v0.6.1 (2023-02-22) - Fix uninitialized constant error (https://github.com/omniauth/omniauth_openid_connect/pull/147) -# v0.6.0 (21.01.2023) +# v0.6.0 (2023-01-22) - Support verification of HS256-signed JWTs (https://github.com/omniauth/omniauth_openid_connect/pull/134) -# v0.5.0 (26.12.2022) +# v0.5.0 (2022-12-26) - Support the "nonce" parameter forwarding without a session [#130](https://github.com/omniauth/omniauth_openid_connect/pull/130) - Fetch key from JWKS URI if available [#133](https://github.com/omniauth/omniauth_openid_connect/pull/133) @@ -14,7 +31,7 @@ - Add email_verified claim in user info [#131](https://github.com/omniauth/omniauth_openid_connect/pull/131) - Add PKCE verification support [#128](https://github.com/omniauth/omniauth_openid_connect/pull/128) -# v0.4.0 (06.02.2022) +# v0.4.0 (2022-02-06) - Support dynamic parameters to the authorize URI [#90](https://github.com/omniauth/omniauth_openid_connect/pull/90) - Upgrade Faker and replace Travis with Github Actions [#102](https://github.com/omniauth/omniauth_openid_connect/pull/102) @@ -22,12 +39,12 @@ - Fall back to the discovered jwks when no key specified [#97](https://github.com/omniauth/omniauth_openid_connect/pull/97) - Allow updating to omniauth v2 [#88](https://github.com/omniauth/omniauth_openid_connect/pull/88) -# v0.3.5 (07.06.2020) +# v0.3.5 (2020-06-07) - bugfix: Info from decoded id_token is not exposed into `request.env['omniauth.auth']` [#61](https://github.com/m0n9oose/omniauth_openid_connect/pull/61) - bugfix: NoMethodError (`undefined method 'count' for #`) [#60](https://github.com/m0n9oose/omniauth_openid_connect/pull/60) -# v0.3.4 (21.05.2020) +# v0.3.4 (2020-05-21) - Try to verify id_token when response_type is code [#44](https://github.com/m0n9oose/omniauth_openid_connect/pull/44) - Provide more information on error [#49](https://github.com/m0n9oose/omniauth_openid_connect/pull/49) @@ -36,7 +53,7 @@ - refactor: take uid_field from raw_attributes [#54](https://github.com/m0n9oose/omniauth_openid_connect/pull/54) - chore(ci): add 2.7, ruby-head and jruby-head [#55](https://github.com/m0n9oose/omniauth_openid_connect/pull/55) -# v0.3.3 (09.11.2019) +# v0.3.3 (2019-11-09) - Pass `acr_values` to authorize url [#43](https://github.com/m0n9oose/omniauth_openid_connect/pull/43) - Add raw info for id token [#42](https://github.com/m0n9oose/omniauth_openid_connect/pull/42) @@ -46,17 +63,17 @@ - Fix gemspec homepage [#33](https://github.com/m0n9oose/omniauth_openid_connect/pull/33) - Add support for `response_type` `id_token` [#32](https://github.com/m0n9oose/omniauth_openid_connect/pull/32) -# v0.3.2 (03.08.2019) +# v0.3.2 (2019-08-03) - Use response_mode in `authorize_uri` if the option is defined [#30](https://github.com/m0n9oose/omniauth_openid_connect/pull/30) - Move verification of `id_token` to before accessing tokens [#28](https://github.com/m0n9oose/omniauth_openid_connect/pull/28) - Update omniauth dependency [#26](https://github.com/m0n9oose/omniauth_openid_connect/pull/26) -# v0.3.1 (08.06.2019) +# v0.3.1 (2019-06-08) - Set default OmniAuth name to openid_connect [#23](https://github.com/m0n9oose/omniauth_openid_connect/pull/23) -# v0.3.0 (27.04.2019) +# v0.3.0 (2019-04-07) - RP-Initiated Logout phase [#5](https://github.com/m0n9oose/omniauth_openid_connect/pull/5) - Allows `ui_locales`, `claims_locales` and `login_hint` as request params [#6](https://github.com/m0n9oose/omniauth_openid_connect/pull/6) @@ -65,7 +82,7 @@ - Handle errors when fetching access_token at callback_phase [#17](https://github.com/m0n9oose/omniauth_openid_connect/pull/17) - Allow state method to receive env [#19](https://github.com/m0n9oose/omniauth_openid_connect/pull/19) -# v0.2.4 (06.01.2019) +# v0.2.4 (2019-01-06) - Prompt and login hint [#4](https://github.com/m0n9oose/omniauth_openid_connect/pull/4) - Bump openid_connect dependency [#9](https://github.com/m0n9oose/omniauth_openid_connect/pull/9) diff --git a/README.md b/README.md index b98cd8f6..d9c44e3a 100644 --- a/README.md +++ b/README.md @@ -23,53 +23,79 @@ Or install it yourself as: ## Supported Ruby Versions -OmniAuth::OpenIDConnect is tested under 2.5, 2.6, 2.7, 3.0, 3.1 +OmniAuth::OpenIDConnect is tested under 2.7, 3.0, 3.1, 3.2 ## Usage Example configuration + +```ruby +Rails.application.config.middleware.use OmniAuth::Builder do + provider :openid_connect, { + name: :my_provider, + scope: [:openid, :email, :profile, :address], + response_type: :code, + uid_field: "preferred_username", + client_options: { + port: 443, + scheme: "https", + host: "myprovider.com", + identifier: ENV["OP_CLIENT_ID"], + secret: ENV["OP_SECRET_KEY"], + redirect_uri: "http://myapp.com/users/auth/openid_connect/callback", + }, + } +end +``` + +### with Devise ```ruby -config.omniauth :openid_connect, { - name: :my_provider, - scope: [:openid, :email, :profile, :address], - response_type: :code, - uid_field: "preferred_username", - client_options: { - port: 443, - scheme: "https", - host: "myprovider.com", - identifier: ENV["OP_CLIENT_ID"], - secret: ENV["OP_SECRET_KEY"], - redirect_uri: "http://myapp.com/users/auth/openid_connect/callback", - }, -} +Devise.setup do |config| + config.omniauth :openid_connect, { + name: :my_provider, + scope: [:openid, :email, :profile, :address], + response_type: :code, + uid_field: "preferred_username", + client_options: { + port: 443, + scheme: "https", + host: "myprovider.com", + identifier: ENV["OP_CLIENT_ID"], + secret: ENV["OP_SECRET_KEY"], + redirect_uri: "http://myapp.com/users/auth/openid_connect/callback", + }, + } +end ``` ### Options Overview -| Field | Description | Required | Default | Example/Options | -|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------------------------------|-----------------------------------------------------| -| name | Arbitrary string to identify connection and identify it from other openid_connect providers | no | String: openid_connect | :my_idp | -| issuer | Root url for the authorization server | yes | | https://myprovider.com | -| discovery | Should OpenID discovery be used. This is recommended if the IDP provides a discovery endpoint. See client config for how to manually enter discovered values. | no | false | one of: true, false | -| client_auth_method | Which authentication method to use to authenticate your app with the authorization server | no | Sym: basic | "basic", "jwks" | -| scope | Which OpenID scopes to include (:openid is always required) | no | Array [:openid] | [:openid, :profile, :email] | -| response_type | Which OAuth2 response type to use with the authorization request | no | String: code | one of: 'code', 'id_token' | -| state | A value to be used for the OAuth2 state parameter on the authorization request. Can be a proc that generates a string. | no | Random 16 character string | Proc.new { SecureRandom.hex(32) } | -| require_state | Should state param be verified - this is recommended, not required by the OIDC specification | no | true | false | -| response_mode | The response mode per [spec](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html) | no | nil | one of: :query, :fragment, :form_post, :web_message | -| display | An optional parameter to the authorization request to determine how the authorization and consent page | no | nil | one of: :page, :popup, :touch, :wap | -| prompt | An optional parameter to the authrization request to determine what pages the user will be shown | no | nil | one of: :none, :login, :consent, :select_account | -| send_scope_to_token_endpoint | Should the scope parameter be sent to the authorization token endpoint? | no | true | one of: true, false | -| post_logout_redirect_uri | The logout redirect uri to use per the [session management draft](https://openid.net/specs/openid-connect-session-1_0.html) | no | empty | https://myapp.com/logout/callback | -| uid_field | The field of the user info response to be used as a unique id | no | 'sub' | "sub", "preferred_username" | -| extra_authorize_params | A hash of extra fixed parameters that will be merged to the authorization request | no | Hash | {"tenant" => "common"} | -| allow_authorize_params | A list of allowed dynamic parameters that will be merged to the authorization request | no | Array | [:screen_name] | -| pkce | Enable [PKCE flow](https://oauth.net/2/pkce/) | no | false | one of: true, false | -| pkce_verifier | Specify a custom PKCE verifier code. | no | A random 128-char string | Proc.new { SecureRandom.hex(64) } | -| pkce_options | Specify a custom implementation of the PKCE code challenge/method. | no | SHA256(code_challenge) in hex | Proc to customise the code challenge generation | -| client_options | A hash of client options detailed in its own section | yes | | | -| jwt_secret_base64 | For HMAC with SHA2 (e.g. HS256) signing algorithms, specify the base64-encoded secret used to sign the JWT token. Defaults to the OAuth2 client secret if not specified. | no | client_options.secret | "bXlzZWNyZXQ=\n" +| Field | Description | Required | Default | Example/Options | +|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------------------------------|-----------------------------------------------------| +| name | Arbitrary string to identify connection and identify it from other openid_connect providers | no | String: openid_connect | :my_idp | +| issuer | Root url for the authorization server | yes | | https://myprovider.com | +| discovery | Should OpenID discovery be used. This is recommended if the IDP provides a discovery endpoint. See client config for how to manually enter discovered values. | no | false | one of: true, false | +| client_auth_method | Which authentication method to use to authenticate your app with the authorization server | no | Sym: basic | "basic", "jwks" | +| scope | Which OpenID scopes to include (:openid is always required) | no | Array [:openid] | [:openid, :profile, :email] | +| response_type | Which OAuth2 response type to use with the authorization request | no | String: code | one of: 'code', 'id_token' | +| state | A value to be used for the OAuth2 state parameter on the authorization request. Can be a proc that generates a string. | no | Random 16 character string | Proc.new { SecureRandom.hex(32) } | +| require_state | Should the callback phase require that a state is present. If `send_state` is true, then the callback state must match the authorize state. This is recommended, not required by the OIDC specification. | no | true | false | +| send_state | Should the authorize phase send a `state` parameter - this is recommended, not required by the OIDC specification | no | true | false | +| response_mode | The response mode per [spec](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html) | no | nil | one of: :query, :fragment, :form_post, :web_message | +| display | An optional parameter to the authorization request to determine how the authorization and consent page | no | nil | one of: :page, :popup, :touch, :wap | +| prompt | An optional parameter to the authorization request to determine what pages the user will be shown | no | nil | one of: :none, :login, :consent, :select_account | +| send_scope_to_token_endpoint | Should the scope parameter be sent to the authorization token endpoint? | no | true | one of: true, false | +| post_logout_redirect_uri | The logout redirect uri to use per the [session management draft](https://openid.net/specs/openid-connect-session-1_0.html) | no | empty | https://myapp.com/logout/callback | +| uid_field | The field of the user info response to be used as a unique id | no | 'sub' | "sub", "preferred_username" | +| extra_authorize_params | A hash of extra fixed parameters that will be merged to the authorization request | no | Hash | {"tenant" => "common"} | +| allow_authorize_params | A list of allowed dynamic parameters that will be merged to the authorization request | no | Array | [:screen_name] | +| pkce | Enable [PKCE flow](https://oauth.net/2/pkce/) | no | false | one of: true, false | +| pkce_verifier | Specify a custom PKCE verifier code. | no | A random 128-char string | Proc.new { SecureRandom.hex(64) } | +| pkce_options | Specify a custom implementation of the PKCE code challenge/method. | no | SHA256(code_challenge) in hex | Proc to customise the code challenge generation | +| client_options | A hash of client options detailed in its own section | yes | | | +| jwt_secret_base64 | For HMAC with SHA2 (e.g. HS256) signing algorithms, specify the base64-encoded secret used to sign the JWT token. Defaults to the OAuth2 client secret if not specified. | no | client_options.secret | "bXlzZWNyZXQ=\n" | +| logout_path | The log out is only triggered when the request path ends on this path | no | '/logout' | '/sign_out' | +| acr_values | Authentication Class Reference (ACR) values to be passed to the authorize_uri to enforce a specific level, see [RFC9470](https://www.rfc-editor.org/rfc/rfc9470.html) | no | nil | "c1 c2" | ### Client Config Options @@ -83,6 +109,7 @@ These are the configuration options for the client_options hash of the configura | scheme | The http scheme to use | https | | | host | The host of the authorization server | nil | | | port | The port for the authorization server | 443 | | +| audience | The intended consumer (`aud` field) of the id_token | nil | | | authorization_endpoint | The authorize endpoint on the authorization server | /authorize | yes | | token_endpoint | The token endpoint on the authorization server | /token | yes | | userinfo_endpoint | The user info endpoint on the authorization server | /userinfo | yes | @@ -131,7 +158,7 @@ For the full low down on OpenID Connect, please check out ## Contributing -1. Fork it ( http://github.com/m0n9oose/omniauth-openid-connect/fork ) +1. Fork it ( http://github.com/omniauth/omniauth_openid_connect/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Cover your changes with tests and make sure they're green (`bundle install && bundle exec rake test`) 4. Commit your changes (`git commit -am 'Add some feature'`) diff --git a/lib/omniauth/openid_connect/version.rb b/lib/omniauth/openid_connect/version.rb index fe4cee10..2305938d 100644 --- a/lib/omniauth/openid_connect/version.rb +++ b/lib/omniauth/openid_connect/version.rb @@ -2,6 +2,6 @@ module OmniAuth module OpenIDConnect - VERSION = '0.6.1' + VERSION = '0.8.0' end end diff --git a/lib/omniauth/strategies/openid_connect.rb b/lib/omniauth/strategies/openid_connect.rb index 0084584a..110ae46a 100644 --- a/lib/omniauth/strategies/openid_connect.rb +++ b/lib/omniauth/strategies/openid_connect.rb @@ -28,6 +28,7 @@ class OpenIDConnect # rubocop:disable Metrics/ClassLength scheme: 'https', host: nil, port: 443, + audience: nil, authorization_endpoint: '/authorize', token_endpoint: '/token', userinfo_endpoint: '/userinfo', @@ -42,6 +43,7 @@ class OpenIDConnect # rubocop:disable Metrics/ClassLength option :client_x509_signing_key option :scope, [:openid] option :response_type, 'code' # ['code', 'id_token'] + option :send_state, true option :require_state, true option :state option :response_mode # [:query, :fragment, :form_post, :web_message] @@ -68,6 +70,8 @@ class OpenIDConnect # rubocop:disable Metrics/ClassLength code_challenge_method: 'S256', } + option :logout_path, '/logout' + def uid user_info.raw_attributes[options.uid_field.to_sym] || user_info.sub end @@ -118,7 +122,12 @@ def request_phase def callback_phase error = params['error_reason'] || params['error'] error_description = params['error_description'] || params['error_reason'] - invalid_state = (options.require_state && params['state'].to_s.empty?) || params['state'] != stored_state + invalid_state = + if options.send_state + (options.require_state && params['state'].to_s.empty?) || params['state'] != stored_state + else + false + end raise CallbackError, error: params['error'], reason: error_description, uri: params['error_uri'] if error raise CallbackError, error: :csrf_detected, reason: "Invalid 'state' parameter" if invalid_state @@ -167,13 +176,12 @@ def end_session_uri end_session_uri.to_s end - def authorize_uri + def authorize_uri # rubocop:disable Metrics/AbcSize client.redirect_uri = redirect_uri opts = { response_type: options.response_type, response_mode: options.response_mode, scope: options.scope, - state: new_state, login_hint: params['login_hint'], ui_locales: params['ui_locales'], claims_locales: params['claims_locales'], @@ -183,6 +191,7 @@ def authorize_uri acr_values: options.acr_values, } + opts[:state] = new_state if options.send_state opts.merge!(options.extra_authorize_params) unless options.extra_authorize_params.empty? options.allow_authorize_params.each do |key| @@ -226,7 +235,7 @@ def pkce_authorize_params(verifier) private def fetch_key - @fetch_key ||= parse_jwk_key(::OpenIDConnect.http_client.get_content(client_options.jwks_uri)) + @fetch_key ||= parse_jwk_key(::OpenIDConnect.http_client.get(client_options.jwks_uri).body) end def base64_decoded_jwt_secret @@ -408,7 +417,7 @@ def parse_x509_key(key) end def parse_jwk_key(key) - json = JSON.parse(key) + json = key.is_a?(String) ? JSON.parse(key) : key return JSON::JWK::Set.new(json['keys']) if json.key?('keys') JSON::JWK.new(json) @@ -440,7 +449,7 @@ def end_session_endpoint_is_valid? end def logout_path_pattern - @logout_path_pattern ||= %r{\A#{Regexp.quote(request_path)}(/logout)} + @logout_path_pattern ||= /\A#{Regexp.quote(request_path)}#{options.logout_path}/ end def id_token_callback_phase @@ -470,9 +479,14 @@ def configured_response_type def verify_id_token!(id_token) return unless id_token - decode_id_token(id_token).verify!(issuer: options.issuer, - client_id: client_options.identifier, - nonce: params['nonce'].presence || stored_nonce) + verify_kwargs = { + issuer: options.issuer, + client_id: client_options.identifier, + nonce: params['nonce'].presence || stored_nonce, + } + verify_kwargs.merge!(audience: client_options.audience) if client_options.audience + + decode_id_token(id_token).verify!(**verify_kwargs) end class CallbackError < StandardError diff --git a/omniauth_openid_connect.gemspec b/omniauth_openid_connect.gemspec index 13cd882e..5b4be7a1 100644 --- a/omniauth_openid_connect.gemspec +++ b/omniauth_openid_connect.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |spec| spec.email = ['jjbohn@gmail.com', 'm0n9oose@gmail.com'] spec.summary = 'OpenID Connect Strategy for OmniAuth' spec.description = 'OpenID Connect Strategy for OmniAuth.' - spec.homepage = 'https://github.com/m0n9oose/omniauth_openid_connect' + spec.homepage = 'https://github.com/omniauth/omniauth_openid_connect' spec.license = 'MIT' spec.files = `git ls-files -z`.split("\x0") @@ -20,23 +20,24 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.metadata = { - 'bug_tracker_uri' => 'https://github.com/m0n9oose/omniauth_openid_connect/issues', - 'changelog_uri' => 'https://github.com/m0n9oose/omniauth_openid_connect/releases', - 'documentation_uri' => "https://github.com/m0n9oose/omniauth_openid_connect/tree/v#{spec.version}#readme", - 'source_code_uri' => "https://github.com/m0n9oose/omniauth_openid_connect/tree/v#{spec.version}", + 'bug_tracker_uri' => 'https://github.com/omniauth/omniauth_openid_connect/issues', + 'changelog_uri' => 'https://github.com/omniauth/omniauth_openid_connect/releases', + 'documentation_uri' => "https://github.com/omniauth/omniauth_openid_connect/tree/v#{spec.version}#readme", + 'source_code_uri' => "https://github.com/omniauth/omniauth_openid_connect/tree/v#{spec.version}", 'rubygems_mfa_required' => 'true', } spec.add_dependency 'omniauth', '>= 1.9', '< 3' - spec.add_dependency 'openid_connect', '~> 1.1' + spec.add_dependency 'openid_connect', '~> 2.2' spec.add_development_dependency 'faker', '~> 2.0' spec.add_development_dependency 'guard', '~> 2.14' spec.add_development_dependency 'guard-bundler', '~> 2.2' spec.add_development_dependency 'guard-minitest', '~> 2.4' - spec.add_development_dependency 'minitest', '~> 5.1' - spec.add_development_dependency 'mocha', '~> 1.7' + spec.add_development_dependency 'minitest', '~> 5.20' + spec.add_development_dependency 'mocha', '~> 2.1' spec.add_development_dependency 'rake', '~> 12.0' spec.add_development_dependency 'rubocop', '~> 1.12' spec.add_development_dependency 'simplecov', '~> 0.21' spec.add_development_dependency 'simplecov-lcov', '~> 0.8' + spec.add_development_dependency 'webmock', '~> 3.18' end diff --git a/test/lib/omniauth/strategies/openid_connect_test.rb b/test/lib/omniauth/strategies/openid_connect_test.rb index 3b53f78d..ffa9f708 100644 --- a/test/lib/omniauth/strategies/openid_connect_test.rb +++ b/test/lib/omniauth/strategies/openid_connect_test.rb @@ -69,6 +69,17 @@ def test_logout_phase_with_discovery_and_post_logout_redirect_uri strategy.other_phase end + def test_logout_phase_with_logout_path + strategy.options.issuer = 'example.com' + strategy.options.client_options.host = 'example.com' + strategy.options.logout_path = '/sign_out' + + request.stubs(:path).returns('/auth/openid_connect/sign_out') + + strategy.expects(:call_app!) + strategy.other_phase + end + def test_logout_phase strategy.options.issuer = 'example.com' strategy.options.client_options.host = 'example.com' @@ -237,6 +248,27 @@ def test_callback_phase_with_id_token strategy.callback_phase end + def test_callback_phase_with_audience + state = SecureRandom.hex(16) + strategy.options.response_type = 'id_token' + strategy.options.issuer = 'example.com' + strategy.options.client_options.audience = 'my_audience' + + id_token = stub('OpenIDConnect::ResponseObject::IdToken') + id_token.expects(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, audience: 'my_audience', + nonce: nonce).returns(true) + id_token.stubs(:raw_attributes, :to_h).returns(payload) + + request.stubs(:params).returns('state' => state, 'nounce' => nonce, 'id_token' => id_token) + request.stubs(:path).returns('') + + strategy.stubs(:decode_id_token).returns(id_token) + strategy.stubs(:stored_state).returns(state) + + strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce }) + strategy.callback_phase + end + def test_callback_phase_with_id_token_and_param_provided_nonce # rubocop:disable Metrics/AbcSize code = SecureRandom.hex(16) state = SecureRandom.hex(16) @@ -442,6 +474,50 @@ def test_callback_phase_with_discovery # rubocop:disable Metrics/AbcSize strategy.callback_phase end + def test_callback_phase_with_send_state_disabled # rubocop:disable Metrics/AbcSize + code = SecureRandom.hex(16) + + strategy.options.client_options.host = 'example.com' + strategy.options.require_state = true + strategy.options.send_state = false + strategy.options.discovery = true + refute_match(/state/, strategy.authorize_uri, 'URI must not contain state') + + request.stubs(:params).returns('code' => code) + request.stubs(:path).returns('') + + issuer = stub('OpenIDConnect::Discovery::Issuer') + issuer.stubs(:issuer).returns('https://example.com/') + ::OpenIDConnect::Discovery::Provider.stubs(:discover!).returns(issuer) + + config = stub('OpenIDConnect::Discovery::Provder::Config') + config.stubs(:authorization_endpoint).returns('https://example.com/authorization') + config.stubs(:token_endpoint).returns('https://example.com/token') + config.stubs(:userinfo_endpoint).returns('https://example.com/userinfo') + config.stubs(:jwks_uri).returns('https://example.com/jwks') + config.stubs(:jwks).returns(JSON::JWK::Set.new(jwks['keys'])) + + ::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('https://example.com/').returns(config) + + id_token = stub('OpenIDConnect::ResponseObject::IdToken') + id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email') + id_token.stubs(:verify!).with(issuer: 'https://example.com/', client_id: @identifier, nonce: nonce).returns(true) + ::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token) + + strategy.unstub(:user_info) + access_token = stub('OpenIDConnect::AccessToken') + access_token.stubs(:access_token) + access_token.stubs(:refresh_token) + access_token.stubs(:expires_in) + access_token.stubs(:scope) + access_token.stubs(:id_token).returns(jwt.to_s) + client.expects(:access_token!).at_least_once.returns(access_token) + access_token.expects(:userinfo!).returns(user_info) + + strategy.call!('rack.session' => { 'omniauth.nonce' => nonce }) + strategy.callback_phase + end + def test_callback_phase_with_no_state_without_state_verification # rubocop:disable Metrics/AbcSize code = SecureRandom.hex(16) @@ -509,10 +585,10 @@ def test_callback_phase_with_jwks_uri strategy.options.client_options.jwks_uri = 'https://jwks.example.com' strategy.options.response_type = 'id_token' - HTTPClient - .any_instance.stubs(:get_content) - .with(strategy.options.client_options.jwks_uri) - .returns(jwks.to_json) + stub_request(:get, strategy.options.client_options.jwks_uri).to_return( + body: jwks.to_json, + headers: { 'Content-Type' => 'application/json' } + ) strategy.unstub(:user_info) access_token = stub('OpenIDConnect::AccessToken') @@ -788,8 +864,7 @@ def test_option_client_auth_method access_token: 'test_access_token', id_token: jwt.to_s, token_type: 'Bearer', - }.to_json - success = Struct.new(:status, :body).new(200, json_response) + } request.stubs(:path).returns('') strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce }) @@ -798,11 +873,13 @@ def test_option_client_auth_method id_token.stubs(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, nonce: nonce).returns(true) ::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token) - HTTPClient.any_instance.stubs(:post).with( - "#{ opts.scheme }://#{ opts.host }:#{ opts.port }#{ opts.token_endpoint }", - { scope: 'openid', grant_type: :client_credentials, client_id: @identifier, client_secret: @secret }, - {} - ).returns(success) + url = "#{ opts.scheme }://#{ opts.host }:#{ opts.port }#{ opts.token_endpoint }" + body = { scope: 'openid', grant_type: 'client_credentials', client_id: @identifier, client_secret: @secret } + + stub_request(:post, url).with(body: body).to_return( + body: json_response.to_json, + headers: { 'Content-Type' => 'application/json' } + ) assert(strategy.send(:access_token)) end diff --git a/test/strategy_test_case.rb b/test/strategy_test_case.rb index 1492d02b..f040b423 100644 --- a/test/strategy_test_case.rb +++ b/test/strategy_test_case.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class StrategyTestCase < MiniTest::Test +class StrategyTestCase < Minitest::Test class DummyApp def call(env); end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 908ea450..9bf7ba94 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,6 +5,7 @@ require 'mocha/minitest' require 'faker' require 'active_support' +require 'webmock/minitest' SimpleCov.start do if ENV['CI']