gitlab-omniauth-openid-connect 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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