omniauth-auth0 3.1.0 → 3.2.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/devcontainer.json +1 -1
  3. data/.github/CODEOWNERS +1 -1
  4. data/.github/ISSUE_TEMPLATE/Bug Report.yml +76 -0
  5. data/.github/ISSUE_TEMPLATE/Feature Request.yml +53 -0
  6. data/.github/ISSUE_TEMPLATE/config.yml +2 -2
  7. data/.github/actions/get-prerelease/action.yml +30 -0
  8. data/.github/actions/get-release-notes/action.yml +42 -0
  9. data/.github/actions/get-version/action.yml +21 -0
  10. data/.github/actions/release-create/action.yml +47 -0
  11. data/.github/actions/rl-scanner/action.yml +71 -0
  12. data/.github/actions/rubygems-publish/action.yml +30 -0
  13. data/.github/actions/setup/action.yml +28 -0
  14. data/.github/actions/tag-exists/action.yml +36 -0
  15. data/.github/dependabot.yml +13 -0
  16. data/.github/workflows/codeql.yml +53 -0
  17. data/.github/workflows/matrix.json +7 -0
  18. data/.github/workflows/publish.yml +33 -0
  19. data/.github/workflows/rl-scanner.yml +65 -0
  20. data/.github/workflows/ruby-release.yml +72 -0
  21. data/.github/workflows/snyk.yml +40 -0
  22. data/.github/workflows/test.yml +69 -0
  23. data/.shiprc +2 -1
  24. data/.version +1 -0
  25. data/CHANGELOG.md +20 -0
  26. data/EXAMPLES.md +19 -5
  27. data/Gemfile +4 -5
  28. data/Gemfile.lock +128 -91
  29. data/README.md +42 -1
  30. data/lib/omniauth/auth0/jwt_token.rb +38 -0
  31. data/lib/omniauth/auth0/jwt_validator.rb +19 -3
  32. data/lib/omniauth/strategies/auth0.rb +48 -14
  33. data/lib/omniauth-auth0/version.rb +1 -1
  34. data/omniauth-auth0.gemspec +1 -0
  35. data/spec/omniauth/auth0/jwt_token_spec.rb +87 -0
  36. data/spec/omniauth/auth0/jwt_validator_spec.rb +109 -31
  37. data/spec/omniauth/strategies/auth0_spec.rb +478 -230
  38. data/spec/spec_helper.rb +1 -0
  39. metadata +39 -14
  40. data/.circleci/config.yml +0 -63
  41. data/.gemrelease +0 -2
  42. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -39
  43. data/.github/ISSUE_TEMPLATE/report_a_bug.md +0 -55
  44. data/.github/workflows/semgrep.yml +0 -24
@@ -3,6 +3,7 @@
3
3
  require 'spec_helper'
4
4
  require 'jwt'
5
5
  require 'multi_json'
6
+ require 'cgi'
6
7
 
7
8
  OmniAuth.config.allowed_request_methods = [:get, :post]
8
9
 
@@ -10,10 +11,231 @@ RSpec.shared_examples 'site has valid domain url' do |url|
10
11
  it { expect(subject.site).to eq(url) }
11
12
  end
12
13
 
14
+ RSpec.shared_examples 'client_options with valid configuration' do
15
+ context 'domain with https' do
16
+ let(:domain_url) { 'https://samples.auth0.com' }
17
+ it_behaves_like 'site has valid domain url', 'https://samples.auth0.com'
18
+ end
19
+
20
+ context 'domain with http' do
21
+ let(:domain_url) { 'http://mydomain.com' }
22
+ it_behaves_like 'site has valid domain url', 'http://mydomain.com'
23
+ end
24
+
25
+ context 'domain with host only' do
26
+ let(:domain_url) { 'samples.auth0.com' }
27
+ it_behaves_like 'site has valid domain url', 'https://samples.auth0.com'
28
+ end
29
+
30
+ it 'should have correct authorize path' do
31
+ expect(subject.options[:authorize_url]).to eq('/authorize')
32
+ end
33
+
34
+ it 'should have the correct userinfo path' do
35
+ expect(subject.options[:userinfo_url]).to eq('/userinfo')
36
+ end
37
+
38
+ it 'should have the correct token path' do
39
+ expect(subject.options[:token_url]).to eq('/oauth/token')
40
+ end
41
+ end
42
+
43
+ RSpec.shared_examples 'oauth redirects with various parameters' do
44
+ it 'redirects to hosted login page' do
45
+ get 'auth/auth0'
46
+ expect(last_response.status).to eq(302)
47
+ redirect_url = last_response.headers['Location']
48
+ expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
49
+ expect(redirect_url).to have_query('response_type', 'code')
50
+ expect(redirect_url).to have_query('state')
51
+ expect(redirect_url).to have_query('client_id')
52
+ expect(redirect_url).to have_query('redirect_uri')
53
+ expect(redirect_url).not_to have_query('auth0Client')
54
+ expect(redirect_url).not_to have_query('connection')
55
+ expect(redirect_url).not_to have_query('connection_scope')
56
+ expect(redirect_url).not_to have_query('prompt')
57
+ expect(redirect_url).not_to have_query('screen_hint')
58
+ expect(redirect_url).not_to have_query('login_hint')
59
+ expect(redirect_url).not_to have_query('organization')
60
+ expect(redirect_url).not_to have_query('invitation')
61
+ end
62
+
63
+ it 'redirects to hosted login page' do
64
+ get 'auth/auth0?connection=abcd'
65
+ expect(last_response.status).to eq(302)
66
+ redirect_url = last_response.headers['Location']
67
+ expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
68
+ expect(redirect_url).to have_query('response_type', 'code')
69
+ expect(redirect_url).to have_query('state')
70
+ expect(redirect_url).to have_query('client_id')
71
+ expect(redirect_url).to have_query('redirect_uri')
72
+ expect(redirect_url).to have_query('connection', 'abcd')
73
+ expect(redirect_url).not_to have_query('auth0Client')
74
+ expect(redirect_url).not_to have_query('connection_scope')
75
+ expect(redirect_url).not_to have_query('prompt')
76
+ expect(redirect_url).not_to have_query('screen_hint')
77
+ expect(redirect_url).not_to have_query('login_hint')
78
+ expect(redirect_url).not_to have_query('organization')
79
+ expect(redirect_url).not_to have_query('invitation')
80
+ end
81
+
82
+ it 'redirects to the hosted login page with connection_scope' do
83
+ get 'auth/auth0?connection_scope=identity_provider_scope'
84
+ expect(last_response.status).to eq(302)
85
+ redirect_url = last_response.headers['Location']
86
+ expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
87
+ expect(redirect_url)
88
+ .to have_query('connection_scope', 'identity_provider_scope')
89
+ end
90
+
91
+ it 'redirects to hosted login page with prompt=login' do
92
+ get 'auth/auth0?prompt=login'
93
+ expect(last_response.status).to eq(302)
94
+ redirect_url = last_response.headers['Location']
95
+ expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
96
+ expect(redirect_url).to have_query('response_type', 'code')
97
+ expect(redirect_url).to have_query('state')
98
+ expect(redirect_url).to have_query('client_id')
99
+ expect(redirect_url).to have_query('redirect_uri')
100
+ expect(redirect_url).to have_query('prompt', 'login')
101
+ expect(redirect_url).not_to have_query('auth0Client')
102
+ expect(redirect_url).not_to have_query('connection')
103
+ expect(redirect_url).not_to have_query('login_hint')
104
+ expect(redirect_url).not_to have_query('organization')
105
+ expect(redirect_url).not_to have_query('invitation')
106
+ end
107
+
108
+ it 'redirects to hosted login page with screen_hint=signup' do
109
+ get 'auth/auth0?screen_hint=signup'
110
+ expect(last_response.status).to eq(302)
111
+ redirect_url = last_response.headers['Location']
112
+ expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
113
+ expect(redirect_url).to have_query('response_type', 'code')
114
+ expect(redirect_url).to have_query('state')
115
+ expect(redirect_url).to have_query('client_id')
116
+ expect(redirect_url).to have_query('redirect_uri')
117
+ expect(redirect_url).to have_query('screen_hint', 'signup')
118
+ expect(redirect_url).not_to have_query('auth0Client')
119
+ expect(redirect_url).not_to have_query('connection')
120
+ expect(redirect_url).not_to have_query('login_hint')
121
+ expect(redirect_url).not_to have_query('organization')
122
+ expect(redirect_url).not_to have_query('invitation')
123
+ end
124
+
125
+ it 'redirects to hosted login page with organization=TestOrg and invitation=TestInvite' do
126
+ get 'auth/auth0?organization=TestOrg&invitation=TestInvite'
127
+ expect(last_response.status).to eq(302)
128
+ redirect_url = last_response.headers['Location']
129
+ expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
130
+ expect(redirect_url).to have_query('response_type', 'code')
131
+ expect(redirect_url).to have_query('state')
132
+ expect(redirect_url).to have_query('client_id')
133
+ expect(redirect_url).to have_query('redirect_uri')
134
+ expect(redirect_url).to have_query('organization', 'TestOrg')
135
+ expect(redirect_url).to have_query('invitation', 'TestInvite')
136
+ expect(redirect_url).not_to have_query('auth0Client')
137
+ expect(redirect_url).not_to have_query('connection')
138
+ expect(redirect_url).not_to have_query('connection_scope')
139
+ expect(redirect_url).not_to have_query('prompt')
140
+ expect(redirect_url).not_to have_query('screen_hint')
141
+ expect(redirect_url).not_to have_query('login_hint')
142
+ end
143
+
144
+ it 'redirects to hosted login page with login_hint=example@mail.com' do
145
+ get 'auth/auth0?login_hint=example@mail.com'
146
+ expect(last_response.status).to eq(302)
147
+ redirect_url = last_response.headers['Location']
148
+ expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
149
+ expect(redirect_url).to have_query('response_type', 'code')
150
+ expect(redirect_url).to have_query('state')
151
+ expect(redirect_url).to have_query('client_id')
152
+ expect(redirect_url).to have_query('redirect_uri')
153
+ expect(redirect_url).to have_query('login_hint', 'example@mail.com')
154
+ expect(redirect_url).not_to have_query('auth0Client')
155
+ expect(redirect_url).not_to have_query('connection')
156
+ expect(redirect_url).not_to have_query('connection_scope')
157
+ expect(redirect_url).not_to have_query('prompt')
158
+ expect(redirect_url).not_to have_query('screen_hint')
159
+ expect(redirect_url).not_to have_query('organization')
160
+ expect(redirect_url).not_to have_query('invitation')
161
+ end
162
+
163
+ it "stores session['authorize_params'] as a plain Ruby Hash" do
164
+ get '/auth/auth0'
165
+ expect(session['authorize_params'].class).to eq(::Hash)
166
+ end
167
+ end
168
+
169
+ RSpec.shared_examples 'basic oauth callback assertions' do
170
+ it 'to succeed' do
171
+ expect(last_response.status).to eq(200)
172
+ end
173
+
174
+ it 'has credentials' do
175
+ expect(subject['credentials']['token']).to eq(access_token)
176
+ expect(subject['credentials']['expires']).to be true
177
+ expect(subject['credentials']['expires_at']).to_not be_nil
178
+ end
179
+
180
+ it 'has basic values' do
181
+ expect(subject['provider']).to eq('auth0')
182
+ expect(subject['uid']).to eq(user_id)
183
+ expect(subject['info']['name']).to eq(name)
184
+ end
185
+
186
+ it 'should use the user info endpoint' do
187
+ expect(subject['extra']['raw_info']).to eq(basic_user_info)
188
+ end
189
+ end
190
+
191
+ RSpec.shared_examples 'basic oauth refresh token callback assertions' do
192
+ it 'to succeed' do
193
+ expect(last_response.status).to eq(200)
194
+ end
195
+
196
+ it 'has credentials' do
197
+ expect(subject['credentials']['token']).to eq(access_token)
198
+ expect(subject['credentials']['refresh_token']).to eq(refresh_token)
199
+ expect(subject['credentials']['expires']).to be true
200
+ expect(subject['credentials']['expires_at']).to_not be_nil
201
+ end
202
+ end
203
+
204
+ RSpec.shared_examples 'oidc callback assertions' do
205
+ it 'to succeed' do
206
+ expect(last_response.status).to eq(200)
207
+ end
208
+
209
+ it 'has credentials' do
210
+ expect(subject['credentials']['token']).to eq(access_token)
211
+ expect(subject['credentials']['expires']).to be true
212
+ expect(subject['credentials']['expires_at']).to_not be_nil
213
+ expect(subject['credentials']['id_token']).to eq(id_token)
214
+ end
215
+
216
+ it 'has basic values' do
217
+ expect(subject['provider']).to eq('auth0')
218
+ expect(subject['uid']).to eq(user_id)
219
+ end
220
+
221
+ it 'has info' do
222
+ expect(subject['info']['name']).to eq(name)
223
+ expect(subject['info']['nickname']).to eq(nickname)
224
+ expect(subject['info']['image']).to eq(picture)
225
+ expect(subject['info']['email']).to eq(email)
226
+ end
227
+
228
+ it 'has extra' do
229
+ expect(subject['extra']['raw_info']['email_verified']).to be true
230
+ end
231
+ end
232
+
13
233
  describe OmniAuth::Strategies::Auth0 do
14
234
  let(:client_id) { 'CLIENT_ID' }
15
235
  let(:client_secret) { 'CLIENT_SECRET' }
16
236
  let(:domain_url) { 'https://samples.auth0.com' }
237
+ let(:client_assertion_signing_algorithm) { 'RS256' }
238
+ let(:client_assertion_signing_key) { OpenSSL::PKey::RSA.generate(2048) }
17
239
  let(:application) do
18
240
  lambda do
19
241
  [200, {}, ['Hello.']]
@@ -27,176 +249,97 @@ describe OmniAuth::Strategies::Auth0 do
27
249
  domain_url
28
250
  )
29
251
  end
30
-
31
- describe 'client_options' do
32
- let(:subject) { OmniAuth::Strategies::Auth0.new(
252
+ let(:auth0_client_assertion_signing_key) do
253
+ OmniAuth::Strategies::Auth0.new(
33
254
  application,
34
255
  client_id,
35
- client_secret,
36
- domain_url
37
- ).client }
38
-
39
- context 'domain with https' do
40
- let(:domain_url) { 'https://samples.auth0.com' }
41
- it_behaves_like 'site has valid domain url', 'https://samples.auth0.com'
42
- end
43
-
44
- context 'domain with http' do
45
- let(:domain_url) { 'http://mydomain.com' }
46
- it_behaves_like 'site has valid domain url', 'http://mydomain.com'
47
- end
48
-
49
- context 'domain with host only' do
50
- let(:domain_url) { 'samples.auth0.com' }
51
- it_behaves_like 'site has valid domain url', 'https://samples.auth0.com'
256
+ nil,
257
+ domain_url,
258
+ { client_assertion_signing_key: client_assertion_signing_key,
259
+ client_assertion_signing_algorithm: client_assertion_signing_algorithm}
260
+ )
261
+ end
262
+ describe 'client_options' do
263
+ context 'when using client_secret authentication' do
264
+ let(:subject) { OmniAuth::Strategies::Auth0.new(
265
+ application,
266
+ client_id,
267
+ client_secret,
268
+ domain_url
269
+ ).client }
270
+
271
+ it_behaves_like 'client_options with valid configuration'
52
272
  end
53
273
 
54
- it 'should have correct authorize path' do
55
- expect(subject.options[:authorize_url]).to eq('/authorize')
56
- end
274
+ context 'when using client assertion signing key authentication' do
275
+ let(:subject) do
276
+ OmniAuth::Strategies::Auth0.new(
277
+ application,
278
+ client_id,
279
+ nil,
280
+ domain_url,
281
+ { client_assertion_signing_key: client_assertion_signing_key,
282
+ client_assertion_signing_algorithm: client_assertion_signing_algorithm }
283
+ ).client
284
+ end
57
285
 
58
- it 'should have the correct userinfo path' do
59
- expect(subject.options[:userinfo_url]).to eq('/userinfo')
60
- end
286
+ it_behaves_like 'client_options with valid configuration'
61
287
 
62
- it 'should have the correct token path' do
63
- expect(subject.options[:token_url]).to eq('/oauth/token')
288
+ it 'should have the correct auth_scheme' do
289
+ expect(subject.options[:auth_scheme]).to eq(:request_body)
290
+ end
64
291
  end
65
292
  end
66
293
 
67
294
  describe 'options' do
68
- let(:subject) { auth0.options }
295
+ context 'when using client_secret authentication' do
296
+ let(:subject) { auth0.options }
69
297
 
70
- it 'should have the correct client_id' do
71
- expect(subject[:client_id]).to eq(client_id)
72
- end
298
+ it 'should have the correct client_id' do
299
+ expect(subject[:client_id]).to eq(client_id)
300
+ end
73
301
 
74
- it 'should have the correct client secret' do
75
- expect(subject[:client_secret]).to eq(client_secret)
76
- end
77
- it 'should have correct domain' do
78
- expect(subject[:domain]).to eq(domain_url)
302
+ it 'should have the correct client secret' do
303
+ expect(subject[:client_secret]).to eq(client_secret)
304
+ end
305
+ it 'should have correct domain' do
306
+ expect(subject[:domain]).to eq(domain_url)
307
+ end
79
308
  end
80
- end
81
309
 
82
- describe 'oauth' do
83
- it 'redirects to hosted login page' do
84
- get 'auth/auth0'
85
- expect(last_response.status).to eq(302)
86
- redirect_url = last_response.headers['Location']
87
- expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
88
- expect(redirect_url).to have_query('response_type', 'code')
89
- expect(redirect_url).to have_query('state')
90
- expect(redirect_url).to have_query('client_id')
91
- expect(redirect_url).to have_query('redirect_uri')
92
- expect(redirect_url).not_to have_query('auth0Client')
93
- expect(redirect_url).not_to have_query('connection')
94
- expect(redirect_url).not_to have_query('connection_scope')
95
- expect(redirect_url).not_to have_query('prompt')
96
- expect(redirect_url).not_to have_query('screen_hint')
97
- expect(redirect_url).not_to have_query('login_hint')
98
- expect(redirect_url).not_to have_query('organization')
99
- expect(redirect_url).not_to have_query('invitation')
100
- end
310
+ context 'when using client assertion signing key authentication' do
311
+ let(:subject) { auth0_client_assertion_signing_key.options }
101
312
 
102
- it 'redirects to hosted login page' do
103
- get 'auth/auth0?connection=abcd'
104
- expect(last_response.status).to eq(302)
105
- redirect_url = last_response.headers['Location']
106
- expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
107
- expect(redirect_url).to have_query('response_type', 'code')
108
- expect(redirect_url).to have_query('state')
109
- expect(redirect_url).to have_query('client_id')
110
- expect(redirect_url).to have_query('redirect_uri')
111
- expect(redirect_url).to have_query('connection', 'abcd')
112
- expect(redirect_url).not_to have_query('auth0Client')
113
- expect(redirect_url).not_to have_query('connection_scope')
114
- expect(redirect_url).not_to have_query('prompt')
115
- expect(redirect_url).not_to have_query('screen_hint')
116
- expect(redirect_url).not_to have_query('login_hint')
117
- expect(redirect_url).not_to have_query('organization')
118
- expect(redirect_url).not_to have_query('invitation')
119
- end
313
+ it 'should have the correct client_id' do
314
+ expect(subject[:client_id]).to eq(client_id)
315
+ end
120
316
 
121
- it 'redirects to the hosted login page with connection_scope' do
122
- get 'auth/auth0?connection_scope=identity_provider_scope'
123
- expect(last_response.status).to eq(302)
124
- redirect_url = last_response.headers['Location']
125
- expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
126
- expect(redirect_url)
127
- .to have_query('connection_scope', 'identity_provider_scope')
128
- end
317
+ it 'should have the correct client secret' do
318
+ expect(subject[:client_secret]).to eq(nil)
319
+ end
320
+ it 'should have correct domain' do
321
+ expect(subject[:domain]).to eq(domain_url)
322
+ end
129
323
 
130
- it 'redirects to hosted login page with prompt=login' do
131
- get 'auth/auth0?prompt=login'
132
- expect(last_response.status).to eq(302)
133
- redirect_url = last_response.headers['Location']
134
- expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
135
- expect(redirect_url).to have_query('response_type', 'code')
136
- expect(redirect_url).to have_query('state')
137
- expect(redirect_url).to have_query('client_id')
138
- expect(redirect_url).to have_query('redirect_uri')
139
- expect(redirect_url).to have_query('prompt', 'login')
140
- expect(redirect_url).not_to have_query('auth0Client')
141
- expect(redirect_url).not_to have_query('connection')
142
- expect(redirect_url).not_to have_query('login_hint')
143
- expect(redirect_url).not_to have_query('organization')
144
- expect(redirect_url).not_to have_query('invitation')
324
+ it 'should have the correct client_assertion_signing_key' do
325
+ expect(subject[:client_assertion_signing_key]).to eq(client_assertion_signing_key)
326
+ end
145
327
  end
328
+ end
146
329
 
147
- it 'redirects to hosted login page with screen_hint=signup' do
148
- get 'auth/auth0?screen_hint=signup'
149
- expect(last_response.status).to eq(302)
150
- redirect_url = last_response.headers['Location']
151
- expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
152
- expect(redirect_url).to have_query('response_type', 'code')
153
- expect(redirect_url).to have_query('state')
154
- expect(redirect_url).to have_query('client_id')
155
- expect(redirect_url).to have_query('redirect_uri')
156
- expect(redirect_url).to have_query('screen_hint', 'signup')
157
- expect(redirect_url).not_to have_query('auth0Client')
158
- expect(redirect_url).not_to have_query('connection')
159
- expect(redirect_url).not_to have_query('login_hint')
160
- expect(redirect_url).not_to have_query('organization')
161
- expect(redirect_url).not_to have_query('invitation')
330
+ describe 'oauth' do
331
+ context 'when using client_secret authentication' do
332
+ it_behaves_like 'oauth redirects with various parameters'
162
333
  end
163
334
 
164
- it 'redirects to hosted login page with organization=TestOrg and invitation=TestInvite' do
165
- get 'auth/auth0?organization=TestOrg&invitation=TestInvite'
166
- expect(last_response.status).to eq(302)
167
- redirect_url = last_response.headers['Location']
168
- expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
169
- expect(redirect_url).to have_query('response_type', 'code')
170
- expect(redirect_url).to have_query('state')
171
- expect(redirect_url).to have_query('client_id')
172
- expect(redirect_url).to have_query('redirect_uri')
173
- expect(redirect_url).to have_query('organization', 'TestOrg')
174
- expect(redirect_url).to have_query('invitation', 'TestInvite')
175
- expect(redirect_url).not_to have_query('auth0Client')
176
- expect(redirect_url).not_to have_query('connection')
177
- expect(redirect_url).not_to have_query('connection_scope')
178
- expect(redirect_url).not_to have_query('prompt')
179
- expect(redirect_url).not_to have_query('screen_hint')
180
- expect(redirect_url).not_to have_query('login_hint')
181
- end
335
+ context 'when using client assertion signing key authentication' do
336
+ before do
337
+ @app = make_application(client_secret: nil,
338
+ client_assertion_signing_key: client_assertion_signing_key,
339
+ client_assertion_signing_algorithm: client_assertion_signing_algorithm)
340
+ end
182
341
 
183
- it 'redirects to hosted login page with login_hint=example@mail.com' do
184
- get 'auth/auth0?login_hint=example@mail.com'
185
- expect(last_response.status).to eq(302)
186
- redirect_url = last_response.headers['Location']
187
- expect(redirect_url).to start_with('https://samples.auth0.com/authorize')
188
- expect(redirect_url).to have_query('response_type', 'code')
189
- expect(redirect_url).to have_query('state')
190
- expect(redirect_url).to have_query('client_id')
191
- expect(redirect_url).to have_query('redirect_uri')
192
- expect(redirect_url).to have_query('login_hint', 'example@mail.com')
193
- expect(redirect_url).not_to have_query('auth0Client')
194
- expect(redirect_url).not_to have_query('connection')
195
- expect(redirect_url).not_to have_query('connection_scope')
196
- expect(redirect_url).not_to have_query('prompt')
197
- expect(redirect_url).not_to have_query('screen_hint')
198
- expect(redirect_url).not_to have_query('organization')
199
- expect(redirect_url).not_to have_query('invitation')
342
+ it_behaves_like 'oauth redirects with various parameters'
200
343
  end
201
344
 
202
345
  def session
@@ -206,12 +349,6 @@ describe OmniAuth::Strategies::Auth0 do
206
349
  Marshal.load(decoded_session_data)
207
350
  end
208
351
 
209
- it "stores session['authorize_params'] as a plain Ruby Hash" do
210
- get '/auth/auth0'
211
-
212
- expect(session['authorize_params'].class).to eq(::Hash)
213
- end
214
-
215
352
  describe 'callback' do
216
353
  let(:access_token) { 'access token' }
217
354
  let(:expires_in) { 2000 }
@@ -227,20 +364,6 @@ describe OmniAuth::Strategies::Auth0 do
227
364
  let(:email) { 'mail@mail.com' }
228
365
  let(:email_verified) { true }
229
366
 
230
- let(:id_token) do
231
- payload = {}
232
- payload['sub'] = user_id
233
- payload['iss'] = "#{domain_url}/"
234
- payload['aud'] = client_id
235
- payload['name'] = name
236
- payload['nickname'] = nickname
237
- payload['picture'] = picture
238
- payload['email'] = email
239
- payload['email_verified'] = email_verified
240
-
241
- JWT.encode payload, client_secret, 'HS256'
242
- end
243
-
244
367
  let(:oauth_response) do
245
368
  {
246
369
  access_token: access_token,
@@ -260,15 +383,6 @@ describe OmniAuth::Strategies::Auth0 do
260
383
 
261
384
  let(:basic_user_info) { { "sub" => user_id, "name" => name } }
262
385
 
263
- def stub_auth(body)
264
- stub_request(:post, 'https://samples.auth0.com/oauth/token')
265
- .with(headers: { 'Auth0-Client' => telemetry_value })
266
- .to_return(
267
- headers: { 'Content-Type' => 'application/json' },
268
- body: MultiJson.encode(body)
269
- )
270
- end
271
-
272
386
  def stub_userinfo(body)
273
387
  stub_request(:get, 'https://samples.auth0.com/userinfo')
274
388
  .to_return(
@@ -290,91 +404,217 @@ describe OmniAuth::Strategies::Auth0 do
290
404
  MultiJson.decode(last_response.body)
291
405
  end
292
406
 
293
- context 'basic oauth' do
294
- before do
295
- stub_auth(oauth_response)
296
- stub_userinfo(basic_user_info)
297
- trigger_callback
407
+ context 'when using client_secret authentication' do
408
+ let(:id_token) do
409
+ payload = {}
410
+ payload['sub'] = user_id
411
+ payload['iss'] = "#{domain_url}/"
412
+ payload['aud'] = client_id
413
+ payload['name'] = name
414
+ payload['nickname'] = nickname
415
+ payload['picture'] = picture
416
+ payload['email'] = email
417
+ payload['email_verified'] = email_verified
418
+
419
+ JWT.encode payload, client_secret, 'HS256'
298
420
  end
299
421
 
300
- it 'to succeed' do
301
- expect(last_response.status).to eq(200)
422
+ def stub_auth(body)
423
+ stub_request(:post, 'https://samples.auth0.com/oauth/token')
424
+ .with(headers: { 'Auth0-Client' => telemetry_value })
425
+ .to_return(
426
+ headers: { 'Content-Type' => 'application/json' },
427
+ body: MultiJson.encode(body)
428
+ )
302
429
  end
303
430
 
304
- it 'has credentials' do
305
- expect(subject['credentials']['token']).to eq(access_token)
306
- expect(subject['credentials']['expires']).to be true
307
- expect(subject['credentials']['expires_at']).to_not be_nil
431
+ context 'basic oauth' do
432
+ before do
433
+ stub_auth(oauth_response)
434
+ stub_userinfo(basic_user_info)
435
+ trigger_callback
436
+ end
437
+
438
+ it_behaves_like 'basic oauth callback assertions'
308
439
  end
309
440
 
310
- it 'has basic values' do
311
- expect(subject['provider']).to eq('auth0')
312
- expect(subject['uid']).to eq(user_id)
313
- expect(subject['info']['name']).to eq(name)
441
+ context 'basic oauth w/refresh token' do
442
+ before do
443
+ stub_auth(oauth_response.merge(refresh_token: refresh_token))
444
+ stub_userinfo(basic_user_info)
445
+ trigger_callback
446
+ end
447
+
448
+ it_behaves_like 'basic oauth refresh token callback assertions'
314
449
  end
315
450
 
316
- it 'should use the user info endpoint' do
317
- expect(subject['extra']['raw_info']).to eq(basic_user_info)
451
+ context 'oidc' do
452
+ before do
453
+ stub_auth(oidc_response)
454
+ trigger_callback
455
+ end
456
+
457
+ it_behaves_like 'oidc callback assertions'
318
458
  end
319
459
  end
320
460
 
321
- context 'basic oauth w/refresh token' do
322
- before do
323
- stub_auth(oauth_response.merge(refresh_token: refresh_token))
324
- stub_userinfo(basic_user_info)
325
- trigger_callback
461
+ context 'when using client assertion signing key authentication' do
462
+ let(:jwt_token) { JWT.encode({ sub: client_id }, client_assertion_signing_key, 'RS256') }
463
+ let(:valid_jwks_kid) { 'NkJCQzIyQzRBMEU4NjhGNUU4MzU4RkY0M0ZDQzkwOUQ0Q0VGNUMwQg' }
464
+
465
+ let(:rsa_private_key) do
466
+ OpenSSL::PKey::RSA.generate 2048
326
467
  end
327
468
 
328
- it 'to succeed' do
329
- expect(last_response.status).to eq(200)
469
+ let(:valid_jwks) do
470
+ {
471
+ keys: [
472
+ {
473
+ kid: valid_jwks_kid,
474
+ x5c: [Base64.encode64(make_cert(rsa_private_key).to_der)]
475
+ }
476
+ ]
477
+ }.to_json
330
478
  end
331
479
 
332
- it 'has credentials' do
333
- expect(subject['credentials']['token']).to eq(access_token)
334
- expect(subject['credentials']['refresh_token']).to eq(refresh_token)
335
- expect(subject['credentials']['expires']).to be true
336
- expect(subject['credentials']['expires_at']).to_not be_nil
480
+ let(:id_token) do
481
+ payload = {}
482
+ payload['sub'] = user_id
483
+ payload['iss'] = "#{domain_url}/"
484
+ payload['aud'] = client_id
485
+ payload['name'] = name
486
+ payload['nickname'] = nickname
487
+ payload['picture'] = picture
488
+ payload['email'] = email
489
+ payload['email_verified'] = email_verified
490
+
491
+ JWT.encode payload, rsa_private_key, 'RS256', kid: valid_jwks_kid
337
492
  end
338
- end
339
493
 
340
- context 'oidc' do
341
- before do
342
- stub_auth(oidc_response)
343
- trigger_callback
494
+ def jwt_token?(token)
495
+ JWT.decode(token, nil, false)
496
+ true
497
+ rescue JWT::DecodeError, ArgumentError
498
+ false
344
499
  end
345
500
 
346
- it 'to succeed' do
347
- expect(last_response.status).to eq(200)
501
+ def make_cert(private_key)
502
+ cert = OpenSSL::X509::Certificate.new
503
+ cert.issuer = OpenSSL::X509::Name.parse('/C=BE/O=Auth0/OU=Auth0/CN=Auth0')
504
+ cert.subject = cert.issuer
505
+ cert.not_before = Time.now
506
+ cert.not_after = Time.now + 365 * 24 * 60 * 60
507
+ cert.public_key = private_key.public_key
508
+ cert.serial = 0x0
509
+ cert.version = 2
510
+
511
+ ef = OpenSSL::X509::ExtensionFactory.new
512
+ ef.subject_certificate = cert
513
+ ef.issuer_certificate = cert
514
+ cert.extensions = [
515
+ ef.create_extension('basicConstraints', 'CA:TRUE', true),
516
+ ef.create_extension('subjectKeyIdentifier', 'hash')
517
+ ]
518
+ cert.add_extension ef.create_extension(
519
+ 'authorityKeyIdentifier',
520
+ 'keyid:always,issuer:always'
521
+ )
522
+
523
+ cert.sign private_key, OpenSSL::Digest.new('SHA1')
348
524
  end
349
525
 
350
- it 'has credentials' do
351
- expect(subject['credentials']['token']).to eq(access_token)
352
- expect(subject['credentials']['expires']).to be true
353
- expect(subject['credentials']['expires_at']).to_not be_nil
354
- expect(subject['credentials']['id_token']).to eq(id_token)
526
+ def stub_auth(body, stubbed_jwt_token: true)
527
+ stub_request(:post, "#{domain_url}/oauth/token")
528
+ .with do |request|
529
+ params = URI.decode_www_form(request.body).to_h
530
+ token = params['client_assertion']
531
+
532
+ request.headers['Auth0-Client'] == telemetry_value &&
533
+ params['grant_type'] == described_class::AUTHORIZATION_CODE_GRANT_TYPE &&
534
+ params['client_id'] == client_id &&
535
+ params['client_assertion_type'] == described_class::CLIENT_ASSERTION_TYPE &&
536
+ (stubbed_jwt_token ? token == jwt_token : jwt_token?(token))
537
+ end
538
+ .to_return(
539
+ headers: { 'Content-Type' => 'application/json' },
540
+ body: MultiJson.encode(body)
541
+ )
355
542
  end
356
543
 
357
- it 'has basic values' do
358
- expect(subject['provider']).to eq('auth0')
359
- expect(subject['uid']).to eq(user_id)
544
+ def stub_expected_jwks
545
+ stub_request(:get, 'https://samples.auth0.com/.well-known/jwks.json')
546
+ .to_return(
547
+ headers: { 'Content-Type' => 'application/json' },
548
+ body: valid_jwks,
549
+ status: 200
550
+ )
360
551
  end
361
552
 
362
- it 'has info' do
363
- expect(subject['info']['name']).to eq(name)
364
- expect(subject['info']['nickname']).to eq(nickname)
365
- expect(subject['info']['image']).to eq(picture)
366
- expect(subject['info']['email']).to eq(email)
553
+ def stub_jwt_token(algorithm: client_assertion_signing_algorithm)
554
+ allow(OmniAuth::Auth0::JWTToken).to receive(:new)
555
+ .with(client_id,
556
+ domain_url,
557
+ client_assertion_signing_key,
558
+ algorithm)
559
+ .and_return(instance_double(OmniAuth::Auth0::JWTToken, jwt_token: jwt_token))
367
560
  end
368
561
 
369
- it 'has extra' do
370
- expect(subject['extra']['raw_info']['email_verified']).to be true
562
+ context 'basic oauth' do
563
+ before do
564
+ @app = make_application(client_secret: nil, client_assertion_signing_key: client_assertion_signing_key)
565
+ stub_jwt_token(algorithm: nil)
566
+ stub_auth(oauth_response)
567
+ stub_userinfo(basic_user_info)
568
+ trigger_callback
569
+ end
570
+
571
+ it_behaves_like 'basic oauth callback assertions'
572
+ end
573
+
574
+ context 'basic oath without stubbing jwt token' do
575
+ before do
576
+ @app = make_application(client_secret: nil, client_assertion_signing_key: client_assertion_signing_key)
577
+ stub_auth(oauth_response, stubbed_jwt_token: false)
578
+ stub_userinfo(basic_user_info)
579
+ trigger_callback
580
+ end
581
+
582
+ it_behaves_like 'basic oauth callback assertions'
583
+ end
584
+
585
+ context 'basic oauth w/refresh token' do
586
+ before do
587
+ @app = make_application(client_secret: nil,
588
+ client_assertion_signing_key: client_assertion_signing_key,
589
+ client_assertion_signing_algorithm: client_assertion_signing_algorithm)
590
+ stub_jwt_token
591
+ stub_auth(oauth_response.merge(refresh_token: refresh_token))
592
+ stub_userinfo(basic_user_info)
593
+ trigger_callback
594
+ end
595
+
596
+ it_behaves_like 'basic oauth refresh token callback assertions'
597
+ end
598
+
599
+ context 'oidc' do
600
+ before do
601
+ @app = make_application(client_secret: nil,
602
+ client_assertion_signing_key: client_assertion_signing_key,
603
+ client_assertion_signing_algorithm: client_assertion_signing_algorithm)
604
+ stub_jwt_token
605
+ stub_auth(oidc_response)
606
+ stub_expected_jwks
607
+ trigger_callback
608
+ end
609
+
610
+ it_behaves_like 'oidc callback assertions'
371
611
  end
372
612
  end
373
613
  end
374
614
  end
375
615
 
376
616
  describe 'error_handling' do
377
- it 'fails when missing client_id' do
617
+ it 'fails when missing client_id and client_assertion_signing_key' do
378
618
  @app = make_application(client_id: nil)
379
619
  get 'auth/auth0'
380
620
  expect(last_response.status).to eq(302)
@@ -382,7 +622,7 @@ describe OmniAuth::Strategies::Auth0 do
382
622
  expect(redirect_url).to fail_auth_with('missing_client_id')
383
623
  end
384
624
 
385
- it 'fails when missing client_secret' do
625
+ it 'fails when missing client_secret and client_assertion_signing_key' do
386
626
  @app = make_application(client_secret: nil)
387
627
  get 'auth/auth0'
388
628
  expect(last_response.status).to eq(302)
@@ -397,6 +637,14 @@ describe OmniAuth::Strategies::Auth0 do
397
637
  redirect_url = last_response.headers['Location']
398
638
  expect(redirect_url).to fail_auth_with('missing_domain')
399
639
  end
640
+
641
+ it 'fails when missing client_assertion_signing_key' do
642
+ @app = make_application(client_secret: nil, client_assertion_signing_key: nil)
643
+ get 'auth/auth0'
644
+ expect(last_response.status).to eq(302)
645
+ redirect_url = last_response.headers['Location']
646
+ expect(redirect_url).to fail_auth_with('missing_client_assertion_signing_key')
647
+ end
400
648
  end
401
649
  end
402
650