omniauth_openid_connect 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +34 -2
- data/.rubocop.yml +1 -4
- data/CHANGELOG.md +9 -1
- data/Gemfile +6 -0
- data/README.md +30 -29
- data/Rakefile +2 -0
- data/lib/omniauth/openid_connect/version.rb +1 -1
- data/lib/omniauth/strategies/openid_connect.rb +54 -18
- data/omniauth_openid_connect.gemspec +2 -3
- data/test/lib/omniauth/strategies/openid_connect_test.rb +197 -65
- data/test/strategy_test_case.rb +35 -3
- data/test/test_helper.rb +17 -7
- metadata +24 -44
- data/.github/config/rubocop_linter_action.yml +0 -59
- data/.github/workflows/rubocop.yml +0 -22
- data/test/fixtures/id_token.txt +0 -1
- data/test/fixtures/jwks.json +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34218d7218e2aca766623a1252b641baa20d81330cfd38c67a733f9221d76aad
|
4
|
+
data.tar.gz: 54f874832122f3cc1444280d8820d222a4dff7ef4c3580eb899d4da1efb38051
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4465213a06e82fd61d9997d0ec1b9f993620dead092add6ba2f07b34aa08dca53bcb63d92a256839c7310478c89ecff9359778e5721cf1cc6dcc300e5d780f8
|
7
|
+
data.tar.gz: 8fdbda1f579f271f6273a6380a7a9cf581b4b1239b589a1e2cb98799b8731056c3ec222c519e445d95387749ab62b58839e4005906938e0e1f76b9d396e76565
|
data/.github/workflows/main.yml
CHANGED
@@ -9,12 +9,12 @@ on:
|
|
9
9
|
types: [opened, synchronize, reopened]
|
10
10
|
|
11
11
|
jobs:
|
12
|
-
|
12
|
+
test:
|
13
13
|
runs-on: ubuntu-latest
|
14
14
|
strategy:
|
15
15
|
fail-fast: false
|
16
16
|
matrix:
|
17
|
-
ruby: ["2.5", "2.6", "2.7", "3.0"]
|
17
|
+
ruby: ["2.5", "2.6", "2.7", "3.0", "3.1"]
|
18
18
|
name: Ruby ${{ matrix.ruby }}
|
19
19
|
|
20
20
|
steps:
|
@@ -29,3 +29,35 @@ jobs:
|
|
29
29
|
|
30
30
|
- name: Run tests
|
31
31
|
run: bundle exec rake
|
32
|
+
|
33
|
+
- name: Coveralls Parallel
|
34
|
+
uses: coverallsapp/github-action@master
|
35
|
+
with:
|
36
|
+
github-token: ${{ secrets.github_token }}
|
37
|
+
flag-name: ruby-${{ matrix.ruby }}
|
38
|
+
parallel: true
|
39
|
+
|
40
|
+
finish:
|
41
|
+
needs: test
|
42
|
+
runs-on: ubuntu-latest
|
43
|
+
steps:
|
44
|
+
- name: Coveralls Finished
|
45
|
+
uses: coverallsapp/github-action@master
|
46
|
+
with:
|
47
|
+
github-token: ${{ secrets.github_token }}
|
48
|
+
parallel-finished: true
|
49
|
+
|
50
|
+
rubocop:
|
51
|
+
runs-on: ubuntu-latest
|
52
|
+
steps:
|
53
|
+
- name: Checkout code
|
54
|
+
uses: actions/checkout@v2
|
55
|
+
|
56
|
+
- name: Setup Ruby
|
57
|
+
uses: ruby/setup-ruby@v1
|
58
|
+
with:
|
59
|
+
bundler-cache: true
|
60
|
+
ruby-version: "2.7"
|
61
|
+
|
62
|
+
- name: rubocop
|
63
|
+
run: bundle exec rubocop --parallel
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,12 @@
|
|
1
|
-
# v0.
|
1
|
+
# v0.5.0 (26.12.2022)
|
2
|
+
|
3
|
+
- Support the "nonce" parameter forwarding without a session [#130](https://github.com/omniauth/omniauth_openid_connect/pull/130)
|
4
|
+
- Fetch key from JWKS URI if available [#133](https://github.com/omniauth/omniauth_openid_connect/pull/133)
|
5
|
+
- Make the state parameter verification optional [#122](https://github.com/omniauth/omniauth_openid_connect/pull/122)
|
6
|
+
- Add email_verified claim in user info [#131](https://github.com/omniauth/omniauth_openid_connect/pull/131)
|
7
|
+
- Add PKCE verification support [#128](https://github.com/omniauth/omniauth_openid_connect/pull/128)
|
8
|
+
|
9
|
+
# v0.4.0 (06.02.2022)
|
2
10
|
|
3
11
|
- Support dynamic parameters to the authorize URI [#90](https://github.com/omniauth/omniauth_openid_connect/pull/90)
|
4
12
|
- Upgrade Faker and replace Travis with Github Actions [#102](https://github.com/omniauth/omniauth_openid_connect/pull/102)
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,3 @@
|
|
1
|
-
# Maintainers Wanted
|
2
|
-
|
3
|
-
This project is looking for maintainers.
|
4
|
-
|
5
|
-
Due to lack of using this gem in my projects I have no time to keep it alive.
|
6
|
-
Feel free to open an issue if you interested.
|
7
|
-
|
8
|
-
_This project is built and maintained 100% voluntarily._
|
9
|
-
|
10
1
|
# OmniAuth::OpenIDConnect
|
11
2
|
|
12
3
|
Originally was [omniauth-openid-connect](https://github.com/jjbohn/omniauth-openid-connect)
|
@@ -14,6 +5,7 @@ Originally was [omniauth-openid-connect](https://github.com/jjbohn/omniauth-open
|
|
14
5
|
I've forked this repository and launch as separate gem because maintaining of original was dropped.
|
15
6
|
|
16
7
|
[](https://github.com/omniauth/omniauth_openid_connect/actions/workflows/main.yml)
|
8
|
+
[](https://coveralls.io/github/omniauth/omniauth_openid_connect)
|
17
9
|
|
18
10
|
## Installation
|
19
11
|
|
@@ -31,7 +23,7 @@ Or install it yourself as:
|
|
31
23
|
|
32
24
|
## Supported Ruby Versions
|
33
25
|
|
34
|
-
OmniAuth::OpenIDConnect is tested under 2.
|
26
|
+
OmniAuth::OpenIDConnect is tested under 2.5, 2.6, 2.7, 3.0, 3.1
|
35
27
|
|
36
28
|
## Usage
|
37
29
|
|
@@ -55,24 +47,28 @@ config.omniauth :openid_connect, {
|
|
55
47
|
|
56
48
|
### Options Overview
|
57
49
|
|
58
|
-
| Field | Description | Required | Default
|
59
|
-
|
60
|
-
| name | Arbitrary string to identify connection and identify it from other openid_connect providers
|
61
|
-
| issuer | Root url for the authorization server | yes |
|
62
|
-
| 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
|
63
|
-
| client_auth_method | Which authentication method to use to authenticate your app with the authorization server | no | Sym: basic
|
64
|
-
| scope | Which OpenID scopes to include (:openid is always required) | no | Array<sym> [:openid]
|
65
|
-
| response_type | Which OAuth2 response type to use with the authorization request | no | String: code
|
66
|
-
| 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
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
50
|
+
| Field | Description | Required | Default | Example/Options |
|
51
|
+
|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------------------------------|-----------------------------------------------------|
|
52
|
+
| name | Arbitrary string to identify connection and identify it from other openid_connect providers | no | String: openid_connect | :my_idp |
|
53
|
+
| issuer | Root url for the authorization server | yes | | https://myprovider.com |
|
54
|
+
| 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 |
|
55
|
+
| client_auth_method | Which authentication method to use to authenticate your app with the authorization server | no | Sym: basic | "basic", "jwks" |
|
56
|
+
| scope | Which OpenID scopes to include (:openid is always required) | no | Array<sym> [:openid] | [:openid, :profile, :email] |
|
57
|
+
| response_type | Which OAuth2 response type to use with the authorization request | no | String: code | one of: 'code', 'id_token' |
|
58
|
+
| 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) } |
|
59
|
+
| require_state | Should state param be verified - this is recommended, not required by the OIDC specification | no | true | false |
|
60
|
+
| 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 |
|
61
|
+
| display | An optional parameter to the authorization request to determine how the authorization and consent page | no | nil | one of: :page, :popup, :touch, :wap |
|
62
|
+
| 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 |
|
63
|
+
| send_scope_to_token_endpoint | Should the scope parameter be sent to the authorization token endpoint? | no | true | one of: true, false |
|
64
|
+
| 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 |
|
65
|
+
| uid_field | The field of the user info response to be used as a unique id | no | 'sub' | "sub", "preferred_username" |
|
66
|
+
| extra_authorize_params | A hash of extra fixed parameters that will be merged to the authorization request | no | Hash | {"tenant" => "common"} |
|
67
|
+
| allow_authorize_params | A list of allowed dynamic parameters that will be merged to the authorization request | no | Array | [:screen_name] |
|
68
|
+
| pkce | Enable [PKCE flow](https://oauth.net/2/pkce/) | no | false | one of: true, false |
|
69
|
+
| pkce_verifier | Specify a custom PKCE verifier code. | no | A random 128-char string | Proc.new { SecureRandom.hex(64) } |
|
70
|
+
| 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 |
|
71
|
+
| client_options | A hash of client options detailed in its own section | yes | | |
|
76
72
|
|
77
73
|
### Client Config Options
|
78
74
|
|
@@ -102,7 +98,7 @@ These are the configuration options for the client_options hash of the configura
|
|
102
98
|
|
103
99
|
* `response_type` tells the authorization server which grant type the application wants to use,
|
104
100
|
currently, only `:code` (Authorization Code grant) and `:id_token` (Implicit grant) are valid.
|
105
|
-
* If you want to pass `state`
|
101
|
+
* If you want to pass `state` parameter by yourself. You can set Proc Object.
|
106
102
|
e.g. `state: Proc.new { SecureRandom.hex(32) }`
|
107
103
|
* `nonce` is optional. If don't want to pass "nonce" parameter to provider, You should specify
|
108
104
|
`false` to `send_nonce` option. (default true)
|
@@ -124,6 +120,11 @@ These are the configuration options for the client_options hash of the configura
|
|
124
120
|
property can be used to add the attribute to the token request. Initial value is `true`, which means that the
|
125
121
|
scope attribute is included by default.
|
126
122
|
|
123
|
+
## Additional notes
|
124
|
+
* In some cases, you may want to go straight to the callback phase - e.g. when requested by a stateless client, like a mobile app.
|
125
|
+
In such example, the session is empty, so you have to forward certain parameters received from the client.
|
126
|
+
Currently supported ones are `code_verifier` and `nonce` - simply provide them as the `/callback` request parameters.
|
127
|
+
|
127
128
|
For the full low down on OpenID Connect, please check out
|
128
129
|
[the spec](http://openid.net/specs/openid-connect-core-1_0.html).
|
129
130
|
|
data/Rakefile
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'addressable/uri'
|
4
3
|
require 'timeout'
|
5
4
|
require 'net/http'
|
6
5
|
require 'open-uri'
|
@@ -10,7 +9,7 @@ require 'forwardable'
|
|
10
9
|
|
11
10
|
module OmniAuth
|
12
11
|
module Strategies
|
13
|
-
class OpenIDConnect
|
12
|
+
class OpenIDConnect # rubocop:disable Metrics/ClassLength
|
14
13
|
include OmniAuth::Strategy
|
15
14
|
extend Forwardable
|
16
15
|
|
@@ -41,6 +40,7 @@ module OmniAuth
|
|
41
40
|
option :client_x509_signing_key
|
42
41
|
option :scope, [:openid]
|
43
42
|
option :response_type, 'code' # ['code', 'id_token']
|
43
|
+
option :require_state, true
|
44
44
|
option :state
|
45
45
|
option :response_mode # [:query, :fragment, :form_post, :web_message]
|
46
46
|
option :display, nil # [:page, :popup, :touch, :wap]
|
@@ -57,6 +57,14 @@ module OmniAuth
|
|
57
57
|
option :extra_authorize_params, {}
|
58
58
|
option :allow_authorize_params, []
|
59
59
|
option :uid_field, 'sub'
|
60
|
+
option :pkce, false
|
61
|
+
option :pkce_verifier, nil
|
62
|
+
option :pkce_options, {
|
63
|
+
code_challenge: proc { |verifier|
|
64
|
+
Base64.urlsafe_encode64(Digest::SHA2.digest(verifier), padding: false)
|
65
|
+
},
|
66
|
+
code_challenge_method: 'S256',
|
67
|
+
}
|
60
68
|
|
61
69
|
def uid
|
62
70
|
user_info.raw_attributes[options.uid_field.to_sym] || user_info.sub
|
@@ -66,6 +74,7 @@ module OmniAuth
|
|
66
74
|
{
|
67
75
|
name: user_info.name,
|
68
76
|
email: user_info.email,
|
77
|
+
email_verified: user_info.email_verified,
|
69
78
|
nickname: user_info.preferred_username,
|
70
79
|
first_name: user_info.given_name,
|
71
80
|
last_name: user_info.family_name,
|
@@ -107,7 +116,7 @@ module OmniAuth
|
|
107
116
|
def callback_phase
|
108
117
|
error = params['error_reason'] || params['error']
|
109
118
|
error_description = params['error_description'] || params['error_reason']
|
110
|
-
invalid_state = params['state'].to_s.empty? || params['state'] != stored_state
|
119
|
+
invalid_state = (options.require_state && params['state'].to_s.empty?) || params['state'] != stored_state
|
111
120
|
|
112
121
|
raise CallbackError, error: params['error'], reason: error_description, uri: params['error_uri'] if error
|
113
122
|
raise CallbackError, error: :csrf_detected, reason: "Invalid 'state' parameter" if invalid_state
|
@@ -178,17 +187,40 @@ module OmniAuth
|
|
178
187
|
opts[key] = request.params[key.to_s] unless opts.key?(key)
|
179
188
|
end
|
180
189
|
|
190
|
+
if options.pkce
|
191
|
+
verifier = options.pkce_verifier ? options.pkce_verifier.call : SecureRandom.hex(64)
|
192
|
+
|
193
|
+
opts.merge!(pkce_authorize_params(verifier))
|
194
|
+
session['omniauth.pkce.verifier'] = verifier
|
195
|
+
end
|
196
|
+
|
181
197
|
client.authorization_uri(opts.reject { |_k, v| v.nil? })
|
182
198
|
end
|
183
199
|
|
184
200
|
def public_key
|
185
|
-
|
201
|
+
@public_key ||= if options.discovery
|
202
|
+
config.jwks
|
203
|
+
elsif key_or_secret
|
204
|
+
key_or_secret
|
205
|
+
elsif client_options.jwks_uri
|
206
|
+
fetch_key
|
207
|
+
end
|
208
|
+
end
|
186
209
|
|
187
|
-
|
210
|
+
def pkce_authorize_params(verifier)
|
211
|
+
# NOTE: see https://tools.ietf.org/html/rfc7636#appendix-A
|
212
|
+
{
|
213
|
+
code_challenge: options.pkce_options[:code_challenge].call(verifier),
|
214
|
+
code_challenge_method: options.pkce_options[:code_challenge_method],
|
215
|
+
}
|
188
216
|
end
|
189
217
|
|
190
218
|
private
|
191
219
|
|
220
|
+
def fetch_key
|
221
|
+
@fetch_key ||= parse_jwk_key(::OpenIDConnect.http_client.get_content(client_options.jwks_uri))
|
222
|
+
end
|
223
|
+
|
192
224
|
def issuer
|
193
225
|
resource = "#{ client_options.scheme }://#{ client_options.host }"
|
194
226
|
resource = "#{ resource }:#{ client_options.port }" if client_options.port
|
@@ -220,11 +252,14 @@ module OmniAuth
|
|
220
252
|
def access_token
|
221
253
|
return @access_token if @access_token
|
222
254
|
|
223
|
-
|
255
|
+
token_request_params = {
|
224
256
|
scope: (options.scope if options.send_scope_to_token_endpoint),
|
225
|
-
client_auth_method: options.client_auth_method
|
226
|
-
|
257
|
+
client_auth_method: options.client_auth_method,
|
258
|
+
}
|
259
|
+
|
260
|
+
token_request_params[:code_verifier] = params['code_verifier'] || session.delete('omniauth.pkce.verifier') if options.pkce
|
227
261
|
|
262
|
+
@access_token = client.access_token!(token_request_params)
|
228
263
|
verify_id_token!(@access_token.id_token) if configured_response_type == 'code'
|
229
264
|
|
230
265
|
@access_token
|
@@ -274,16 +309,17 @@ module OmniAuth
|
|
274
309
|
end
|
275
310
|
|
276
311
|
def key_or_secret
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
312
|
+
@key_or_secret ||=
|
313
|
+
case options.client_signing_alg&.to_sym
|
314
|
+
when :HS256, :HS384, :HS512
|
315
|
+
client_options.secret
|
316
|
+
when :RS256, :RS384, :RS512
|
317
|
+
if options.client_jwk_signing_key
|
318
|
+
parse_jwk_key(options.client_jwk_signing_key)
|
319
|
+
elsif options.client_x509_signing_key
|
320
|
+
parse_x509_key(options.client_x509_signing_key)
|
321
|
+
end
|
285
322
|
end
|
286
|
-
end
|
287
323
|
end
|
288
324
|
|
289
325
|
def parse_x509_key(key)
|
@@ -353,7 +389,7 @@ module OmniAuth
|
|
353
389
|
|
354
390
|
decode_id_token(id_token).verify!(issuer: options.issuer,
|
355
391
|
client_id: client_options.identifier,
|
356
|
-
nonce: stored_nonce)
|
392
|
+
nonce: params['nonce'].presence || stored_nonce)
|
357
393
|
end
|
358
394
|
|
359
395
|
class CallbackError < StandardError
|
@@ -27,10 +27,8 @@ Gem::Specification.new do |spec|
|
|
27
27
|
'rubygems_mfa_required' => 'true',
|
28
28
|
}
|
29
29
|
|
30
|
-
spec.add_dependency 'addressable', '~> 2.5'
|
31
30
|
spec.add_dependency 'omniauth', '>= 1.9', '< 3'
|
32
31
|
spec.add_dependency 'openid_connect', '~> 1.1'
|
33
|
-
spec.add_development_dependency 'coveralls', '~> 0.8'
|
34
32
|
spec.add_development_dependency 'faker', '~> 2.0'
|
35
33
|
spec.add_development_dependency 'guard', '~> 2.14'
|
36
34
|
spec.add_development_dependency 'guard-bundler', '~> 2.2'
|
@@ -39,5 +37,6 @@ Gem::Specification.new do |spec|
|
|
39
37
|
spec.add_development_dependency 'mocha', '~> 1.7'
|
40
38
|
spec.add_development_dependency 'rake', '~> 12.0'
|
41
39
|
spec.add_development_dependency 'rubocop', '~> 1.12'
|
42
|
-
spec.add_development_dependency 'simplecov', '~> 0.
|
40
|
+
spec.add_development_dependency 'simplecov', '~> 0.21'
|
41
|
+
spec.add_development_dependency 'simplecov-lcov', '~> 0.8'
|
43
42
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../../../test_helper'
|
2
4
|
|
3
5
|
module OmniAuth
|
4
6
|
module Strategies
|
5
|
-
class OpenIDConnectTest < StrategyTestCase
|
7
|
+
class OpenIDConnectTest < StrategyTestCase # rubocop:disable Metrics/ClassLength
|
6
8
|
def test_client_options_defaults
|
7
9
|
assert_equal 'https', strategy.options.client_options.scheme
|
8
10
|
assert_equal 443, strategy.options.client_options.port
|
@@ -11,7 +13,7 @@ module OmniAuth
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def test_request_phase
|
14
|
-
expected_redirect =
|
16
|
+
expected_redirect = %r{^https://example\.com/authorize\?client_id=1234&nonce=\w{32}&response_type=code&scope=openid&state=\w{32}$}
|
15
17
|
strategy.options.issuer = 'example.com'
|
16
18
|
strategy.options.client_options.host = 'example.com'
|
17
19
|
strategy.expects(:redirect).with(regexp_matches(expected_redirect))
|
@@ -19,7 +21,7 @@ module OmniAuth
|
|
19
21
|
end
|
20
22
|
|
21
23
|
def test_logout_phase_with_discovery
|
22
|
-
expected_redirect = %r{^https
|
24
|
+
expected_redirect = %r{^https://example\.com/logout$}
|
23
25
|
strategy.options.client_options.host = 'example.com'
|
24
26
|
strategy.options.discovery = true
|
25
27
|
|
@@ -78,7 +80,7 @@ module OmniAuth
|
|
78
80
|
end
|
79
81
|
|
80
82
|
def test_request_phase_with_params
|
81
|
-
expected_redirect =
|
83
|
+
expected_redirect = %r{^https://example\.com/authorize\?claims_locales=es&client_id=1234&login_hint=john.doe%40example.com&nonce=\w{32}&response_type=code&scope=openid&state=\w{32}&ui_locales=en$}
|
82
84
|
strategy.options.issuer = 'example.com'
|
83
85
|
strategy.options.client_options.host = 'example.com'
|
84
86
|
request.stubs(:params).returns('login_hint' => 'john.doe@example.com', 'ui_locales' => 'en', 'claims_locales' => 'es')
|
@@ -88,7 +90,7 @@ module OmniAuth
|
|
88
90
|
end
|
89
91
|
|
90
92
|
def test_request_phase_with_discovery
|
91
|
-
expected_redirect =
|
93
|
+
expected_redirect = %r{^https://example\.com/authorization\?client_id=1234&nonce=\w{32}&response_type=code&scope=openid&state=\w{32}$}
|
92
94
|
strategy.options.client_options.host = 'example.com'
|
93
95
|
strategy.options.discovery = true
|
94
96
|
|
@@ -115,7 +117,7 @@ module OmniAuth
|
|
115
117
|
end
|
116
118
|
|
117
119
|
def test_request_phase_with_response_mode
|
118
|
-
expected_redirect =
|
120
|
+
expected_redirect = %r{^https://example\.com/authorize\?client_id=1234&nonce=\w{32}&response_mode=form_post&response_type=id_token&scope=openid&state=\w{32}$}
|
119
121
|
strategy.options.issuer = 'example.com'
|
120
122
|
strategy.options.response_mode = 'form_post'
|
121
123
|
strategy.options.response_type = 'id_token'
|
@@ -126,7 +128,7 @@ module OmniAuth
|
|
126
128
|
end
|
127
129
|
|
128
130
|
def test_request_phase_with_response_mode_symbol
|
129
|
-
expected_redirect =
|
131
|
+
expected_redirect = %r{^https://example\.com/authorize\?client_id=1234&nonce=\w{32}&response_mode=form_post&response_type=id_token&scope=openid&state=\w{32}$}
|
130
132
|
strategy.options.issuer = 'example.com'
|
131
133
|
strategy.options.response_mode = 'form_post'
|
132
134
|
strategy.options.response_type = :id_token
|
@@ -139,25 +141,26 @@ module OmniAuth
|
|
139
141
|
def test_option_acr_values
|
140
142
|
strategy.options.client_options[:host] = 'foobar.com'
|
141
143
|
|
142
|
-
|
144
|
+
refute_match(/acr_values=/, strategy.authorize_uri, 'URI must not contain acr_values')
|
143
145
|
|
144
146
|
strategy.options.acr_values = 'urn:some:acr:values:value'
|
145
|
-
|
147
|
+
assert_match(/acr_values=/, strategy.authorize_uri, 'URI must contain acr_values')
|
146
148
|
end
|
147
149
|
|
148
150
|
def test_option_custom_attributes
|
149
151
|
strategy.options.client_options[:host] = 'foobar.com'
|
150
|
-
strategy.options.extra_authorize_params = {resource: 'xyz'}
|
152
|
+
strategy.options.extra_authorize_params = { resource: 'xyz' }
|
151
153
|
|
152
154
|
assert(strategy.authorize_uri =~ /resource=xyz/, 'URI must contain custom params')
|
153
155
|
end
|
154
156
|
|
155
157
|
def test_request_phase_with_allowed_params
|
156
158
|
strategy.options.issuer = 'example.com'
|
157
|
-
strategy.options.allow_authorize_params = [
|
158
|
-
strategy.options.extra_authorize_params = {resource: 'xyz'}
|
159
|
+
strategy.options.allow_authorize_params = %i[name logo resource]
|
160
|
+
strategy.options.extra_authorize_params = { resource: 'xyz' }
|
159
161
|
strategy.options.client_options.host = 'example.com'
|
160
|
-
request.stubs(:params).returns('name' => 'example', 'logo' => 'example_logo', 'resource' => 'abc',
|
162
|
+
request.stubs(:params).returns('name' => 'example', 'logo' => 'example_logo', 'resource' => 'abc',
|
163
|
+
'not_allowed' => 'filter_me')
|
161
164
|
|
162
165
|
assert(strategy.authorize_uri =~ /resource=xyz/, 'URI must contain fixed param resource')
|
163
166
|
assert(strategy.authorize_uri =~ /name=example/, 'URI must contain dynamic param name')
|
@@ -175,16 +178,15 @@ module OmniAuth
|
|
175
178
|
assert_equal user_info.sub, strategy.uid
|
176
179
|
end
|
177
180
|
|
178
|
-
def test_callback_phase(
|
181
|
+
def test_callback_phase(_session = {}, _params = {}) # rubocop:disable Metrics/AbcSize
|
179
182
|
code = SecureRandom.hex(16)
|
180
183
|
state = SecureRandom.hex(16)
|
181
|
-
nonce = SecureRandom.hex(16)
|
182
184
|
request.stubs(:params).returns('code' => code, 'state' => state)
|
183
185
|
request.stubs(:path).returns('')
|
184
186
|
|
185
187
|
strategy.options.issuer = 'example.com'
|
186
188
|
strategy.options.client_signing_alg = :RS256
|
187
|
-
strategy.options.client_jwk_signing_key =
|
189
|
+
strategy.options.client_jwk_signing_key = jwks.to_s
|
188
190
|
strategy.options.response_type = 'code'
|
189
191
|
|
190
192
|
strategy.unstub(:user_info)
|
@@ -193,7 +195,7 @@ module OmniAuth
|
|
193
195
|
access_token.stubs(:refresh_token)
|
194
196
|
access_token.stubs(:expires_in)
|
195
197
|
access_token.stubs(:scope)
|
196
|
-
access_token.stubs(:id_token).returns(
|
198
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
197
199
|
client.expects(:access_token!).at_least_once.returns(access_token)
|
198
200
|
access_token.expects(:userinfo!).returns(user_info)
|
199
201
|
|
@@ -208,15 +210,13 @@ module OmniAuth
|
|
208
210
|
end
|
209
211
|
|
210
212
|
def test_callback_phase_with_id_token
|
211
|
-
code = SecureRandom.hex(16)
|
212
213
|
state = SecureRandom.hex(16)
|
213
|
-
|
214
|
-
request.stubs(:params).returns('id_token' => code, 'state' => state)
|
214
|
+
request.stubs(:params).returns('id_token' => jwt.to_s, 'state' => state)
|
215
215
|
request.stubs(:path).returns('')
|
216
216
|
|
217
217
|
strategy.options.issuer = 'example.com'
|
218
218
|
strategy.options.client_signing_alg = :RS256
|
219
|
-
strategy.options.client_jwk_signing_key =
|
219
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
220
220
|
strategy.options.response_type = 'id_token'
|
221
221
|
|
222
222
|
strategy.unstub(:user_info)
|
@@ -225,7 +225,7 @@ module OmniAuth
|
|
225
225
|
access_token.stubs(:refresh_token)
|
226
226
|
access_token.stubs(:expires_in)
|
227
227
|
access_token.stubs(:scope)
|
228
|
-
access_token.stubs(:id_token).returns(
|
228
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
229
229
|
|
230
230
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
231
231
|
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
|
@@ -237,13 +237,85 @@ module OmniAuth
|
|
237
237
|
strategy.callback_phase
|
238
238
|
end
|
239
239
|
|
240
|
-
def
|
240
|
+
def test_callback_phase_with_id_token_and_param_provided_nonce # rubocop:disable Metrics/AbcSize
|
241
241
|
code = SecureRandom.hex(16)
|
242
242
|
state = SecureRandom.hex(16)
|
243
243
|
nonce = SecureRandom.hex(16)
|
244
|
-
|
244
|
+
request.stubs(:params).returns('code' => code, 'state' => state, 'nonce' => nonce)
|
245
|
+
request.stubs(:path).returns('')
|
245
246
|
|
246
|
-
|
247
|
+
strategy.options.issuer = 'example.com'
|
248
|
+
strategy.options.client_signing_alg = :RS256
|
249
|
+
strategy.options.client_jwk_signing_key = jwks.to_s
|
250
|
+
strategy.options.response_type = 'code'
|
251
|
+
|
252
|
+
strategy.unstub(:user_info)
|
253
|
+
access_token = stub('OpenIDConnect::AccessToken')
|
254
|
+
access_token.stubs(:access_token)
|
255
|
+
access_token.stubs(:refresh_token)
|
256
|
+
access_token.stubs(:expires_in)
|
257
|
+
access_token.stubs(:scope)
|
258
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
259
|
+
client.expects(:access_token!).at_least_once.returns(access_token)
|
260
|
+
access_token.expects(:userinfo!).returns(user_info)
|
261
|
+
|
262
|
+
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
263
|
+
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
|
264
|
+
id_token.stubs(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, nonce: nonce).returns(true)
|
265
|
+
id_token.expects(:verify!)
|
266
|
+
|
267
|
+
strategy.expects(:decode_id_token).twice.with(access_token.id_token).returns(id_token)
|
268
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state })
|
269
|
+
strategy.callback_phase
|
270
|
+
end
|
271
|
+
|
272
|
+
def test_callback_phase_with_discovery # rubocop:disable Metrics/AbcSize
|
273
|
+
state = SecureRandom.hex(16)
|
274
|
+
|
275
|
+
request.stubs(:params).returns('code' => jwt.to_s, 'state' => state)
|
276
|
+
request.stubs(:path).returns('')
|
277
|
+
|
278
|
+
strategy.options.client_options.host = 'example.com'
|
279
|
+
strategy.options.discovery = true
|
280
|
+
|
281
|
+
issuer = stub('OpenIDConnect::Discovery::Issuer')
|
282
|
+
issuer.stubs(:issuer).returns('https://example.com/')
|
283
|
+
::OpenIDConnect::Discovery::Provider.stubs(:discover!).returns(issuer)
|
284
|
+
|
285
|
+
config = stub('OpenIDConnect::Discovery::Provder::Config')
|
286
|
+
config.stubs(:authorization_endpoint).returns('https://example.com/authorization')
|
287
|
+
config.stubs(:token_endpoint).returns('https://example.com/token')
|
288
|
+
config.stubs(:userinfo_endpoint).returns('https://example.com/userinfo')
|
289
|
+
config.stubs(:jwks_uri).returns('https://example.com/jwks')
|
290
|
+
config.stubs(:jwks).returns(JSON::JWK::Set.new(jwks['keys']))
|
291
|
+
|
292
|
+
::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('https://example.com/').returns(config)
|
293
|
+
|
294
|
+
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
295
|
+
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
|
296
|
+
id_token.stubs(:verify!).with(issuer: 'https://example.com/', client_id: @identifier, nonce: nonce).returns(true)
|
297
|
+
::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
|
298
|
+
|
299
|
+
strategy.unstub(:user_info)
|
300
|
+
access_token = stub('OpenIDConnect::AccessToken')
|
301
|
+
access_token.stubs(:access_token)
|
302
|
+
access_token.stubs(:refresh_token)
|
303
|
+
access_token.stubs(:expires_in)
|
304
|
+
access_token.stubs(:scope)
|
305
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
306
|
+
client.expects(:access_token!).at_least_once.returns(access_token)
|
307
|
+
access_token.expects(:userinfo!).returns(user_info)
|
308
|
+
|
309
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
310
|
+
strategy.callback_phase
|
311
|
+
end
|
312
|
+
|
313
|
+
def test_callback_phase_with_no_state_without_state_verification # rubocop:disable Metrics/AbcSize
|
314
|
+
code = SecureRandom.hex(16)
|
315
|
+
|
316
|
+
strategy.options.require_state = false
|
317
|
+
|
318
|
+
request.stubs(:params).returns('code' => code)
|
247
319
|
request.stubs(:path).returns('')
|
248
320
|
|
249
321
|
strategy.options.client_options.host = 'example.com'
|
@@ -258,7 +330,7 @@ module OmniAuth
|
|
258
330
|
config.stubs(:token_endpoint).returns('https://example.com/token')
|
259
331
|
config.stubs(:userinfo_endpoint).returns('https://example.com/userinfo')
|
260
332
|
config.stubs(:jwks_uri).returns('https://example.com/jwks')
|
261
|
-
config.stubs(:jwks).returns(jwks)
|
333
|
+
config.stubs(:jwks).returns(JSON::JWK::Set.new(jwks['keys']))
|
262
334
|
|
263
335
|
::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('https://example.com/').returns(config)
|
264
336
|
|
@@ -273,21 +345,67 @@ module OmniAuth
|
|
273
345
|
access_token.stubs(:refresh_token)
|
274
346
|
access_token.stubs(:expires_in)
|
275
347
|
access_token.stubs(:scope)
|
276
|
-
access_token.stubs(:id_token).returns(
|
348
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
277
349
|
client.expects(:access_token!).at_least_once.returns(access_token)
|
278
350
|
access_token.expects(:userinfo!).returns(user_info)
|
279
351
|
|
352
|
+
strategy.call!('rack.session' => { 'omniauth.nonce' => nonce })
|
353
|
+
strategy.callback_phase
|
354
|
+
end
|
355
|
+
|
356
|
+
def test_callback_phase_with_invalid_state_without_state_verification
|
357
|
+
code = SecureRandom.hex(16)
|
358
|
+
state = SecureRandom.hex(16)
|
359
|
+
|
360
|
+
strategy.options.require_state = false
|
361
|
+
|
362
|
+
request.stubs(:params).returns('code' => code, 'state' => 'foobar')
|
363
|
+
request.stubs(:path).returns('')
|
364
|
+
|
365
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
366
|
+
strategy.expects(:fail!)
|
367
|
+
strategy.callback_phase
|
368
|
+
end
|
369
|
+
|
370
|
+
def test_callback_phase_with_jwks_uri
|
371
|
+
id_token = jwt.to_s
|
372
|
+
state = SecureRandom.hex(16)
|
373
|
+
request.stubs(:params).returns('id_token' => id_token, 'state' => state)
|
374
|
+
request.stubs(:path_info).returns('')
|
375
|
+
|
376
|
+
strategy.options.issuer = 'example.com'
|
377
|
+
strategy.options.client_options.jwks_uri = 'https://jwks.example.com'
|
378
|
+
strategy.options.response_type = 'id_token'
|
379
|
+
|
380
|
+
HTTPClient
|
381
|
+
.any_instance.stubs(:get_content)
|
382
|
+
.with(strategy.options.client_options.jwks_uri)
|
383
|
+
.returns(jwks.to_json)
|
384
|
+
|
385
|
+
strategy.unstub(:user_info)
|
386
|
+
access_token = stub('OpenIDConnect::AccessToken')
|
387
|
+
access_token.stubs(:access_token)
|
388
|
+
access_token.stubs(:refresh_token)
|
389
|
+
access_token.stubs(:expires_in)
|
390
|
+
access_token.stubs(:scope)
|
391
|
+
access_token.stubs(:id_token).returns(id_token)
|
392
|
+
|
393
|
+
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
394
|
+
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
|
395
|
+
id_token.stubs(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, nonce: nonce).returns(true)
|
396
|
+
::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
|
397
|
+
id_token.expects(:verify!)
|
398
|
+
|
280
399
|
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
281
400
|
strategy.callback_phase
|
282
401
|
end
|
283
402
|
|
284
403
|
def test_callback_phase_with_error
|
285
404
|
state = SecureRandom.hex(16)
|
286
|
-
nonce = SecureRandom.hex(16)
|
287
405
|
request.stubs(:params).returns('error' => 'invalid_request')
|
288
406
|
request.stubs(:path).returns('')
|
289
407
|
|
290
|
-
strategy.call!({'rack.session' => {'omniauth.state' => state, 'omniauth.nonce' => nonce}})
|
408
|
+
strategy.call!({ 'rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce } })
|
291
409
|
strategy.expects(:fail!)
|
292
410
|
strategy.callback_phase
|
293
411
|
end
|
@@ -295,7 +413,6 @@ module OmniAuth
|
|
295
413
|
def test_callback_phase_with_invalid_state
|
296
414
|
code = SecureRandom.hex(16)
|
297
415
|
state = SecureRandom.hex(16)
|
298
|
-
nonce = SecureRandom.hex(16)
|
299
416
|
request.stubs(:params).returns('code' => code, 'state' => 'foobar')
|
300
417
|
request.stubs(:path).returns('')
|
301
418
|
|
@@ -306,7 +423,6 @@ module OmniAuth
|
|
306
423
|
|
307
424
|
def test_callback_phase_without_code
|
308
425
|
state = SecureRandom.hex(16)
|
309
|
-
nonce = SecureRandom.hex(16)
|
310
426
|
request.stubs(:params).returns('state' => state)
|
311
427
|
request.stubs(:path).returns('')
|
312
428
|
|
@@ -318,7 +434,6 @@ module OmniAuth
|
|
318
434
|
|
319
435
|
def test_callback_phase_without_id_token
|
320
436
|
state = SecureRandom.hex(16)
|
321
|
-
nonce = SecureRandom.hex(16)
|
322
437
|
request.stubs(:params).returns('state' => state)
|
323
438
|
request.stubs(:path).returns('')
|
324
439
|
strategy.options.response_type = 'id_token'
|
@@ -331,7 +446,6 @@ module OmniAuth
|
|
331
446
|
|
332
447
|
def test_callback_phase_without_id_token_symbol
|
333
448
|
state = SecureRandom.hex(16)
|
334
|
-
nonce = SecureRandom.hex(16)
|
335
449
|
request.stubs(:params).returns('state' => state)
|
336
450
|
request.stubs(:path).returns('')
|
337
451
|
strategy.options.response_type = :id_token
|
@@ -345,7 +459,6 @@ module OmniAuth
|
|
345
459
|
def test_callback_phase_with_timeout
|
346
460
|
code = SecureRandom.hex(16)
|
347
461
|
state = SecureRandom.hex(16)
|
348
|
-
nonce = SecureRandom.hex(16)
|
349
462
|
request.stubs(:params).returns('code' => code, 'state' => state)
|
350
463
|
request.stubs(:path).returns('')
|
351
464
|
|
@@ -365,7 +478,6 @@ module OmniAuth
|
|
365
478
|
def test_callback_phase_with_etimeout
|
366
479
|
code = SecureRandom.hex(16)
|
367
480
|
state = SecureRandom.hex(16)
|
368
|
-
nonce = SecureRandom.hex(16)
|
369
481
|
request.stubs(:params).returns('code' => code, 'state' => state)
|
370
482
|
request.stubs(:path).returns('')
|
371
483
|
|
@@ -385,7 +497,6 @@ module OmniAuth
|
|
385
497
|
def test_callback_phase_with_socket_error
|
386
498
|
code = SecureRandom.hex(16)
|
387
499
|
state = SecureRandom.hex(16)
|
388
|
-
nonce = SecureRandom.hex(16)
|
389
500
|
request.stubs(:params).returns('code' => code, 'state' => state)
|
390
501
|
request.stubs(:path).returns('')
|
391
502
|
|
@@ -405,7 +516,6 @@ module OmniAuth
|
|
405
516
|
def test_callback_phase_with_rack_oauth2_client_error
|
406
517
|
code = SecureRandom.hex(16)
|
407
518
|
state = SecureRandom.hex(16)
|
408
|
-
nonce = SecureRandom.hex(16)
|
409
519
|
request.stubs(:params).returns('code' => code, 'state' => state)
|
410
520
|
request.stubs(:path).returns('')
|
411
521
|
|
@@ -426,6 +536,7 @@ module OmniAuth
|
|
426
536
|
info = strategy.info
|
427
537
|
assert_equal user_info.name, info[:name]
|
428
538
|
assert_equal user_info.email, info[:email]
|
539
|
+
assert_equal user_info.email_verified, info[:email_verified]
|
429
540
|
assert_equal user_info.preferred_username, info[:nickname]
|
430
541
|
assert_equal user_info.given_name, info[:first_name]
|
431
542
|
assert_equal user_info.family_name, info[:last_name]
|
@@ -442,7 +553,7 @@ module OmniAuth
|
|
442
553
|
def test_credentials
|
443
554
|
strategy.options.issuer = 'example.com'
|
444
555
|
strategy.options.client_signing_alg = :RS256
|
445
|
-
strategy.options.client_jwk_signing_key =
|
556
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
446
557
|
|
447
558
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
448
559
|
id_token.stubs(:verify!).returns(true)
|
@@ -453,7 +564,7 @@ module OmniAuth
|
|
453
564
|
access_token.stubs(:refresh_token).returns(SecureRandom.hex(16))
|
454
565
|
access_token.stubs(:expires_in).returns(Time.now)
|
455
566
|
access_token.stubs(:scope).returns('openidconnect')
|
456
|
-
access_token.stubs(:id_token).returns(
|
567
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
457
568
|
|
458
569
|
client.expects(:access_token!).returns(access_token)
|
459
570
|
access_token.expects(:refresh_token).returns(access_token.refresh_token)
|
@@ -465,7 +576,7 @@ module OmniAuth
|
|
465
576
|
token: access_token.access_token,
|
466
577
|
refresh_token: access_token.refresh_token,
|
467
578
|
expires_in: access_token.expires_in,
|
468
|
-
scope: access_token.scope
|
579
|
+
scope: access_token.scope,
|
469
580
|
},
|
470
581
|
strategy.credentials
|
471
582
|
)
|
@@ -473,11 +584,10 @@ module OmniAuth
|
|
473
584
|
|
474
585
|
def test_option_send_nonce
|
475
586
|
strategy.options.client_options[:host] = 'foobar.com'
|
476
|
-
|
477
|
-
assert(strategy.authorize_uri =~ /nonce=/, 'URI must contain nonce')
|
587
|
+
assert_match(/nonce/, strategy.authorize_uri, 'URI must contain nonce')
|
478
588
|
|
479
589
|
strategy.options.send_nonce = false
|
480
|
-
|
590
|
+
refute_match(/nonce/, strategy.authorize_uri, 'URI must not contain nonce')
|
481
591
|
end
|
482
592
|
|
483
593
|
def test_failure_endpoint_redirect
|
@@ -487,9 +597,9 @@ module OmniAuth
|
|
487
597
|
|
488
598
|
result = strategy.callback_phase
|
489
599
|
|
490
|
-
assert(result.is_a?
|
600
|
+
assert(result.is_a?(Array))
|
491
601
|
assert(result[0] == 302, 'Redirect')
|
492
|
-
assert(result[1][
|
602
|
+
assert(result[1]['Location'] =~ %r{/auth/failure})
|
493
603
|
end
|
494
604
|
|
495
605
|
def test_state
|
@@ -518,7 +628,7 @@ module OmniAuth
|
|
518
628
|
def test_dynamic_state
|
519
629
|
# Stub request parameters
|
520
630
|
request.stubs(:path).returns('')
|
521
|
-
strategy.call!('rack.session' => {
|
631
|
+
strategy.call!('rack.session' => {}, QUERY_STRING: { state: 'abc', client_id: '123' })
|
522
632
|
|
523
633
|
strategy.options.state = lambda { |env|
|
524
634
|
# Get params from request, e.g. CGI.parse(env['QUERY_STRING'])
|
@@ -534,18 +644,17 @@ module OmniAuth
|
|
534
644
|
|
535
645
|
def test_option_client_auth_method
|
536
646
|
state = SecureRandom.hex(16)
|
537
|
-
nonce = SecureRandom.hex(16)
|
538
647
|
|
539
648
|
opts = strategy.options.client_options
|
540
649
|
opts[:host] = 'foobar.com'
|
541
650
|
strategy.options.issuer = 'foobar.com'
|
542
651
|
strategy.options.client_auth_method = :not_basic
|
543
652
|
strategy.options.client_signing_alg = :RS256
|
544
|
-
strategy.options.client_jwk_signing_key =
|
653
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
545
654
|
|
546
655
|
json_response = {
|
547
656
|
access_token: 'test_access_token',
|
548
|
-
id_token:
|
657
|
+
id_token: jwt.to_s,
|
549
658
|
token_type: 'Bearer',
|
550
659
|
}.to_json
|
551
660
|
success = Struct.new(:status, :body).new(200, json_response)
|
@@ -563,21 +672,19 @@ module OmniAuth
|
|
563
672
|
{}
|
564
673
|
).returns(success)
|
565
674
|
|
566
|
-
assert(strategy.send
|
675
|
+
assert(strategy.send(:access_token))
|
567
676
|
end
|
568
677
|
|
569
678
|
def test_public_key_with_jwks
|
570
679
|
strategy.options.client_signing_alg = :RS256
|
571
|
-
strategy.options.client_jwk_signing_key =
|
680
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
572
681
|
|
573
682
|
assert_equal JSON::JWK::Set, strategy.public_key.class
|
574
683
|
end
|
575
684
|
|
576
685
|
def test_public_key_with_jwk
|
577
686
|
strategy.options.client_signing_alg = :RS256
|
578
|
-
|
579
|
-
jwks = JSON.parse(jwks_str)
|
580
|
-
jwk = jwks['keys'].first
|
687
|
+
jwk = jwks[:keys].first
|
581
688
|
strategy.options.client_jwk_signing_key = jwk.to_json
|
582
689
|
|
583
690
|
assert_equal JSON::JWK, strategy.public_key.class
|
@@ -597,22 +704,12 @@ module OmniAuth
|
|
597
704
|
|
598
705
|
def test_id_token_auth_hash
|
599
706
|
state = SecureRandom.hex(16)
|
600
|
-
nonce = SecureRandom.hex(16)
|
601
707
|
strategy.options.response_type = 'id_token'
|
602
708
|
strategy.options.issuer = 'example.com'
|
603
709
|
|
604
710
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
605
711
|
id_token.stubs(:verify!).returns(true)
|
606
|
-
id_token.stubs(:raw_attributes, :to_h).returns(
|
607
|
-
{
|
608
|
-
"iss": "http://server.example.com",
|
609
|
-
"sub": "248289761001",
|
610
|
-
"aud": "s6BhdRkqt3",
|
611
|
-
"nonce": "n-0S6_WzA2Mj",
|
612
|
-
"exp": 1311281970,
|
613
|
-
"iat": 1311280970,
|
614
|
-
}
|
615
|
-
)
|
712
|
+
id_token.stubs(:raw_attributes, :to_h).returns(payload)
|
616
713
|
|
617
714
|
request.stubs(:params).returns('state' => state, 'nounce' => nonce, 'id_token' => id_token)
|
618
715
|
request.stubs(:path).returns('')
|
@@ -630,6 +727,41 @@ module OmniAuth
|
|
630
727
|
assert auth_hash.key?('extra')
|
631
728
|
assert auth_hash['extra'].key?('raw_info')
|
632
729
|
end
|
730
|
+
|
731
|
+
def test_option_pkce
|
732
|
+
strategy.options.client_options[:host] = 'example.com'
|
733
|
+
|
734
|
+
# test pkce disabled
|
735
|
+
strategy.options.pkce = false
|
736
|
+
|
737
|
+
assert((strategy.authorize_uri !~ /code_challenge=/), 'URI must not contain code challenge param')
|
738
|
+
assert((strategy.authorize_uri !~ /code_challenge_method=/), 'URI must not contain code challenge method param')
|
739
|
+
|
740
|
+
# test pkce enabled with default opts
|
741
|
+
strategy.options.pkce = true
|
742
|
+
|
743
|
+
assert(strategy.authorize_uri =~ /code_challenge=/, 'URI must contain code challenge param')
|
744
|
+
assert(strategy.authorize_uri =~ /code_challenge_method=/, 'URI must contain code challenge method param')
|
745
|
+
|
746
|
+
# test pkce with custom verifier code
|
747
|
+
strategy.options.pkce_verifier = proc { 'dummy_verifier' }
|
748
|
+
code_challenge_value = Base64.urlsafe_encode64(
|
749
|
+
Digest::SHA2.digest(strategy.options.pkce_verifier.call),
|
750
|
+
padding: false
|
751
|
+
)
|
752
|
+
|
753
|
+
assert(strategy.authorize_uri =~ /#{Regexp.quote(code_challenge_value)}/, 'URI must contain code challenge value')
|
754
|
+
|
755
|
+
# test pkce with custom options and plain text code
|
756
|
+
strategy.options.pkce_options =
|
757
|
+
{
|
758
|
+
code_challenge: proc { |verifier| verifier },
|
759
|
+
code_challenge_method: 'plain',
|
760
|
+
}
|
761
|
+
|
762
|
+
assert(strategy.authorize_uri =~ /#{Regexp.quote(strategy.options.pkce_verifier.call)}/,
|
763
|
+
'URI must contain code challenge value')
|
764
|
+
end
|
633
765
|
end
|
634
766
|
end
|
635
767
|
end
|
data/test/strategy_test_case.rb
CHANGED
@@ -1,32 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class StrategyTestCase < MiniTest::Test
|
2
4
|
class DummyApp
|
3
5
|
def call(env); end
|
4
6
|
end
|
5
7
|
|
6
|
-
attr_accessor :identifier, :secret
|
8
|
+
attr_accessor :identifier, :secret, :issuer, :nonce
|
7
9
|
|
8
10
|
def setup
|
9
11
|
@identifier = '1234'
|
10
12
|
@secret = '1234asdgat3'
|
13
|
+
@issuer = 'https://server.example.com'
|
14
|
+
@nonce = SecureRandom.hex(16)
|
11
15
|
end
|
12
16
|
|
13
17
|
def client
|
14
18
|
strategy.client
|
15
19
|
end
|
16
20
|
|
21
|
+
def payload
|
22
|
+
{
|
23
|
+
"iss": issuer,
|
24
|
+
"aud": identifier,
|
25
|
+
"sub": '248289761001',
|
26
|
+
"nonce": nonce,
|
27
|
+
"exp": Time.now.to_i + 1000,
|
28
|
+
"iat": Time.now.to_i,
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def private_key
|
33
|
+
@private_key ||= OpenSSL::PKey::RSA.generate(512)
|
34
|
+
end
|
35
|
+
|
36
|
+
def jwt
|
37
|
+
@jwt ||= JSON::JWT.new(payload).sign(private_key, :RS256)
|
38
|
+
end
|
39
|
+
|
40
|
+
def jwks
|
41
|
+
@jwks ||= begin
|
42
|
+
key = JSON::JWK.new(private_key)
|
43
|
+
keyset = JSON::JWK::Set.new(key)
|
44
|
+
{ keys: keyset }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
17
48
|
def user_info
|
18
49
|
@user_info ||= OpenIDConnect::ResponseObject::UserInfo.new(
|
19
50
|
sub: SecureRandom.hex(16),
|
20
51
|
name: Faker::Name.name,
|
21
52
|
email: Faker::Internet.email,
|
53
|
+
email_verified: Faker::Boolean.boolean,
|
22
54
|
nickname: Faker::Name.first_name,
|
23
55
|
preferred_username: Faker::Internet.user_name,
|
24
56
|
given_name: Faker::Name.first_name,
|
25
57
|
family_name: Faker::Name.last_name,
|
26
58
|
gender: 'female',
|
27
|
-
picture: Faker::Internet.url
|
59
|
+
picture: "#{Faker::Internet.url}.png",
|
28
60
|
phone_number: Faker::PhoneNumber.phone_number,
|
29
|
-
website: Faker::Internet.url
|
61
|
+
website: Faker::Internet.url
|
30
62
|
)
|
31
63
|
end
|
32
64
|
|
data/test/test_helper.rb
CHANGED
@@ -1,16 +1,26 @@
|
|
1
|
-
|
2
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require 'simplecov'
|
5
|
-
require 'coveralls'
|
6
4
|
require 'minitest/autorun'
|
7
5
|
require 'mocha/minitest'
|
8
6
|
require 'faker'
|
9
7
|
require 'active_support'
|
8
|
+
|
9
|
+
SimpleCov.start do
|
10
|
+
if ENV['CI']
|
11
|
+
require 'simplecov-lcov'
|
12
|
+
|
13
|
+
SimpleCov::Formatter::LcovFormatter.config do |c|
|
14
|
+
c.report_with_single_file = true
|
15
|
+
c.single_report_path = 'coverage/lcov.info'
|
16
|
+
end
|
17
|
+
|
18
|
+
formatter SimpleCov::Formatter::LcovFormatter
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
lib = File.expand_path('../lib', __dir__)
|
23
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
10
24
|
require 'omniauth_openid_connect'
|
11
25
|
require_relative 'strategy_test_case'
|
12
|
-
|
13
|
-
SimpleCov.command_name 'test'
|
14
|
-
SimpleCov.start
|
15
|
-
Coveralls.wear!
|
16
26
|
OmniAuth.config.test_mode = true
|
metadata
CHANGED
@@ -1,30 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omniauth_openid_connect
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Bohn
|
8
8
|
- Ilya Shcherbinin
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-
|
12
|
+
date: 2022-12-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: addressable
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
requirements:
|
18
|
-
- - "~>"
|
19
|
-
- !ruby/object:Gem::Version
|
20
|
-
version: '2.5'
|
21
|
-
type: :runtime
|
22
|
-
prerelease: false
|
23
|
-
version_requirements: !ruby/object:Gem::Requirement
|
24
|
-
requirements:
|
25
|
-
- - "~>"
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
version: '2.5'
|
28
14
|
- !ruby/object:Gem::Dependency
|
29
15
|
name: omniauth
|
30
16
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,20 +45,6 @@ dependencies:
|
|
59
45
|
- - "~>"
|
60
46
|
- !ruby/object:Gem::Version
|
61
47
|
version: '1.1'
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: coveralls
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0.8'
|
69
|
-
type: :development
|
70
|
-
prerelease: false
|
71
|
-
version_requirements: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0.8'
|
76
48
|
- !ruby/object:Gem::Dependency
|
77
49
|
name: faker
|
78
50
|
requirement: !ruby/object:Gem::Requirement
|
@@ -191,14 +163,28 @@ dependencies:
|
|
191
163
|
requirements:
|
192
164
|
- - "~>"
|
193
165
|
- !ruby/object:Gem::Version
|
194
|
-
version: '0.
|
166
|
+
version: '0.21'
|
167
|
+
type: :development
|
168
|
+
prerelease: false
|
169
|
+
version_requirements: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0.21'
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: simplecov-lcov
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0.8'
|
195
181
|
type: :development
|
196
182
|
prerelease: false
|
197
183
|
version_requirements: !ruby/object:Gem::Requirement
|
198
184
|
requirements:
|
199
185
|
- - "~>"
|
200
186
|
- !ruby/object:Gem::Version
|
201
|
-
version: '0.
|
187
|
+
version: '0.8'
|
202
188
|
description: OpenID Connect Strategy for OmniAuth.
|
203
189
|
email:
|
204
190
|
- jjbohn@gmail.com
|
@@ -207,10 +193,8 @@ executables: []
|
|
207
193
|
extensions: []
|
208
194
|
extra_rdoc_files: []
|
209
195
|
files:
|
210
|
-
- ".github/config/rubocop_linter_action.yml"
|
211
196
|
- ".github/stale.yml"
|
212
197
|
- ".github/workflows/main.yml"
|
213
|
-
- ".github/workflows/rubocop.yml"
|
214
198
|
- ".gitignore"
|
215
199
|
- ".rubocop.yml"
|
216
200
|
- CHANGELOG.md
|
@@ -225,8 +209,6 @@ files:
|
|
225
209
|
- lib/omniauth/strategies/openid_connect.rb
|
226
210
|
- lib/omniauth_openid_connect.rb
|
227
211
|
- omniauth_openid_connect.gemspec
|
228
|
-
- test/fixtures/id_token.txt
|
229
|
-
- test/fixtures/jwks.json
|
230
212
|
- test/fixtures/test.crt
|
231
213
|
- test/lib/omniauth/strategies/openid_connect_test.rb
|
232
214
|
- test/strategy_test_case.rb
|
@@ -237,10 +219,10 @@ licenses:
|
|
237
219
|
metadata:
|
238
220
|
bug_tracker_uri: https://github.com/m0n9oose/omniauth_openid_connect/issues
|
239
221
|
changelog_uri: https://github.com/m0n9oose/omniauth_openid_connect/releases
|
240
|
-
documentation_uri: https://github.com/m0n9oose/omniauth_openid_connect/tree/v0.
|
241
|
-
source_code_uri: https://github.com/m0n9oose/omniauth_openid_connect/tree/v0.
|
222
|
+
documentation_uri: https://github.com/m0n9oose/omniauth_openid_connect/tree/v0.5.0#readme
|
223
|
+
source_code_uri: https://github.com/m0n9oose/omniauth_openid_connect/tree/v0.5.0
|
242
224
|
rubygems_mfa_required: 'true'
|
243
|
-
post_install_message:
|
225
|
+
post_install_message:
|
244
226
|
rdoc_options: []
|
245
227
|
require_paths:
|
246
228
|
- lib
|
@@ -255,13 +237,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
255
237
|
- !ruby/object:Gem::Version
|
256
238
|
version: '0'
|
257
239
|
requirements: []
|
258
|
-
rubygems_version: 3.3.
|
259
|
-
signing_key:
|
240
|
+
rubygems_version: 3.3.26
|
241
|
+
signing_key:
|
260
242
|
specification_version: 4
|
261
243
|
summary: OpenID Connect Strategy for OmniAuth
|
262
244
|
test_files:
|
263
|
-
- test/fixtures/id_token.txt
|
264
|
-
- test/fixtures/jwks.json
|
265
245
|
- test/fixtures/test.crt
|
266
246
|
- test/lib/omniauth/strategies/openid_connect_test.rb
|
267
247
|
- test/strategy_test_case.rb
|
@@ -1,59 +0,0 @@
|
|
1
|
-
# Description: The name of the check that will be created.
|
2
|
-
# Valid Options: A reasonably sized string.
|
3
|
-
# Default: 'Rubocop Action'
|
4
|
-
check_name: 'Rubocop Results'
|
5
|
-
|
6
|
-
# Description: Versions required to run your RuboCop checks.
|
7
|
-
# Valid options: RuboCop and any RuboCop extension, by default the latest gem version will be used. You can explicitly state that
|
8
|
-
# (not required) or use a version number, like '1.5.1'.
|
9
|
-
# Default:
|
10
|
-
# versions:
|
11
|
-
# - rubocop: 'latest'
|
12
|
-
versions:
|
13
|
-
- rubocop
|
14
|
-
- rubocop-minitest
|
15
|
-
- rubocop-performance: '1.5.1'
|
16
|
-
|
17
|
-
# Description: Rubocop configuration file path relative to the workspace.
|
18
|
-
# Valid options: A valid file path inside of the workspace.
|
19
|
-
# Default: nil
|
20
|
-
# Note: This does not need to be filled out for Rubocop to still find your config.
|
21
|
-
# Resource: https://rubocop.readthedocs.io/en/stable/configuration/
|
22
|
-
rubocop_config_path: '.rubocop.yml'
|
23
|
-
|
24
|
-
# Run all cops enabled by configuration except this list.
|
25
|
-
# Valid options: list of valid cop(s) and/or departments.
|
26
|
-
# Default: nil
|
27
|
-
# Resource: https://rubocop.readthedocs.io/en/stable/cops/
|
28
|
-
# rubocop_excluded_cops:
|
29
|
-
# - 'Style/FrozenStringLiteralComment'
|
30
|
-
|
31
|
-
# Minimum severity for exit with error code
|
32
|
-
# Valid options: 'refactor', 'convention', 'warning', 'error', or 'fatal'.
|
33
|
-
# Default: 'warning'
|
34
|
-
# Resource: https://rubocop.readthedocs.io/en/stable/configuration/#severity
|
35
|
-
# rubocop_fail_level: 'warning'
|
36
|
-
|
37
|
-
# Whether or not to use --force-exclusion when building the rubocop command. Use this if you are only linting modified
|
38
|
-
# files and typically excluded files have been changed. For example, if you exclude db/schema.rb in your rubocop.yml
|
39
|
-
# but a change gets made, then with the check_scope config set to 'modified' rubocop will lint db/schema.rb. If you set
|
40
|
-
# this to true, rubocop will ignore it.
|
41
|
-
# Valid options: true || false
|
42
|
-
# Default: false
|
43
|
-
|
44
|
-
# Instead of installing gems from rubygems, we can run `bundle install` on your project,
|
45
|
-
# you would need to do this if you are using something like 'rubocop-github' or if you don't
|
46
|
-
# want to list out dependencies with the `versions` key.
|
47
|
-
# Valid options: true || false
|
48
|
-
# Default: false
|
49
|
-
# bundle: false
|
50
|
-
|
51
|
-
# The scope of code that Rubocop should lint. Use this if you only want to lint changed files. If this is not set
|
52
|
-
# or not equal to 'modified', Rubocop is run against the entire codebase. Note that this will not work on the master branch.
|
53
|
-
# Valid options: 'modified'
|
54
|
-
# Default: nil
|
55
|
-
|
56
|
-
# The base branch against which changes will be compared, if check_scope config is set to 'modified'.
|
57
|
-
# This setting is not used if check_scope != 'modified'.
|
58
|
-
# Valid options: 'origin/another_branch'
|
59
|
-
# Default: 'origin/master'
|
@@ -1,22 +0,0 @@
|
|
1
|
-
name: Rubocop check
|
2
|
-
|
3
|
-
on:
|
4
|
-
pull_request:
|
5
|
-
branches:
|
6
|
-
- "*"
|
7
|
-
push:
|
8
|
-
branches:
|
9
|
-
- master
|
10
|
-
jobs:
|
11
|
-
build:
|
12
|
-
name: RuboCop Action
|
13
|
-
runs-on: ubuntu-latest
|
14
|
-
steps:
|
15
|
-
- name: Checkout Action
|
16
|
-
uses: actions/checkout@v1
|
17
|
-
- name: Rubocop Linter Action
|
18
|
-
uses: andrewmcodes/rubocop-linter-action@v3.2.0
|
19
|
-
with:
|
20
|
-
action_config_path: '.github/config/rubocop_linter_action.yml'
|
21
|
-
env:
|
22
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
data/test/fixtures/id_token.txt
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6qJp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJNqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7TpdQyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoSK5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg
|
data/test/fixtures/jwks.json
DELETED
@@ -1,8 +0,0 @@
|
|
1
|
-
{"keys": [{
|
2
|
-
"kty": "RSA",
|
3
|
-
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
|
4
|
-
"e": "AQAB",
|
5
|
-
"alg": "RS256",
|
6
|
-
"kid": "1e9gdk7"
|
7
|
-
}]
|
8
|
-
}
|