omniauth-auth0 3.1.1 → 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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -1
- data/.github/actions/get-prerelease/action.yml +30 -0
- data/.github/actions/get-release-notes/action.yml +42 -0
- data/.github/actions/get-version/action.yml +21 -0
- data/.github/actions/release-create/action.yml +47 -0
- data/.github/actions/rl-scanner/action.yml +71 -0
- data/.github/actions/rubygems-publish/action.yml +30 -0
- data/.github/actions/setup/action.yml +28 -0
- data/.github/actions/tag-exists/action.yml +36 -0
- data/.github/dependabot.yml +13 -0
- data/.github/workflows/codeql.yml +53 -0
- data/.github/workflows/matrix.json +7 -0
- data/.github/workflows/publish.yml +33 -0
- data/.github/workflows/rl-scanner.yml +65 -0
- data/.github/workflows/ruby-release.yml +72 -0
- data/.github/workflows/snyk.yml +40 -0
- data/.github/workflows/test.yml +69 -0
- data/.shiprc +2 -1
- data/.version +1 -0
- data/CHANGELOG.md +10 -5
- data/Gemfile +1 -2
- data/Gemfile.lock +117 -84
- data/README.md +42 -1
- data/lib/omniauth/auth0/jwt_token.rb +38 -0
- data/lib/omniauth/auth0/jwt_validator.rb +2 -2
- data/lib/omniauth/strategies/auth0.rb +48 -14
- data/lib/omniauth-auth0/version.rb +1 -1
- data/omniauth-auth0.gemspec +1 -0
- data/spec/omniauth/auth0/jwt_token_spec.rb +87 -0
- data/spec/omniauth/strategies/auth0_spec.rb +478 -230
- metadata +39 -9
- data/.circleci/config.yml +0 -63
- data/.gemrelease +0 -2
- 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
|
-
|
|
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
|
-
|
|
36
|
-
domain_url
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
59
|
-
expect(subject.options[:userinfo_url]).to eq('/userinfo')
|
|
60
|
-
end
|
|
286
|
+
it_behaves_like 'client_options with valid configuration'
|
|
61
287
|
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
295
|
+
context 'when using client_secret authentication' do
|
|
296
|
+
let(:subject) { auth0.options }
|
|
69
297
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
298
|
+
it 'should have the correct client_id' do
|
|
299
|
+
expect(subject[:client_id]).to eq(client_id)
|
|
300
|
+
end
|
|
73
301
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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 '
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
301
|
-
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
317
|
-
|
|
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 '
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
329
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
347
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
370
|
-
|
|
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
|
|