gitlab-omniauth-openid-connect 0.4.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.
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'omniauth/openid_connect'
@@ -0,0 +1 @@
1
+ eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6qJp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJNqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7TpdQyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoSK5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg
@@ -0,0 +1,8 @@
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
+ }
@@ -0,0 +1,19 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDJDCCAgwCCQC57Ob2JfXb+DANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJK
3
+ UDEOMAwGA1UECBMFVG9reW8xITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5
4
+ IEx0ZDESMBAGA1UEAxMJbG9jYWxob3N0MB4XDTE0MDgwMTA4NTAxM1oXDTE1MDgw
5
+ MTA4NTAxM1owVDELMAkGA1UEBhMCSlAxDjAMBgNVBAgTBVRva3lvMSEwHwYDVQQK
6
+ ExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDCC
7
+ ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN+7czSGHN2087T+oX2kBCY/
8
+ XN6UOS/mdU2Gn//omZlyxsQXIqvgBLNWeCVt4QdlFUbgPLggfXUelECV/RUOCIIi
9
+ F2Th4t3x1LviN2XkUiva0DZBnOycqEaJdkyreEuGL1CLVZgZjKmSzNqLl0Yci3D0
10
+ zgVsXFZSadQebietm4CCmfJYREt9NJxXcrLxVDgat/Xm/KJBsohs3f+cbBT8EXer
11
+ 7+2oZjZoVUgw1hu0alaOvAfE4mxsVwjn3g2mjDqRJLbbuWqgDobjMHah+d4zwJvN
12
+ ePK8E0hfaz/XBLsJ4e6bQA3M3bANEgSvsicup/qb/0th4gUdc/kj4aJGj0RP7oEC
13
+ AwEAATANBgkqhkiG9w0BAQUFAAOCAQEADuVec/8u2qJiq6K2W/gSLGYCBZq64OrA
14
+ s7L2+S82m9/3gAb62wGcDNZjIGFDQubXmO6RhHv7JUT5YZqv9/kRGTJcHDUrwwoN
15
+ IE99CIPizp7VfnrZ6GsYeszSsw3m+mKTETm+6ELmaSDbYAsrCg4IpGwUF0L88ATv
16
+ CJ8QzW4X7b9dYVc7UAYyCie2N65GXfesBbRlSwFLuVqIzZfMdNpNijTIUwUqGSME
17
+ b8IjLYzvekP53CO4wEBRrAVIPNXgftorxIE30OLWua2Qw3y6Pn+Qp5fLe47025S7
18
+ Lcec18/FbHG0Vbq0qO9cKQw80XyK31N6z556wr2GN2WyixkzVRddXA==
19
+ -----END CERTIFICATE-----
@@ -0,0 +1,684 @@
1
+ require_relative '../../../test_helper'
2
+
3
+ module OmniAuth
4
+ module Strategies
5
+ class OpenIDConnectTest < StrategyTestCase
6
+ def test_client_options_defaults
7
+ assert_equal 'https', strategy.options.client_options.scheme
8
+ assert_equal 443, strategy.options.client_options.port
9
+ assert_equal '/authorize', strategy.options.client_options.authorization_endpoint
10
+ assert_equal '/token', strategy.options.client_options.token_endpoint
11
+ end
12
+
13
+ def test_request_phase
14
+ expected_redirect = /^https:\/\/example\.com\/authorize\?client_id=1234&nonce=\w{32}&response_type=code&scope=openid&state=\w{32}$/
15
+ strategy.options.issuer = 'example.com'
16
+ strategy.options.client_options.host = 'example.com'
17
+ strategy.expects(:redirect).with(regexp_matches(expected_redirect))
18
+ strategy.request_phase
19
+ end
20
+
21
+ def test_logout_phase_with_discovery
22
+ expected_redirect = %r{^https:\/\/example\.com\/logout$}
23
+ strategy.options.client_options.host = 'example.com'
24
+ strategy.options.discovery = true
25
+
26
+ issuer = stub('OpenIDConnect::Discovery::Issuer')
27
+ issuer.stubs(:issuer).returns('https://example.com/')
28
+ ::OpenIDConnect::Discovery::Provider.stubs(:discover!).returns(issuer)
29
+
30
+ config = stub('OpenIDConnect::Discovery::Provder::Config')
31
+ config.stubs(:authorization_endpoint).returns('https://example.com/authorization')
32
+ config.stubs(:token_endpoint).returns('https://example.com/token')
33
+ config.stubs(:userinfo_endpoint).returns('https://example.com/userinfo')
34
+ config.stubs(:jwks_uri).returns('https://example.com/jwks')
35
+ config.stubs(:end_session_endpoint).returns('https://example.com/logout')
36
+ ::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('https://example.com/').returns(config)
37
+
38
+ request.stubs(:path_info).returns('/auth/openid_connect/logout')
39
+
40
+ strategy.expects(:redirect).with(regexp_matches(expected_redirect))
41
+ strategy.other_phase
42
+ end
43
+
44
+ def test_logout_phase_with_discovery_and_post_logout_redirect_uri
45
+ expected_redirect = 'https://example.com/logout?post_logout_redirect_uri=https%3A%2F%2Fmysite.com'
46
+ strategy.options.client_options.host = 'example.com'
47
+ strategy.options.discovery = true
48
+ strategy.options.post_logout_redirect_uri = 'https://mysite.com'
49
+
50
+ issuer = stub('OpenIDConnect::Discovery::Issuer')
51
+ issuer.stubs(:issuer).returns('https://example.com/')
52
+ ::OpenIDConnect::Discovery::Provider.stubs(:discover!).returns(issuer)
53
+
54
+ config = stub('OpenIDConnect::Discovery::Provder::Config')
55
+ config.stubs(:authorization_endpoint).returns('https://example.com/authorization')
56
+ config.stubs(:token_endpoint).returns('https://example.com/token')
57
+ config.stubs(:userinfo_endpoint).returns('https://example.com/userinfo')
58
+ config.stubs(:jwks_uri).returns('https://example.com/jwks')
59
+ config.stubs(:end_session_endpoint).returns('https://example.com/logout')
60
+ ::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('https://example.com/').returns(config)
61
+
62
+ request.stubs(:path_info).returns('/auth/openid_connect/logout')
63
+
64
+ strategy.expects(:redirect).with(expected_redirect)
65
+ strategy.other_phase
66
+ end
67
+
68
+ def test_logout_phase
69
+ strategy.options.issuer = 'example.com'
70
+ strategy.options.client_options.host = 'example.com'
71
+
72
+ request.stubs(:path_info).returns('/auth/openid_connect/logout')
73
+
74
+ strategy.expects(:call_app!)
75
+ strategy.other_phase
76
+ end
77
+
78
+ def test_request_phase_with_params
79
+ expected_redirect = /^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$/
80
+ strategy.options.issuer = 'example.com'
81
+ strategy.options.client_options.host = 'example.com'
82
+ request.stubs(:params).returns('login_hint' => 'john.doe@example.com', 'ui_locales' => 'en', 'claims_locales' => 'es')
83
+
84
+ strategy.expects(:redirect).with(regexp_matches(expected_redirect))
85
+ strategy.request_phase
86
+ end
87
+
88
+ def test_request_phase_with_discovery
89
+ expected_redirect = /^https:\/\/example\.com\/authorization\?client_id=1234&nonce=\w{32}&response_type=code&scope=openid&state=\w{32}$/
90
+ strategy.options.client_options.host = 'example.com'
91
+ strategy.options.discovery = true
92
+
93
+ issuer = stub('OpenIDConnect::Discovery::Issuer')
94
+ issuer.stubs(:issuer).returns('https://example.com/')
95
+ ::OpenIDConnect::Discovery::Provider.stubs(:discover!).returns(issuer)
96
+
97
+ config = stub('OpenIDConnect::Discovery::Provder::Config')
98
+ config.stubs(:authorization_endpoint).returns('https://example.com/authorization')
99
+ config.stubs(:token_endpoint).returns('https://example.com/token')
100
+ config.stubs(:userinfo_endpoint).returns('https://example.com/userinfo')
101
+ config.stubs(:jwks_uri).returns('https://example.com/jwks')
102
+ ::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('https://example.com/').returns(config)
103
+
104
+ strategy.expects(:redirect).with(regexp_matches(expected_redirect))
105
+ strategy.request_phase
106
+
107
+ assert_equal strategy.options.issuer, 'https://example.com/'
108
+ assert_equal strategy.options.client_options.authorization_endpoint, 'https://example.com/authorization'
109
+ assert_equal strategy.options.client_options.token_endpoint, 'https://example.com/token'
110
+ assert_equal strategy.options.client_options.userinfo_endpoint, 'https://example.com/userinfo'
111
+ assert_equal strategy.options.client_options.jwks_uri, 'https://example.com/jwks'
112
+ assert_nil strategy.options.client_options.end_session_endpoint
113
+ end
114
+
115
+ def test_request_phase_with_response_mode
116
+ expected_redirect = /^https:\/\/example\.com\/authorize\?client_id=1234&nonce=\w{32}&response_mode=form_post&response_type=id_token&scope=openid&state=\w{32}$/
117
+ strategy.options.issuer = 'example.com'
118
+ strategy.options.response_mode = 'form_post'
119
+ strategy.options.response_type = 'id_token'
120
+ strategy.options.client_options.host = 'example.com'
121
+
122
+ strategy.expects(:redirect).with(regexp_matches(expected_redirect))
123
+ strategy.request_phase
124
+ end
125
+
126
+ def test_request_phase_with_response_mode_symbol
127
+ expected_redirect = /^https:\/\/example\.com\/authorize\?client_id=1234&nonce=\w{32}&response_mode=form_post&response_type=id_token&scope=openid&state=\w{32}$/
128
+ strategy.options.issuer = 'example.com'
129
+ strategy.options.response_mode = 'form_post'
130
+ strategy.options.response_type = :id_token
131
+ strategy.options.client_options.host = 'example.com'
132
+
133
+ strategy.expects(:redirect).with(regexp_matches(expected_redirect))
134
+ strategy.request_phase
135
+ end
136
+
137
+ def test_option_acr_values
138
+ strategy.options.client_options[:host] = 'foobar.com'
139
+
140
+ assert(!(strategy.authorize_uri =~ /acr_values=/), 'URI must not contain acr_values')
141
+
142
+ strategy.options.acr_values = 'urn:some:acr:values:value'
143
+ assert(strategy.authorize_uri =~ /acr_values=/, 'URI must contain acr_values')
144
+ end
145
+
146
+ def test_option_custom_attributes
147
+ strategy.options.client_options[:host] = 'foobar.com'
148
+ strategy.options.extra_authorize_params = {resource: 'xyz'}
149
+
150
+ assert(strategy.authorize_uri =~ /resource=xyz/, 'URI must contain custom params')
151
+ end
152
+
153
+ def test_uid
154
+ assert_equal user_info.sub, strategy.uid
155
+
156
+ strategy.options.uid_field = 'preferred_username'
157
+ assert_equal user_info.preferred_username, strategy.uid
158
+
159
+ strategy.options.uid_field = 'something'
160
+ assert_equal user_info.sub, strategy.uid
161
+ end
162
+
163
+ def test_callback_phase(session = {}, params = {})
164
+ code = SecureRandom.hex(16)
165
+ state = SecureRandom.hex(16)
166
+ request.stubs(:params).returns('code' => code, 'state' => state)
167
+ request.stubs(:path_info).returns('')
168
+
169
+ strategy.options.issuer = 'example.com'
170
+ strategy.options.client_signing_alg = :RS256
171
+ strategy.options.client_jwk_signing_key = File.read('test/fixtures/jwks.json')
172
+ strategy.options.response_type = 'code'
173
+
174
+ strategy.unstub(:user_info)
175
+ access_token = stub('OpenIDConnect::AccessToken')
176
+ access_token.stubs(:access_token)
177
+ access_token.stubs(:refresh_token)
178
+ access_token.stubs(:expires_in)
179
+ access_token.stubs(:scope)
180
+ access_token.stubs(:id_token).returns(File.read('test/fixtures/id_token.txt'))
181
+ client.expects(:access_token!).at_least_once.returns(access_token)
182
+ access_token.expects(:userinfo!).returns(user_info)
183
+
184
+ id_token = stub('OpenIDConnect::ResponseObject::IdToken')
185
+ id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
186
+ id_token.stubs(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, nonce: nonce).returns(true)
187
+ id_token.expects(:verify!)
188
+
189
+ strategy.expects(:decode_id_token).twice.with(access_token.id_token).returns(id_token)
190
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
191
+ strategy.callback_phase
192
+ end
193
+
194
+ def test_callback_phase_with_id_token
195
+ code = SecureRandom.hex(16)
196
+ state = SecureRandom.hex(16)
197
+ request.stubs(:params).returns('id_token' => code, 'state' => state)
198
+ request.stubs(:path_info).returns('')
199
+
200
+ strategy.options.issuer = 'example.com'
201
+ strategy.options.client_signing_alg = :RS256
202
+ strategy.options.client_jwk_signing_key = File.read('test/fixtures/jwks.json')
203
+ strategy.options.response_type = 'id_token'
204
+
205
+ strategy.unstub(:user_info)
206
+ access_token = stub('OpenIDConnect::AccessToken')
207
+ access_token.stubs(:access_token)
208
+ access_token.stubs(:refresh_token)
209
+ access_token.stubs(:expires_in)
210
+ access_token.stubs(:scope)
211
+ access_token.stubs(:id_token).returns(File.read('test/fixtures/id_token.txt'))
212
+
213
+ id_token = stub('OpenIDConnect::ResponseObject::IdToken')
214
+ id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
215
+ id_token.stubs(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, nonce: nonce).returns(true)
216
+ ::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
217
+ id_token.expects(:verify!)
218
+
219
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
220
+ strategy.callback_phase
221
+ end
222
+
223
+ def test_callback_phase_with_id_token_no_kid
224
+ rsa_private = OpenSSL::PKey::RSA.generate(2048)
225
+ other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
226
+
227
+ key = JSON::JWK.new(rsa_private)
228
+ other_key = JSON::JWK.new(other_rsa_private)
229
+ token = JSON::JWT.new(payload).sign(rsa_private, :RS256).to_s
230
+ state = SecureRandom.hex(16)
231
+ request.stubs(:params).returns('id_token' => token, 'state' => state)
232
+ request.stubs(:path_info).returns('')
233
+
234
+ strategy.options.issuer = issuer
235
+ strategy.options.client_signing_alg = :RS256
236
+ strategy.options.client_jwk_signing_key = { 'keys' => [other_key, key] }.to_json
237
+ strategy.options.response_type = 'id_token'
238
+
239
+ strategy.unstub(:user_info)
240
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
241
+ strategy.callback_phase
242
+ end
243
+
244
+ def test_callback_phase_with_id_token_no_matching_key
245
+ rsa_private = OpenSSL::PKey::RSA.generate(2048)
246
+ other_rsa_private = OpenSSL::PKey::RSA.generate(2048)
247
+
248
+ key = JSON::JWK.new(rsa_private)
249
+ other_key = JSON::JWK.new(other_rsa_private)
250
+ token = JSON::JWT.new(payload).sign(rsa_private, :RS256).to_s
251
+ state = SecureRandom.hex(16)
252
+ request.stubs(:params).returns('id_token' => token, 'state' => state)
253
+ request.stubs(:path_info).returns('')
254
+
255
+ strategy.options.issuer = issuer
256
+ strategy.options.client_signing_alg = :RS256
257
+ strategy.options.client_jwk_signing_key = { 'keys' => [other_key] }.to_json
258
+ strategy.options.response_type = 'id_token'
259
+
260
+ strategy.unstub(:user_info)
261
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
262
+
263
+ assert_raises JSON::JWK::Set::KidNotFound do
264
+ strategy.callback_phase
265
+ end
266
+ end
267
+
268
+ def test_callback_phase_with_discovery
269
+ code = SecureRandom.hex(16)
270
+ state = SecureRandom.hex(16)
271
+ jwks = JSON::JWK::Set.new(JSON.parse(File.read('test/fixtures/jwks.json'))['keys'])
272
+
273
+ request.stubs(:params).returns('code' => code, 'state' => state)
274
+ request.stubs(:path_info).returns('')
275
+
276
+ strategy.options.client_options.host = 'example.com'
277
+ strategy.options.discovery = true
278
+
279
+ issuer = stub('OpenIDConnect::Discovery::Issuer')
280
+ issuer.stubs(:issuer).returns('https://example.com/')
281
+ ::OpenIDConnect::Discovery::Provider.stubs(:discover!).returns(issuer)
282
+
283
+ config = stub('OpenIDConnect::Discovery::Provder::Config')
284
+ config.stubs(:authorization_endpoint).returns('https://example.com/authorization')
285
+ config.stubs(:token_endpoint).returns('https://example.com/token')
286
+ config.stubs(:userinfo_endpoint).returns('https://example.com/userinfo')
287
+ config.stubs(:jwks_uri).returns('https://example.com/jwks')
288
+ config.stubs(:jwks).returns(jwks)
289
+
290
+ ::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('https://example.com/').returns(config)
291
+
292
+ id_token = stub('OpenIDConnect::ResponseObject::IdToken')
293
+ id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
294
+ id_token.stubs(:verify!).with(issuer: 'https://example.com/', client_id: @identifier, nonce: nonce).returns(true)
295
+ ::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
296
+
297
+ strategy.unstub(:user_info)
298
+ access_token = stub('OpenIDConnect::AccessToken')
299
+ access_token.stubs(:access_token)
300
+ access_token.stubs(:refresh_token)
301
+ access_token.stubs(:expires_in)
302
+ access_token.stubs(:scope)
303
+ access_token.stubs(:id_token).returns(File.read('test/fixtures/id_token.txt'))
304
+ client.expects(:access_token!).at_least_once.returns(access_token)
305
+ access_token.expects(:userinfo!).returns(user_info)
306
+
307
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
308
+ strategy.callback_phase
309
+ end
310
+
311
+ def test_callback_phase_with_jwks_uri
312
+ code = SecureRandom.hex(16)
313
+ state = SecureRandom.hex(16)
314
+ request.stubs(:params).returns('id_token' => code, 'state' => state)
315
+ request.stubs(:path_info).returns('')
316
+
317
+ strategy.options.issuer = 'example.com'
318
+ strategy.options.client_options.jwks_uri = 'https://jwks.example.com'
319
+ strategy.options.response_type = 'id_token'
320
+
321
+ HTTPClient
322
+ .any_instance.stubs(:get_content)
323
+ .with(strategy.options.client_options.jwks_uri)
324
+ .returns(File.read('test/fixtures/jwks.json'))
325
+
326
+ strategy.unstub(:user_info)
327
+ access_token = stub('OpenIDConnect::AccessToken')
328
+ access_token.stubs(:access_token)
329
+ access_token.stubs(:refresh_token)
330
+ access_token.stubs(:expires_in)
331
+ access_token.stubs(:scope)
332
+ access_token.stubs(:id_token).returns(File.read('test/fixtures/id_token.txt'))
333
+
334
+ id_token = stub('OpenIDConnect::ResponseObject::IdToken')
335
+ id_token.stubs(:raw_attributes).returns('sub' => 'sub', 'name' => 'name', 'email' => 'email')
336
+ id_token.stubs(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, nonce: nonce).returns(true)
337
+ ::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
338
+ id_token.expects(:verify!)
339
+
340
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
341
+ strategy.callback_phase
342
+ end
343
+
344
+ def test_callback_phase_with_error
345
+ state = SecureRandom.hex(16)
346
+ request.stubs(:params).returns('error' => 'invalid_request')
347
+ request.stubs(:path_info).returns('')
348
+
349
+ strategy.call!({'rack.session' => {'omniauth.state' => state, 'omniauth.nonce' => nonce}})
350
+ strategy.expects(:fail!)
351
+ strategy.callback_phase
352
+ end
353
+
354
+ def test_callback_phase_with_invalid_state
355
+ code = SecureRandom.hex(16)
356
+ state = SecureRandom.hex(16)
357
+ request.stubs(:params).returns('code' => code, 'state' => 'foobar')
358
+ request.stubs(:path_info).returns('')
359
+
360
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
361
+ strategy.expects(:fail!)
362
+ strategy.callback_phase
363
+ end
364
+
365
+ def test_callback_phase_without_code
366
+ state = SecureRandom.hex(16)
367
+ request.stubs(:params).returns('state' => state)
368
+ request.stubs(:path_info).returns('')
369
+
370
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
371
+
372
+ strategy.expects(:fail!).with(:missing_code, is_a(OmniAuth::OpenIDConnect::MissingCodeError))
373
+ strategy.callback_phase
374
+ end
375
+
376
+ def test_callback_phase_without_id_token
377
+ state = SecureRandom.hex(16)
378
+ request.stubs(:params).returns('state' => state)
379
+ request.stubs(:path_info).returns('')
380
+ strategy.options.response_type = 'id_token'
381
+
382
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
383
+
384
+ strategy.expects(:fail!).with(:missing_id_token, is_a(OmniAuth::OpenIDConnect::MissingIdTokenError))
385
+ strategy.callback_phase
386
+ end
387
+
388
+ def test_callback_phase_without_id_token_symbol
389
+ state = SecureRandom.hex(16)
390
+ request.stubs(:params).returns('state' => state)
391
+ request.stubs(:path_info).returns('')
392
+ strategy.options.response_type = :id_token
393
+
394
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
395
+
396
+ strategy.expects(:fail!).with(:missing_id_token, is_a(OmniAuth::OpenIDConnect::MissingIdTokenError))
397
+ strategy.callback_phase
398
+ end
399
+
400
+ def test_callback_phase_with_timeout
401
+ code = SecureRandom.hex(16)
402
+ state = SecureRandom.hex(16)
403
+ request.stubs(:params).returns('code' => code, 'state' => state)
404
+ request.stubs(:path_info).returns('')
405
+
406
+ strategy.options.issuer = 'example.com'
407
+
408
+ strategy.stubs(:access_token).raises(::Timeout::Error.new('error'))
409
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
410
+ strategy.expects(:fail!)
411
+
412
+ id_token = stub('OpenIDConnect::ResponseObject::IdToken')
413
+ id_token.stubs(:verify!).with(issuer: 'example.com', client_id: @identifier, nonce: nonce).returns(true)
414
+ ::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
415
+
416
+ strategy.callback_phase
417
+ end
418
+
419
+ def test_callback_phase_with_etimeout
420
+ code = SecureRandom.hex(16)
421
+ state = SecureRandom.hex(16)
422
+ request.stubs(:params).returns('code' => code, 'state' => state)
423
+ request.stubs(:path_info).returns('')
424
+
425
+ strategy.options.issuer = 'example.com'
426
+
427
+ strategy.stubs(:access_token).raises(::Errno::ETIMEDOUT.new('error'))
428
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
429
+ strategy.expects(:fail!)
430
+
431
+ id_token = stub('OpenIDConnect::ResponseObject::IdToken')
432
+ id_token.stubs(:verify!).with(issuer: 'example.com', client_id: @identifier, nonce: nonce).returns(true)
433
+ ::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
434
+
435
+ strategy.callback_phase
436
+ end
437
+
438
+ def test_callback_phase_with_socket_error
439
+ code = SecureRandom.hex(16)
440
+ state = SecureRandom.hex(16)
441
+ request.stubs(:params).returns('code' => code, 'state' => state)
442
+ request.stubs(:path_info).returns('')
443
+
444
+ strategy.options.issuer = 'example.com'
445
+
446
+ strategy.stubs(:access_token).raises(::SocketError.new('error'))
447
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
448
+ strategy.expects(:fail!)
449
+
450
+ id_token = stub('OpenIDConnect::ResponseObject::IdToken')
451
+ id_token.stubs(:verify!).with(issuer: 'example.com', client_id: @identifier, nonce: nonce).returns(true)
452
+ ::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
453
+
454
+ strategy.callback_phase
455
+ end
456
+
457
+ def test_callback_phase_with_rack_oauth2_client_error
458
+ code = SecureRandom.hex(16)
459
+ state = SecureRandom.hex(16)
460
+ request.stubs(:params).returns('code' => code, 'state' => state)
461
+ request.stubs(:path_info).returns('')
462
+
463
+ strategy.options.issuer = 'example.com'
464
+
465
+ strategy.stubs(:access_token).raises(::Rack::OAuth2::Client::Error.new('error', error: 'Unknown'))
466
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
467
+ strategy.expects(:fail!)
468
+
469
+ id_token = stub('OpenIDConnect::ResponseObject::IdToken')
470
+ id_token.stubs(:verify!).with(issuer: 'example.com', client_id: @identifier, nonce: nonce).returns(true)
471
+ ::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
472
+
473
+ strategy.callback_phase
474
+ end
475
+
476
+ def test_info
477
+ info = strategy.info
478
+ assert_equal user_info.name, info[:name]
479
+ assert_equal user_info.email, info[:email]
480
+ assert_equal user_info.preferred_username, info[:nickname]
481
+ assert_equal user_info.given_name, info[:first_name]
482
+ assert_equal user_info.family_name, info[:last_name]
483
+ assert_equal user_info.gender, info[:gender]
484
+ assert_equal user_info.picture, info[:image]
485
+ assert_equal user_info.phone_number, info[:phone]
486
+ assert_equal({ website: user_info.website }, info[:urls])
487
+ end
488
+
489
+ def test_extra
490
+ assert_equal({ raw_info: user_info.as_json }, strategy.extra)
491
+ end
492
+
493
+ def test_credentials
494
+ strategy.options.issuer = 'example.com'
495
+ strategy.options.client_signing_alg = :RS256
496
+ strategy.options.client_jwk_signing_key = File.read('test/fixtures/jwks.json')
497
+
498
+ id_token = stub('OpenIDConnect::ResponseObject::IdToken')
499
+ id_token.stubs(:verify!).returns(true)
500
+ ::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
501
+
502
+ access_token = stub('OpenIDConnect::AccessToken')
503
+ access_token.stubs(:access_token).returns(SecureRandom.hex(16))
504
+ access_token.stubs(:refresh_token).returns(SecureRandom.hex(16))
505
+ access_token.stubs(:expires_in).returns(Time.now)
506
+ access_token.stubs(:scope).returns('openidconnect')
507
+ access_token.stubs(:id_token).returns(File.read('test/fixtures/id_token.txt'))
508
+
509
+ client.expects(:access_token!).returns(access_token)
510
+ access_token.expects(:refresh_token).returns(access_token.refresh_token)
511
+ access_token.expects(:expires_in).returns(access_token.expires_in)
512
+
513
+ assert_equal(
514
+ {
515
+ id_token: access_token.id_token,
516
+ token: access_token.access_token,
517
+ refresh_token: access_token.refresh_token,
518
+ expires_in: access_token.expires_in,
519
+ scope: access_token.scope
520
+ },
521
+ strategy.credentials
522
+ )
523
+ end
524
+
525
+ def test_option_send_nonce
526
+ strategy.options.client_options[:host] = 'foobar.com'
527
+
528
+ assert(strategy.authorize_uri =~ /nonce=/, 'URI must contain nonce')
529
+
530
+ strategy.options.send_nonce = false
531
+ assert(!(strategy.authorize_uri =~ /nonce=/), 'URI must not contain nonce')
532
+ end
533
+
534
+ def test_failure_endpoint_redirect
535
+ OmniAuth.config.stubs(:failure_raise_out_environments).returns([])
536
+ strategy.stubs(:env).returns({})
537
+ request.stubs(:params).returns('error' => 'access denied')
538
+
539
+ result = strategy.callback_phase
540
+
541
+ assert(result.is_a? Array)
542
+ assert(result[0] == 302, 'Redirect')
543
+ assert(result[1]["Location"] =~ /\/auth\/failure/)
544
+ end
545
+
546
+ def test_state
547
+ strategy.options.state = -> { 42 }
548
+
549
+ expected_redirect = /&state=42/
550
+ strategy.options.issuer = 'example.com'
551
+ strategy.options.client_options.host = 'example.com'
552
+ strategy.expects(:redirect).with(regexp_matches(expected_redirect))
553
+ strategy.request_phase
554
+
555
+ session = { 'state' => 42 }
556
+ # this should succeed as the correct state is passed with the request
557
+ test_callback_phase(session, { 'state' => 42 })
558
+
559
+ # the following should fail because the wrong state is passed to the callback
560
+ code = SecureRandom.hex(16)
561
+ request.stubs(:params).returns('code' => code, 'state' => 43)
562
+ request.stubs(:path_info).returns('')
563
+
564
+ strategy.call!('rack.session' => session)
565
+ strategy.expects(:fail!)
566
+ strategy.callback_phase
567
+ end
568
+
569
+ def test_dynamic_state
570
+ # Stub request parameters
571
+ request.stubs(:path_info).returns('')
572
+ strategy.call!('rack.session' => { }, QUERY_STRING: { state: 'abc', client_id: '123' } )
573
+
574
+ strategy.options.state = lambda { |env|
575
+ # Get params from request, e.g. CGI.parse(env['QUERY_STRING'])
576
+ env[:QUERY_STRING][:state] + env[:QUERY_STRING][:client_id]
577
+ }
578
+
579
+ expected_redirect = /&state=abc123/
580
+ strategy.options.issuer = 'example.com'
581
+ strategy.options.client_options.host = 'example.com'
582
+ strategy.expects(:redirect).with(regexp_matches(expected_redirect))
583
+ strategy.request_phase
584
+ end
585
+
586
+ def test_option_client_auth_method
587
+ state = SecureRandom.hex(16)
588
+
589
+ opts = strategy.options.client_options
590
+ opts[:host] = 'foobar.com'
591
+ strategy.options.issuer = 'foobar.com'
592
+ strategy.options.client_auth_method = :not_basic
593
+ strategy.options.client_signing_alg = :RS256
594
+ strategy.options.client_jwk_signing_key = File.read('test/fixtures/jwks.json')
595
+
596
+ json_response = {
597
+ access_token: 'test_access_token',
598
+ id_token: File.read('test/fixtures/id_token.txt'),
599
+ token_type: 'Bearer',
600
+ }.to_json
601
+ success = Struct.new(:status, :body).new(200, json_response)
602
+
603
+ request.stubs(:path_info).returns('')
604
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
605
+
606
+ id_token = stub('OpenIDConnect::ResponseObject::IdToken')
607
+ id_token.stubs(:verify!).with(issuer: strategy.options.issuer, client_id: @identifier, nonce: nonce).returns(true)
608
+ ::OpenIDConnect::ResponseObject::IdToken.stubs(:decode).returns(id_token)
609
+
610
+ HTTPClient.any_instance.stubs(:post).with(
611
+ "#{ opts.scheme }://#{ opts.host }:#{ opts.port }#{ opts.token_endpoint }",
612
+ { scope: 'openid', grant_type: :client_credentials, client_id: @identifier, client_secret: @secret },
613
+ {}
614
+ ).returns(success)
615
+
616
+ assert(strategy.send :access_token)
617
+ end
618
+
619
+ def test_public_key_with_jwks
620
+ strategy.options.client_signing_alg = :RS256
621
+ strategy.options.client_jwk_signing_key = File.read('./test/fixtures/jwks.json')
622
+
623
+ assert_equal JSON::JWK::Set, strategy.public_key.class
624
+ end
625
+
626
+ def test_public_key_with_jwk
627
+ strategy.options.client_signing_alg = :RS256
628
+ jwks_str = File.read('./test/fixtures/jwks.json')
629
+ jwks = JSON.parse(jwks_str)
630
+ jwk = jwks['keys'].first
631
+ strategy.options.client_jwk_signing_key = jwk.to_json
632
+
633
+ assert_equal JSON::JWK, strategy.public_key.class
634
+ end
635
+
636
+ def test_public_key_with_x509
637
+ strategy.options.client_signing_alg = :RS256
638
+ strategy.options.client_x509_signing_key = File.read('./test/fixtures/test.crt')
639
+ assert_equal OpenSSL::PKey::RSA, strategy.public_key.class
640
+ end
641
+
642
+ def test_public_key_with_hmac
643
+ strategy.options.client_options.secret = 'secret'
644
+ strategy.options.client_signing_alg = :HS256
645
+ assert_equal strategy.options.client_options.secret, strategy.public_key
646
+ end
647
+
648
+ def test_id_token_auth_hash
649
+ state = SecureRandom.hex(16)
650
+ strategy.options.response_type = 'id_token'
651
+ strategy.options.issuer = 'example.com'
652
+
653
+ id_token = stub('OpenIDConnect::ResponseObject::IdToken')
654
+ id_token.stubs(:verify!).returns(true)
655
+ id_token.stubs(:raw_attributes, :to_h).returns(
656
+ {
657
+ "iss": "http://server.example.com",
658
+ "sub": "248289761001",
659
+ "aud": "s6BhdRkqt3",
660
+ "nonce": "n-0S6_WzA2Mj",
661
+ "exp": 1311281970,
662
+ "iat": 1311280970,
663
+ }
664
+ )
665
+
666
+ request.stubs(:params).returns('state' => state, 'nounce' => nonce, 'id_token' => id_token)
667
+ request.stubs(:path_info).returns('')
668
+
669
+ strategy.stubs(:decode_id_token).returns(id_token)
670
+ strategy.stubs(:stored_state).returns(state)
671
+
672
+ strategy.call!('rack.session' => { 'omniauth.state' => state, 'omniauth.nonce' => nonce })
673
+ strategy.callback_phase
674
+
675
+ auth_hash = strategy.send(:env)['omniauth.auth']
676
+ assert auth_hash.key?('provider')
677
+ assert auth_hash.key?('uid')
678
+ assert auth_hash.key?('info')
679
+ assert auth_hash.key?('extra')
680
+ assert auth_hash['extra'].key?('raw_info')
681
+ end
682
+ end
683
+ end
684
+ end