gitlab-omniauth-openid-connect 0.5.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/CONTRIBUTING.md +40 -0
- data/README.md +2 -0
- data/lib/omniauth/openid_connect/version.rb +1 -1
- data/lib/omniauth/strategies/openid_connect.rb +58 -23
- data/test/lib/omniauth/strategies/openid_connect_test.rb +110 -39
- data/test/strategy_test_case.rb +24 -0
- metadata +7 -8
- data/test/fixtures/id_token.txt +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37197f76441b711d0f9ecbc461b533298e827e0a0be5679994e7c2e72d930695
|
4
|
+
data.tar.gz: d854e863540d1132cd182988f909338b192e0655da0872f9b357d0a165a1a4c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 184378eed49019d8fe115ea54071cc17d97a9482ad16ae0fb5ad33e4985ee49ef29ad20c52d66a6e7a016bb14a28ced575f0b73d820e87e0663f234f66304183
|
7
|
+
data.tar.gz: 468a122b46e8a1e9fc62d380131500471d66d68285753334cf60b699c7e3d6e87ecd44b3d4f9ded43bf3bfb968bfe3403133b31cd6d0af456e180e90bc0cb979
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
# v0.9.0 (01.03.2022)
|
2
|
+
|
3
|
+
- [Add support for ES[256|384|512|256K] algorithms](https://gitlab.com/gitlab-org/gitlab-omniauth-openid-connect/-/merge_requests/17)
|
4
|
+
|
5
|
+
# v0.8.0 (07.16.2021)
|
6
|
+
|
7
|
+
- [Add `jwt_secret_base64` option to support binary secrets](https://gitlab.com/gitlab-org/gitlab-omniauth-openid-connect/-/merge_requests/12)
|
8
|
+
|
9
|
+
# v0.7.0 (07.16.2021)
|
10
|
+
|
11
|
+
- [Add `jwt_secret` option to support Keycloak private key](https://gitlab.com/gitlab-org/gitlab-omniauth-openid-connect/-/merge_requests/10)
|
12
|
+
|
13
|
+
# v0.6.0 (07.08.2021)
|
14
|
+
|
15
|
+
- [Support verification of HS256-signed JWTs](https://gitlab.com/gitlab-org/gitlab-omniauth-openid-connect/-/merge_requests/8)
|
16
|
+
|
1
17
|
# v0.5.0 (05.07.2021)
|
2
18
|
|
3
19
|
- [Add email_verified field to info dict](https://gitlab.com/gitlab-org/gitlab-omniauth-openid-connect/-/merge_requests/7)
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
## Developer Certificate of Origin and License
|
2
|
+
|
3
|
+
By contributing to GitLab B.V., you accept and agree to the following terms and
|
4
|
+
conditions for your present and future contributions submitted to GitLab B.V.
|
5
|
+
Except for the license granted herein to GitLab B.V. and recipients of software
|
6
|
+
distributed by GitLab B.V., you reserve all right, title, and interest in and to
|
7
|
+
your Contributions.
|
8
|
+
|
9
|
+
All contributions are subject to the Developer Certificate of Origin and license set out at [docs.gitlab.com/ce/legal/developer_certificate_of_origin](https://docs.gitlab.com/ce/legal/developer_certificate_of_origin).
|
10
|
+
|
11
|
+
_This notice should stay as the first item in the CONTRIBUTING.md file._
|
12
|
+
|
13
|
+
## Code of conduct
|
14
|
+
|
15
|
+
As contributors and maintainers of this project, we pledge to respect all people
|
16
|
+
who contribute through reporting issues, posting feature requests, updating
|
17
|
+
documentation, submitting pull requests or patches, and other activities.
|
18
|
+
|
19
|
+
We are committed to making participation in this project a harassment-free
|
20
|
+
experience for everyone, regardless of level of experience, gender, gender
|
21
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
22
|
+
body size, race, ethnicity, age, or religion.
|
23
|
+
|
24
|
+
Examples of unacceptable behavior by participants include the use of sexual
|
25
|
+
language or imagery, derogatory comments or personal attacks, trolling, public
|
26
|
+
or private harassment, insults, or other unprofessional conduct.
|
27
|
+
|
28
|
+
Project maintainers have the right and responsibility to remove, edit, or reject
|
29
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
30
|
+
not aligned to this Code of Conduct. Project maintainers who do not follow the
|
31
|
+
Code of Conduct may be removed from the project team.
|
32
|
+
|
33
|
+
This code of conduct applies both within project spaces and in public spaces
|
34
|
+
when an individual is representing the project or its community.
|
35
|
+
|
36
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior can be
|
37
|
+
reported by emailing contact@gitlab.com.
|
38
|
+
|
39
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org), version 1.1.0,
|
40
|
+
available at [https://contributor-covenant.org/version/1/1/0/](https://contributor-covenant.org/version/1/1/0/).
|
data/README.md
CHANGED
@@ -66,6 +66,8 @@ config.omniauth :openid_connect, {
|
|
66
66
|
| 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 |
|
67
67
|
| uid_field | The field of the user info response to be used as a unique id | no | 'sub' | "sub", "preferred_username" |
|
68
68
|
| client_options | A hash of client options detailed in its own section | yes | | |
|
69
|
+
| jwt_secret | For HMAC with SHA2 (e.g. HS256) signing algorithms, specify the secret used to sign the JWT token. Defaults to the OAuth2 client secret if not specified. For secrets in binary, use `jwt_secret_base64`. | no | client_options.secret | "mysecret" |
|
70
|
+
| jwt_secret_base64 | For HMAC with SHA2 (e.g. HS256) signing algorithms, specify the base64-encoded secret used to sign the JWT token. Defaults to the OAuth2 client secret if not specified. `jwt_secret` takes precedence. | no | client_options.secret | "bXlzZWNyZXQ=\n"
|
69
71
|
|
70
72
|
### Client Config Options
|
71
73
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'addressable/uri'
|
4
|
+
require 'base64'
|
4
5
|
require 'timeout'
|
5
6
|
require 'net/http'
|
6
7
|
require 'open-uri'
|
@@ -36,7 +37,9 @@ module OmniAuth
|
|
36
37
|
|
37
38
|
option :issuer
|
38
39
|
option :discovery, false
|
39
|
-
option :client_signing_alg
|
40
|
+
option :client_signing_alg # Deprecated since we detect what is used to sign the JWT
|
41
|
+
option :jwt_secret
|
42
|
+
option :jwt_secret_base64
|
40
43
|
option :client_jwk_signing_key
|
41
44
|
option :client_x509_signing_key
|
42
45
|
option :scope, [:openid]
|
@@ -181,16 +184,28 @@ module OmniAuth
|
|
181
184
|
@public_key ||= begin
|
182
185
|
if options.discovery
|
183
186
|
config.jwks
|
184
|
-
elsif
|
185
|
-
|
187
|
+
elsif configured_public_key
|
188
|
+
configured_public_key
|
186
189
|
elsif client_options.jwks_uri
|
187
190
|
fetch_key
|
188
191
|
end
|
189
192
|
end
|
190
193
|
end
|
191
194
|
|
195
|
+
# Some OpenID providers use the OAuth2 client secret as the shared secret, but
|
196
|
+
# Keycloak uses a separate key that's stored inside the database.
|
197
|
+
def secret
|
198
|
+
options.jwt_secret || base64_decoded_jwt_secret || client_options.secret
|
199
|
+
end
|
200
|
+
|
192
201
|
private
|
193
202
|
|
203
|
+
def base64_decoded_jwt_secret
|
204
|
+
return unless options.jwt_secret_base64
|
205
|
+
|
206
|
+
Base64.decode64(options.jwt_secret_base64)
|
207
|
+
end
|
208
|
+
|
194
209
|
def fetch_key
|
195
210
|
@fetch_key ||= parse_jwk_key(::OpenIDConnect.http_client.get_content(client_options.jwks_uri))
|
196
211
|
end
|
@@ -236,31 +251,55 @@ module OmniAuth
|
|
236
251
|
@access_token
|
237
252
|
end
|
238
253
|
|
254
|
+
# Unlike ::OpenIDConnect::ResponseObject::IdToken.decode, this
|
255
|
+
# method splits the decoding and verification of JWT into two
|
256
|
+
# steps. First, we decode the JWT without verifying it to
|
257
|
+
# determine the algorithm used to sign. Then, we verify it using
|
258
|
+
# the appropriate public key (e.g. if algorithm is RS256) or
|
259
|
+
# shared secret (e.g. if algorithm is HS256). This works around a
|
260
|
+
# limitation in the openid_connect gem:
|
261
|
+
# https://github.com/nov/openid_connect/issues/61
|
239
262
|
def decode_id_token(id_token)
|
240
|
-
decode
|
263
|
+
decoded = JSON::JWT.decode(id_token, :skip_verification)
|
264
|
+
algorithm = decoded.algorithm.to_sym
|
265
|
+
|
266
|
+
keyset =
|
267
|
+
case algorithm
|
268
|
+
when :ES256, :ES384, :ES512, :ES256K, :RS256, :RS384, :RS512
|
269
|
+
public_key
|
270
|
+
when :HS256, :HS384, :HS512
|
271
|
+
secret
|
272
|
+
end
|
273
|
+
|
274
|
+
decoded.verify!(keyset)
|
275
|
+
::OpenIDConnect::ResponseObject::IdToken.new(decoded)
|
241
276
|
rescue JSON::JWK::Set::KidNotFound
|
242
|
-
#
|
243
|
-
#
|
244
|
-
#
|
277
|
+
# If the JWT has a key ID (kid), then we know that the set of
|
278
|
+
# keys supplied doesn't contain the one we want, and we're
|
279
|
+
# done. However, if there is no kid, then we try each key
|
280
|
+
# individually to see if one works:
|
245
281
|
# https://github.com/nov/json-jwt/pull/92#issuecomment-824654949
|
246
|
-
|
282
|
+
raise if decoded&.header&.key?('kid')
|
283
|
+
|
284
|
+
decoded = decode_with_each_key!(id_token, keyset)
|
247
285
|
|
248
286
|
raise unless decoded
|
249
287
|
|
250
288
|
decoded
|
289
|
+
|
251
290
|
end
|
252
291
|
|
253
292
|
def decode!(id_token, key)
|
254
293
|
::OpenIDConnect::ResponseObject::IdToken.decode(id_token, key)
|
255
294
|
end
|
256
295
|
|
257
|
-
def decode_with_each_key!(id_token)
|
258
|
-
return unless
|
296
|
+
def decode_with_each_key!(id_token, keyset)
|
297
|
+
return unless keyset.is_a?(JSON::JWK::Set)
|
259
298
|
|
260
|
-
|
299
|
+
keyset.each do |key|
|
261
300
|
begin
|
262
301
|
decoded = decode!(id_token, key)
|
263
|
-
rescue JSON::JWS::VerificationFailed
|
302
|
+
rescue JSON::JWS::VerificationFailed, JSON::JWS::UnexpectedAlgorithm, JSON::JWS::UnknownAlgorithm
|
264
303
|
next
|
265
304
|
end
|
266
305
|
|
@@ -303,18 +342,14 @@ module OmniAuth
|
|
303
342
|
super
|
304
343
|
end
|
305
344
|
|
306
|
-
def
|
307
|
-
@
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
if options.client_jwk_signing_key
|
313
|
-
parse_jwk_key(options.client_jwk_signing_key)
|
314
|
-
elsif options.client_x509_signing_key
|
315
|
-
parse_x509_key(options.client_x509_signing_key)
|
316
|
-
end
|
345
|
+
def configured_public_key
|
346
|
+
@configured_public_key ||= begin
|
347
|
+
if options.client_jwk_signing_key
|
348
|
+
parse_jwk_key(options.client_jwk_signing_key)
|
349
|
+
elsif options.client_x509_signing_key
|
350
|
+
parse_x509_key(options.client_x509_signing_key)
|
317
351
|
end
|
352
|
+
end
|
318
353
|
end
|
319
354
|
|
320
355
|
def parse_x509_key(key)
|
@@ -168,7 +168,7 @@ module OmniAuth
|
|
168
168
|
|
169
169
|
strategy.options.issuer = 'example.com'
|
170
170
|
strategy.options.client_signing_alg = :RS256
|
171
|
-
strategy.options.client_jwk_signing_key =
|
171
|
+
strategy.options.client_jwk_signing_key = jwks.to_s
|
172
172
|
strategy.options.response_type = 'code'
|
173
173
|
|
174
174
|
strategy.unstub(:user_info)
|
@@ -177,7 +177,7 @@ module OmniAuth
|
|
177
177
|
access_token.stubs(:refresh_token)
|
178
178
|
access_token.stubs(:expires_in)
|
179
179
|
access_token.stubs(:scope)
|
180
|
-
access_token.stubs(:id_token).returns(
|
180
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
181
181
|
client.expects(:access_token!).at_least_once.returns(access_token)
|
182
182
|
access_token.expects(:userinfo!).returns(user_info)
|
183
183
|
|
@@ -192,14 +192,13 @@ module OmniAuth
|
|
192
192
|
end
|
193
193
|
|
194
194
|
def test_callback_phase_with_id_token
|
195
|
-
code = SecureRandom.hex(16)
|
196
195
|
state = SecureRandom.hex(16)
|
197
|
-
request.stubs(:params).returns('id_token' =>
|
196
|
+
request.stubs(:params).returns('id_token' => jwt.to_s, 'state' => state)
|
198
197
|
request.stubs(:path_info).returns('')
|
199
198
|
|
200
199
|
strategy.options.issuer = 'example.com'
|
201
200
|
strategy.options.client_signing_alg = :RS256
|
202
|
-
strategy.options.client_jwk_signing_key =
|
201
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
203
202
|
strategy.options.response_type = 'id_token'
|
204
203
|
|
205
204
|
strategy.unstub(:user_info)
|
@@ -208,7 +207,7 @@ module OmniAuth
|
|
208
207
|
access_token.stubs(:refresh_token)
|
209
208
|
access_token.stubs(:expires_in)
|
210
209
|
access_token.stubs(:scope)
|
211
|
-
access_token.stubs(:id_token).returns(
|
210
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
212
211
|
|
213
212
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
214
213
|
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
|
@@ -221,14 +220,12 @@ module OmniAuth
|
|
221
220
|
end
|
222
221
|
|
223
222
|
def test_callback_phase_with_id_token_no_kid
|
224
|
-
rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
225
223
|
other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
226
224
|
|
227
|
-
key = JSON::JWK.new(
|
225
|
+
key = JSON::JWK.new(private_key)
|
228
226
|
other_key = JSON::JWK.new(other_rsa_private)
|
229
|
-
token = JSON::JWT.new(payload).sign(rsa_private, :RS256).to_s
|
230
227
|
state = SecureRandom.hex(16)
|
231
|
-
request.stubs(:params).returns('id_token' =>
|
228
|
+
request.stubs(:params).returns('id_token' => jwt.to_s, 'state' => state)
|
232
229
|
request.stubs(:path_info).returns('')
|
233
230
|
|
234
231
|
strategy.options.issuer = issuer
|
@@ -241,6 +238,93 @@ module OmniAuth
|
|
241
238
|
strategy.callback_phase
|
242
239
|
end
|
243
240
|
|
241
|
+
def test_callback_phase_with_id_token_with_kid
|
242
|
+
other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
243
|
+
|
244
|
+
key = JSON::JWK.new(private_key)
|
245
|
+
other_key = JSON::JWK.new(other_rsa_private)
|
246
|
+
state = SecureRandom.hex(16)
|
247
|
+
jwt_with_kid = JSON::JWT.new(payload).sign(key, :RS256)
|
248
|
+
request.stubs(:params).returns('id_token' => jwt_with_kid.to_s, 'state' => state)
|
249
|
+
request.stubs(:path_info).returns('')
|
250
|
+
|
251
|
+
strategy.options.issuer = issuer
|
252
|
+
strategy.options.client_signing_alg = :RS256
|
253
|
+
strategy.options.client_jwk_signing_key = { 'keys' => [other_key, key] }.to_json
|
254
|
+
strategy.options.response_type = 'id_token'
|
255
|
+
|
256
|
+
strategy.unstub(:user_info)
|
257
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
258
|
+
strategy.callback_phase
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_callback_phase_with_id_token_with_kid_and_no_matching_kid
|
262
|
+
other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
263
|
+
|
264
|
+
key = JSON::JWK.new(private_key)
|
265
|
+
other_key = JSON::JWK.new(other_rsa_private)
|
266
|
+
state = SecureRandom.hex(16)
|
267
|
+
jwt_with_kid = JSON::JWT.new(payload).sign(key, :RS256)
|
268
|
+
request.stubs(:params).returns('id_token' => jwt_with_kid.to_s, 'state' => state)
|
269
|
+
request.stubs(:path_info).returns('')
|
270
|
+
|
271
|
+
strategy.options.issuer = issuer
|
272
|
+
strategy.options.client_signing_alg = :RS256
|
273
|
+
# We use private_key here instead of the wrapped key, which contains a kid
|
274
|
+
strategy.options.client_jwk_signing_key = { 'keys' => [other_key, private_key] }.to_json
|
275
|
+
strategy.options.response_type = 'id_token'
|
276
|
+
|
277
|
+
strategy.unstub(:user_info)
|
278
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
279
|
+
|
280
|
+
assert_raises JSON::JWK::Set::KidNotFound do
|
281
|
+
strategy.callback_phase
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def test_callback_phase_with_id_token_with_hs256
|
286
|
+
state = SecureRandom.hex(16)
|
287
|
+
request.stubs(:params).returns('id_token' => jwt_with_hs256.to_s, 'state' => state)
|
288
|
+
request.stubs(:path_info).returns('')
|
289
|
+
|
290
|
+
strategy.options.issuer = issuer
|
291
|
+
strategy.options.client_options.secret = hmac_secret
|
292
|
+
strategy.options.client_signing_alg = :HS256
|
293
|
+
strategy.options.response_type = 'id_token'
|
294
|
+
|
295
|
+
strategy.unstub(:user_info)
|
296
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
297
|
+
strategy.callback_phase
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_callback_phase_with_hs256_jwt_secret
|
301
|
+
state = SecureRandom.hex(16)
|
302
|
+
request.stubs(:params).returns('id_token' => jwt_with_hs256.to_s, 'state' => state)
|
303
|
+
request.stubs(:path_info).returns('')
|
304
|
+
|
305
|
+
strategy.options.issuer = issuer
|
306
|
+
strategy.options.jwt_secret = hmac_secret
|
307
|
+
strategy.options.response_type = 'id_token'
|
308
|
+
|
309
|
+
strategy.unstub(:user_info)
|
310
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
311
|
+
strategy.callback_phase
|
312
|
+
end
|
313
|
+
|
314
|
+
def test_callback_phase_with_hs256_base64_jwt_secret
|
315
|
+
state = SecureRandom.hex(16)
|
316
|
+
request.stubs(:params).returns('id_token' => jwt_with_hs256.to_s, 'state' => state)
|
317
|
+
request.stubs(:path_info).returns('')
|
318
|
+
|
319
|
+
strategy.options.issuer = issuer
|
320
|
+
strategy.options.jwt_secret_base64 = Base64.encode64(hmac_secret)
|
321
|
+
strategy.options.response_type = 'id_token'
|
322
|
+
|
323
|
+
strategy.unstub(:user_info)
|
324
|
+
strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
|
325
|
+
strategy.callback_phase
|
326
|
+
end
|
327
|
+
|
244
328
|
def test_callback_phase_with_id_token_no_matching_key
|
245
329
|
rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
246
330
|
other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
|
@@ -266,11 +350,9 @@ module OmniAuth
|
|
266
350
|
end
|
267
351
|
|
268
352
|
def test_callback_phase_with_discovery
|
269
|
-
code = SecureRandom.hex(16)
|
270
353
|
state = SecureRandom.hex(16)
|
271
|
-
jwks = JSON::JWK::Set.new(JSON.parse(File.read('test/fixtures/jwks.json'))['keys'])
|
272
354
|
|
273
|
-
request.stubs(:params).returns('code' =>
|
355
|
+
request.stubs(:params).returns('code' => jwt.to_s, 'state' => state)
|
274
356
|
request.stubs(:path_info).returns('')
|
275
357
|
|
276
358
|
strategy.options.client_options.host = 'example.com'
|
@@ -285,7 +367,7 @@ module OmniAuth
|
|
285
367
|
config.stubs(:token_endpoint).returns('https://example.com/token')
|
286
368
|
config.stubs(:userinfo_endpoint).returns('https://example.com/userinfo')
|
287
369
|
config.stubs(:jwks_uri).returns('https://example.com/jwks')
|
288
|
-
config.stubs(:jwks).returns(jwks)
|
370
|
+
config.stubs(:jwks).returns(JSON::JWK::Set.new(jwks['keys']))
|
289
371
|
|
290
372
|
::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('https://example.com/').returns(config)
|
291
373
|
|
@@ -300,7 +382,7 @@ module OmniAuth
|
|
300
382
|
access_token.stubs(:refresh_token)
|
301
383
|
access_token.stubs(:expires_in)
|
302
384
|
access_token.stubs(:scope)
|
303
|
-
access_token.stubs(:id_token).returns(
|
385
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
304
386
|
client.expects(:access_token!).at_least_once.returns(access_token)
|
305
387
|
access_token.expects(:userinfo!).returns(user_info)
|
306
388
|
|
@@ -309,9 +391,9 @@ module OmniAuth
|
|
309
391
|
end
|
310
392
|
|
311
393
|
def test_callback_phase_with_jwks_uri
|
312
|
-
|
394
|
+
id_token = jwt.to_s
|
313
395
|
state = SecureRandom.hex(16)
|
314
|
-
request.stubs(:params).returns('id_token' =>
|
396
|
+
request.stubs(:params).returns('id_token' => id_token, 'state' => state)
|
315
397
|
request.stubs(:path_info).returns('')
|
316
398
|
|
317
399
|
strategy.options.issuer = 'example.com'
|
@@ -321,7 +403,7 @@ module OmniAuth
|
|
321
403
|
HTTPClient
|
322
404
|
.any_instance.stubs(:get_content)
|
323
405
|
.with(strategy.options.client_options.jwks_uri)
|
324
|
-
.returns(
|
406
|
+
.returns(jwks.to_json)
|
325
407
|
|
326
408
|
strategy.unstub(:user_info)
|
327
409
|
access_token = stub('OpenIDConnect::AccessToken')
|
@@ -329,7 +411,7 @@ module OmniAuth
|
|
329
411
|
access_token.stubs(:refresh_token)
|
330
412
|
access_token.stubs(:expires_in)
|
331
413
|
access_token.stubs(:scope)
|
332
|
-
access_token.stubs(:id_token).returns(
|
414
|
+
access_token.stubs(:id_token).returns(id_token)
|
333
415
|
|
334
416
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
335
417
|
id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
|
@@ -494,7 +576,7 @@ module OmniAuth
|
|
494
576
|
def test_credentials
|
495
577
|
strategy.options.issuer = 'example.com'
|
496
578
|
strategy.options.client_signing_alg = :RS256
|
497
|
-
strategy.options.client_jwk_signing_key =
|
579
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
498
580
|
|
499
581
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
500
582
|
id_token.stubs(:verify!).returns(true)
|
@@ -505,7 +587,7 @@ module OmniAuth
|
|
505
587
|
access_token.stubs(:refresh_token).returns(SecureRandom.hex(16))
|
506
588
|
access_token.stubs(:expires_in).returns(Time.now)
|
507
589
|
access_token.stubs(:scope).returns('openidconnect')
|
508
|
-
access_token.stubs(:id_token).returns(
|
590
|
+
access_token.stubs(:id_token).returns(jwt.to_s)
|
509
591
|
|
510
592
|
client.expects(:access_token!).returns(access_token)
|
511
593
|
access_token.expects(:refresh_token).returns(access_token.refresh_token)
|
@@ -592,11 +674,11 @@ module OmniAuth
|
|
592
674
|
strategy.options.issuer = 'foobar.com'
|
593
675
|
strategy.options.client_auth_method = :not_basic
|
594
676
|
strategy.options.client_signing_alg = :RS256
|
595
|
-
strategy.options.client_jwk_signing_key =
|
677
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
596
678
|
|
597
679
|
json_response = {
|
598
680
|
access_token: 'test_access_token',
|
599
|
-
id_token:
|
681
|
+
id_token: jwt.to_s,
|
600
682
|
token_type: 'Bearer',
|
601
683
|
}.to_json
|
602
684
|
success = Struct.new(:status, :body).new(200, json_response)
|
@@ -619,16 +701,14 @@ module OmniAuth
|
|
619
701
|
|
620
702
|
def test_public_key_with_jwks
|
621
703
|
strategy.options.client_signing_alg = :RS256
|
622
|
-
strategy.options.client_jwk_signing_key =
|
704
|
+
strategy.options.client_jwk_signing_key = jwks.to_json
|
623
705
|
|
624
706
|
assert_equal JSON::JWK::Set, strategy.public_key.class
|
625
707
|
end
|
626
708
|
|
627
709
|
def test_public_key_with_jwk
|
628
710
|
strategy.options.client_signing_alg = :RS256
|
629
|
-
|
630
|
-
jwks = JSON.parse(jwks_str)
|
631
|
-
jwk = jwks['keys'].first
|
711
|
+
jwk = jwks[:keys].first
|
632
712
|
strategy.options.client_jwk_signing_key = jwk.to_json
|
633
713
|
|
634
714
|
assert_equal JSON::JWK, strategy.public_key.class
|
@@ -640,10 +720,10 @@ module OmniAuth
|
|
640
720
|
assert_equal OpenSSL::PKey::RSA, strategy.public_key.class
|
641
721
|
end
|
642
722
|
|
643
|
-
def
|
723
|
+
def test_secret_with_hmac
|
644
724
|
strategy.options.client_options.secret = 'secret'
|
645
725
|
strategy.options.client_signing_alg = :HS256
|
646
|
-
assert_equal strategy.options.client_options.secret, strategy.
|
726
|
+
assert_equal strategy.options.client_options.secret, strategy.secret
|
647
727
|
end
|
648
728
|
|
649
729
|
def test_id_token_auth_hash
|
@@ -653,16 +733,7 @@ module OmniAuth
|
|
653
733
|
|
654
734
|
id_token = stub('OpenIDConnect::ResponseObject::IdToken')
|
655
735
|
id_token.stubs(:verify!).returns(true)
|
656
|
-
id_token.stubs(:raw_attributes, :to_h).returns(
|
657
|
-
{
|
658
|
-
"iss": "http://server.example.com",
|
659
|
-
"sub": "248289761001",
|
660
|
-
"aud": "s6BhdRkqt3",
|
661
|
-
"nonce": "n-0S6_WzA2Mj",
|
662
|
-
"exp": 1311281970,
|
663
|
-
"iat": 1311280970,
|
664
|
-
}
|
665
|
-
)
|
736
|
+
id_token.stubs(:raw_attributes, :to_h).returns(payload)
|
666
737
|
|
667
738
|
request.stubs(:params).returns('state' => state, 'nounce' => nonce, 'id_token' => id_token)
|
668
739
|
request.stubs(:path_info).returns('')
|
data/test/strategy_test_case.rb
CHANGED
@@ -27,6 +27,30 @@ class StrategyTestCase < MiniTest::Test
|
|
27
27
|
}
|
28
28
|
end
|
29
29
|
|
30
|
+
def private_key
|
31
|
+
@private_key ||= OpenSSL::PKey::RSA.generate(512)
|
32
|
+
end
|
33
|
+
|
34
|
+
def jwt
|
35
|
+
@jwt ||= JSON::JWT.new(payload).sign(private_key, :RS256)
|
36
|
+
end
|
37
|
+
|
38
|
+
def hmac_secret
|
39
|
+
@hmac_secret ||= SecureRandom.hex(16)
|
40
|
+
end
|
41
|
+
|
42
|
+
def jwt_with_hs256
|
43
|
+
@jwt_with_hs256 ||= JSON::JWT.new(payload).sign(hmac_secret, :HS256)
|
44
|
+
end
|
45
|
+
|
46
|
+
def jwks
|
47
|
+
@jwks ||= begin
|
48
|
+
key = JSON::JWK.new(private_key)
|
49
|
+
keyset = JSON::JWK::Set.new(key)
|
50
|
+
{ keys: keyset }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
30
54
|
def user_info
|
31
55
|
@user_info ||= OpenIDConnect::ResponseObject::UserInfo.new(
|
32
56
|
sub: SecureRandom.hex(16),
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-omniauth-openid-connect
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.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:
|
12
|
+
date: 2022-01-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: addressable
|
@@ -209,6 +209,7 @@ files:
|
|
209
209
|
- ".rubocop.yml"
|
210
210
|
- ".travis.yml"
|
211
211
|
- CHANGELOG.md
|
212
|
+
- CONTRIBUTING.md
|
212
213
|
- Gemfile
|
213
214
|
- Guardfile
|
214
215
|
- LICENSE.txt
|
@@ -220,7 +221,6 @@ files:
|
|
220
221
|
- lib/omniauth/openid_connect/version.rb
|
221
222
|
- lib/omniauth/strategies/openid_connect.rb
|
222
223
|
- lib/omniauth_openid_connect.rb
|
223
|
-
- test/fixtures/id_token.txt
|
224
224
|
- test/fixtures/jwks.json
|
225
225
|
- test/fixtures/test.crt
|
226
226
|
- test/lib/omniauth/strategies/openid_connect_test.rb
|
@@ -230,7 +230,7 @@ homepage: https://gitlab.com/gitlab-org/gitlab-omniauth-openid-connect
|
|
230
230
|
licenses:
|
231
231
|
- MIT
|
232
232
|
metadata: {}
|
233
|
-
post_install_message:
|
233
|
+
post_install_message:
|
234
234
|
rdoc_options: []
|
235
235
|
require_paths:
|
236
236
|
- lib
|
@@ -245,12 +245,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
245
245
|
- !ruby/object:Gem::Version
|
246
246
|
version: '0'
|
247
247
|
requirements: []
|
248
|
-
rubygems_version: 3.
|
249
|
-
signing_key:
|
248
|
+
rubygems_version: 3.2.28
|
249
|
+
signing_key:
|
250
250
|
specification_version: 4
|
251
251
|
summary: OpenID Connect Strategy for OmniAuth
|
252
252
|
test_files:
|
253
|
-
- test/fixtures/id_token.txt
|
254
253
|
- test/fixtures/jwks.json
|
255
254
|
- test/fixtures/test.crt
|
256
255
|
- test/lib/omniauth/strategies/openid_connect_test.rb
|
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
|