omniauth_openid_connect 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://github.com/omniauth/omniauth_openid_connect/actions/workflows/main.yml/badge.svg)](https://github.com/omniauth/omniauth_openid_connect/actions/workflows/main.yml)
|
8
|
+
[![Coverage Status](https://coveralls.io/repos/github/omniauth/omniauth_openid_connect/badge.svg)](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
|
-
}
|