omniauth-globus 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,514 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'json'
5
+ require 'omniauth-globus'
6
+ require 'stringio'
7
+
8
+ describe OmniAuth::Strategies::Globus do
9
+ let(:request) { double('Request', params: {}, cookies: {}, env: {}) }
10
+ let(:app) do
11
+ lambda do
12
+ [200, {}, ['Hello.']]
13
+ end
14
+ end
15
+
16
+ subject do
17
+ OmniAuth::Strategies::Globus.new(app, 'appid', 'secret', @options || {}).tap do |strategy|
18
+ allow(strategy).to receive(:request) do
19
+ request
20
+ end
21
+ end
22
+ end
23
+
24
+ before do
25
+ OmniAuth.config.test_mode = true
26
+ end
27
+
28
+ after do
29
+ OmniAuth.config.test_mode = false
30
+ end
31
+
32
+ describe '#client_options' do
33
+ it 'has correct site' do
34
+ expect(subject.client.site).to eq('https://auth.globus.org')
35
+ end
36
+
37
+ it 'has correct authorize_url' do
38
+ expect(subject.client.options[:authorize_url]).to eq('https://auth.globus.org/v2/oauth2/authorize')
39
+ end
40
+
41
+ it 'has correct token_url' do
42
+ expect(subject.client.options[:token_url]).to eq('https://auth.globus.org/v2/oauth2/token')
43
+ end
44
+
45
+ describe 'overrides' do
46
+ context 'as strings' do
47
+ it 'should allow overriding the site' do
48
+ @options = { client_options: { 'site' => 'https://example.com' } }
49
+ expect(subject.client.site).to eq('https://example.com')
50
+ end
51
+
52
+ it 'should allow overriding the authorization_endpoint' do
53
+ @options = { client_options: { 'authorization_endpoint' => 'https://example.com' } }
54
+ expect(subject.client.options[:authorization_endpoint]).to eq('https://example.com')
55
+ end
56
+
57
+ it 'should allow overriding the token_endpoint' do
58
+ @options = { client_options: { 'token_endpoint' => 'https://example.com' } }
59
+ expect(subject.client.options[:token_endpoint]).to eq('https://example.com')
60
+ end
61
+ end
62
+
63
+ context 'as symbols' do
64
+ it 'should allow overriding the site' do
65
+ @options = { client_options: { site: 'https://example.com' } }
66
+ expect(subject.client.site).to eq('https://example.com')
67
+ end
68
+
69
+ it 'should allow overriding the authorization_endpoint' do
70
+ @options = { client_options: { authorization_endpoint: 'https://example.com' } }
71
+ expect(subject.client.options[:authorization_endpoint]).to eq('https://example.com')
72
+ end
73
+
74
+ it 'should allow overriding the token_endpoint' do
75
+ @options = { client_options: { token_endpoint: 'https://example.com' } }
76
+ expect(subject.client.options[:token_endpoint]).to eq('https://example.com')
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ describe '#authorize_options' do
83
+ %i[access_type login_hint prompt scope state device_id device_name].each do |k|
84
+ it "should support #{k}" do
85
+ @options = { k => 'http://someval' }
86
+ expect(subject.authorize_params[k.to_s]).to eq('http://someval')
87
+ end
88
+ end
89
+
90
+ describe 'redirect_uri' do
91
+ it 'should default to nil' do
92
+ @options = {}
93
+ expect(subject.authorize_params['redirect_uri']).to eq(nil)
94
+ end
95
+
96
+ it 'should set the redirect_uri parameter if present' do
97
+ @options = { redirect_uri: 'https://example.com' }
98
+ expect(subject.authorize_params['redirect_uri']).to eq('https://example.com')
99
+ end
100
+ end
101
+
102
+ describe 'access_type' do
103
+ it 'should default to "offline"' do
104
+ @options = {}
105
+ expect(subject.authorize_params['access_type']).to eq('offline')
106
+ end
107
+
108
+ it 'should set the access_type parameter if present' do
109
+ @options = { access_type: 'online' }
110
+ expect(subject.authorize_params['access_type']).to eq('online')
111
+ end
112
+ end
113
+
114
+ describe 'login_hint' do
115
+ it 'should default to nil' do
116
+ expect(subject.authorize_params['login_hint']).to eq(nil)
117
+ end
118
+
119
+ it 'should set the login_hint parameter if present' do
120
+ @options = { login_hint: 'john@example.com' }
121
+ expect(subject.authorize_params['login_hint']).to eq('john@example.com')
122
+ end
123
+ end
124
+
125
+ describe 'prompt' do
126
+ it 'should default to nil' do
127
+ expect(subject.authorize_params['prompt']).to eq(nil)
128
+ end
129
+
130
+ it 'should set the prompt parameter if present' do
131
+ @options = { prompt: 'consent select_account' }
132
+ expect(subject.authorize_params['prompt']).to eq('consent select_account')
133
+ end
134
+ end
135
+
136
+ describe 'request_visible_actions' do
137
+ it 'should default to nil' do
138
+ expect(subject.authorize_params['request_visible_actions']).to eq(nil)
139
+ end
140
+
141
+ it 'should set the request_visible_actions parameter if present' do
142
+ @options = { request_visible_actions: 'something' }
143
+ expect(subject.authorize_params['request_visible_actions']).to eq('something')
144
+ end
145
+ end
146
+
147
+ describe 'include_granted_scopes' do
148
+ it 'should default to nil' do
149
+ expect(subject.authorize_params['include_granted_scopes']).to eq(nil)
150
+ end
151
+
152
+ it 'should set the include_granted_scopes parameter if present' do
153
+ @options = { include_granted_scopes: 'true' }
154
+ expect(subject.authorize_params['include_granted_scopes']).to eq('true')
155
+ end
156
+ end
157
+
158
+ describe 'scope' do
159
+ it 'should leave base scopes as is' do
160
+ @options = { scope: 'profile' }
161
+ expect(subject.authorize_params['scope']).to eq('profile')
162
+ end
163
+
164
+ it 'should join scopes' do
165
+ @options = { scope: 'profile,email' }
166
+ expect(subject.authorize_params['scope']).to eq('profile email')
167
+ end
168
+
169
+ it 'should deal with whitespace when joining scopes' do
170
+ @options = { scope: 'profile, email' }
171
+ expect(subject.authorize_params['scope']).to eq('profile email')
172
+ end
173
+
174
+ it 'should set default scope to openid, profile, email' do
175
+ expect(subject.authorize_params['scope']).to eq("openid profile email")
176
+ end
177
+
178
+ it 'should support space delimited scopes' do
179
+ @options = { scope: 'profile email' }
180
+ expect(subject.authorize_params['scope']).to eq('profile email')
181
+ end
182
+
183
+ it 'should support extremely badly formed scopes' do
184
+ @options = { scope: 'profile email,foo,steve yeah' }
185
+ expect(subject.authorize_params['scope']).to eq('profile email foo steve yeah')
186
+ end
187
+ end
188
+
189
+ describe 'state' do
190
+ it 'should set the state parameter' do
191
+ @options = { state: 'some_state' }
192
+ expect(subject.authorize_params['state']).to eq('some_state')
193
+ expect(subject.authorize_params[:state]).to eq('some_state')
194
+ expect(subject.session['omniauth.state']).to eq('some_state')
195
+ end
196
+
197
+ it 'should set the omniauth.state dynamically' do
198
+ allow(subject).to receive(:request) { double('Request', params: { 'state' => 'some_state' }, env: {}) }
199
+ expect(subject.authorize_params['state']).to eq('some_state')
200
+ expect(subject.authorize_params[:state]).to eq('some_state')
201
+ expect(subject.session['omniauth.state']).to eq('some_state')
202
+ end
203
+ end
204
+
205
+ describe 'overrides' do
206
+ it 'should include top-level options that are marked as :authorize_options' do
207
+ @options = { authorize_options: %i[scope foo request_visible_actions], scope: 'http://bar', foo: 'baz', request_visible_actions: 'something' }
208
+ expect(subject.authorize_params['scope']).to eq('http://bar')
209
+ expect(subject.authorize_params['foo']).to eq('baz')
210
+ expect(subject.authorize_params['request_visible_actions']).to eq('something')
211
+ end
212
+
213
+ describe 'request overrides' do
214
+ %i[access_type login_hint prompt scope state].each do |k|
215
+ context "authorize option #{k}" do
216
+ let(:request) { double('Request', params: { k.to_s => 'http://example.com' }, cookies: {}, env: {}) }
217
+
218
+ it "should set the #{k} authorize option dynamically in the request" do
219
+ @options = { k: '' }
220
+ expect(subject.authorize_params[k.to_s]).to eq('http://example.com')
221
+ end
222
+ end
223
+ end
224
+
225
+ describe 'custom authorize_options' do
226
+ let(:request) { double('Request', params: { 'foo' => 'something' }, cookies: {}, env: {}) }
227
+
228
+ it 'should support request overrides from custom authorize_options' do
229
+ @options = { authorize_options: [:foo], foo: '' }
230
+ expect(subject.authorize_params['foo']).to eq('something')
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ describe '#authorize_params' do
238
+ it 'should include any authorize params passed in the :authorize_params option' do
239
+ @options = { authorize_params: { request_visible_actions: 'something', foo: 'bar', baz: 'zip' }, bad: 'not_included' }
240
+ expect(subject.authorize_params['request_visible_actions']).to eq('something')
241
+ expect(subject.authorize_params['foo']).to eq('bar')
242
+ expect(subject.authorize_params['baz']).to eq('zip')
243
+ expect(subject.authorize_params['bad']).to eq(nil)
244
+ end
245
+ end
246
+
247
+ describe '#token_params' do
248
+ it 'should include any token params passed in the :token_params option' do
249
+ @options = { token_params: { foo: 'bar', baz: 'zip' } }
250
+ expect(subject.token_params['foo']).to eq('bar')
251
+ expect(subject.token_params['baz']).to eq('zip')
252
+ end
253
+ end
254
+
255
+ describe '#token_options' do
256
+ it 'should include top-level options that are marked as :token_options' do
257
+ @options = { token_options: %i[scope foo], scope: 'bar', foo: 'baz', bad: 'not_included' }
258
+ expect(subject.token_params['scope']).to eq('bar')
259
+ expect(subject.token_params['foo']).to eq('baz')
260
+ expect(subject.token_params['bad']).to eq(nil)
261
+ end
262
+ end
263
+
264
+ describe '#callback_path' do
265
+ it 'has the correct default callback path' do
266
+ expect(subject.callback_path).to eq("/auth/globus/callback")
267
+ end
268
+
269
+ it 'should set the callback_path parameter if present' do
270
+ @options = { callback_path: '/auth/foo/callback' }
271
+ expect(subject.callback_path).to eq('/auth/foo/callback')
272
+ end
273
+ end
274
+
275
+ describe '#info' do
276
+ let(:client) do
277
+ OAuth2::Client.new('abc', 'def') do |builder|
278
+ builder.request :url_encoded
279
+ builder.adapter :test do |stub|
280
+ stub.get("/v2/oauth2/userinfo") { [200, { "content-type" => "application/json" }, response_hash.to_json] }
281
+ end
282
+ end
283
+ end
284
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, {}) }
285
+ before { allow(subject).to receive(:access_token).and_return(access_token) }
286
+
287
+ context 'with email' do
288
+ let(:response_hash) do
289
+ { email: 'something@domain.invalid' }
290
+ end
291
+
292
+ it 'should return email' do
293
+ expect(subject.info[:email]).to eq('something@domain.invalid')
294
+ end
295
+ end
296
+ end
297
+
298
+ describe '#extra' do
299
+ let(:client) do
300
+ OAuth2::Client.new('abc', 'def') do |builder|
301
+ builder.request :url_encoded
302
+ builder.adapter :test do |stub|
303
+ stub.get('/v2/oauth2/userinfo') { [200, { 'content-type' => 'application/json' }, '{"sub": "12345"}'] }
304
+ end
305
+ end
306
+ end
307
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, {}) }
308
+
309
+ before { allow(subject).to receive(:access_token).and_return(access_token) }
310
+
311
+ describe 'id_token' do
312
+ shared_examples 'id_token issued by valid issuer' do |issuer| # rubocop:disable Metrics/BlockLength
313
+ context 'when the id_token is passed into the access token' do
314
+ let(:token_info) do
315
+ {
316
+ identity_provider_display_name: "ORCID",
317
+ sub: "abc",
318
+ preferred_username: "0000-0003-1419-2405@orcid.org",
319
+ identity_provider: "def",
320
+ organization: "DataCite",
321
+ email: "mfenner@datacite.org",
322
+ name: "Martin Fenner",
323
+ exp: Time.now.to_i + 3600,
324
+ iss: "https://auth.globus.org"
325
+ }
326
+ end
327
+ let(:id_token) { JWT.encode(token_info, 'secret') }
328
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, 'id_token' => id_token) }
329
+
330
+ it 'should include id_token when set on the access_token' do
331
+ expect(subject.extra).to include(id_token: id_token)
332
+ end
333
+ end
334
+ end
335
+
336
+ it_behaves_like 'id_token issued by valid issuer', 'https://auth.globus.org'
337
+
338
+ context 'when the id_token is issued by an invalid issuer' do
339
+ let(:token_info) do
340
+ {
341
+ identity_provider_display_name: "ORCID",
342
+ sub: "abc",
343
+ preferred_username: "0000-0003-1419-2405@orcid.org",
344
+ identity_provider: "def",
345
+ organization: "DataCite",
346
+ email: "mfenner@datacite.org",
347
+ name: "Martin Fenner",
348
+ exp: Time.now.to_i + 3600,
349
+ iss: "https://fake.globus.org"
350
+ }
351
+ end
352
+ let(:id_token) { JWT.encode(token_info, 'secret') }
353
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, 'id_token' => id_token) }
354
+
355
+ it 'raises JWT::InvalidIssuerError' do
356
+ expect { subject.extra }.to raise_error(JWT::InvalidIssuerError)
357
+ end
358
+ end
359
+
360
+ context 'when the id_token is missing' do
361
+ it 'should not include id_token' do
362
+ expect(subject.extra).not_to have_key(:id_token)
363
+ end
364
+
365
+ it 'should not include id_info' do
366
+ expect(subject.extra).not_to have_key(:id_info)
367
+ end
368
+ end
369
+ end
370
+
371
+ describe 'raw_info' do
372
+ context 'when skip_info is true' do
373
+ before { subject.options[:skip_info] = true }
374
+
375
+ it 'should not include raw_info' do
376
+ expect(subject.extra).not_to have_key(:raw_info)
377
+ end
378
+ end
379
+
380
+ context 'when skip_info is false' do
381
+ before { subject.options[:skip_info] = false }
382
+
383
+ it 'should include raw_info' do
384
+ expect(subject.extra[:raw_info]).to eq('sub' => '12345')
385
+ end
386
+ end
387
+ end
388
+ end
389
+
390
+ describe 'build_access_token' do
391
+ it 'should use a hybrid authorization request_uri if this is an AJAX request with a code parameter' do
392
+ allow(request).to receive(:xhr?).and_return(true)
393
+ allow(request).to receive(:params).and_return('code' => 'valid_code')
394
+
395
+ client = double(:client)
396
+ auth_code = double(:auth_code)
397
+ allow(client).to receive(:auth_code).and_return(auth_code)
398
+ expect(subject).to receive(:client).and_return(client)
399
+ expect(auth_code).to receive(:get_token).with('valid_code', { redirect_uri: 'postmessage' }, {})
400
+
401
+ expect(subject).not_to receive(:orig_build_access_token)
402
+ subject.build_access_token
403
+ end
404
+
405
+ it 'should use a hybrid authorization request_uri if this is an AJAX request (mobile) with a code parameter' do
406
+ allow(request).to receive(:xhr?).and_return(true)
407
+ allow(request).to receive(:params).and_return('code' => 'valid_code', 'redirect_uri' => '')
408
+
409
+ client = double(:client)
410
+ auth_code = double(:auth_code)
411
+ allow(client).to receive(:auth_code).and_return(auth_code)
412
+ expect(subject).to receive(:client).and_return(client)
413
+ expect(auth_code).to receive(:get_token).with('valid_code', { redirect_uri: '' }, {})
414
+
415
+ expect(subject).not_to receive(:orig_build_access_token)
416
+ subject.build_access_token
417
+ end
418
+
419
+ it 'should use the request_uri from params if this not an AJAX request (request from installed app) with a code parameter' do
420
+ allow(request).to receive(:xhr?).and_return(false)
421
+ allow(request).to receive(:params).and_return('code' => 'valid_code', 'redirect_uri' => 'redirect_uri')
422
+
423
+ client = double(:client)
424
+ auth_code = double(:auth_code)
425
+ allow(client).to receive(:auth_code).and_return(auth_code)
426
+ expect(subject).to receive(:client).and_return(client)
427
+ expect(auth_code).to receive(:get_token).with('valid_code', { redirect_uri: 'redirect_uri' }, {})
428
+
429
+ expect(subject).not_to receive(:orig_build_access_token)
430
+ subject.build_access_token
431
+ end
432
+
433
+ it 'should read access_token from hash if this is not an AJAX request with a code parameter' do
434
+ allow(request).to receive(:xhr?).and_return(false)
435
+ allow(request).to receive(:params).and_return('access_token' => 'valid_access_token')
436
+ expect(subject).to receive(:verify_token).with('valid_access_token').and_return true
437
+ expect(subject).to receive(:client).and_return(:client)
438
+
439
+ token = subject.build_access_token
440
+ expect(token).to be_instance_of(::OAuth2::AccessToken)
441
+ expect(token.token).to eq('valid_access_token')
442
+ expect(token.client).to eq(:client)
443
+ end
444
+
445
+ it 'reads the code from a json request body' do
446
+ body = StringIO.new(%({"code":"json_access_token"}))
447
+ client = double(:client)
448
+ auth_code = double(:auth_code)
449
+
450
+ allow(request).to receive(:xhr?).and_return(false)
451
+ allow(request).to receive(:content_type).and_return('application/json')
452
+ allow(request).to receive(:body).and_return(body)
453
+ allow(client).to receive(:auth_code).and_return(auth_code)
454
+ expect(subject).to receive(:client).and_return(client)
455
+
456
+ expect(auth_code).to receive(:get_token).with('json_access_token', { redirect_uri: 'postmessage' }, {})
457
+
458
+ subject.build_access_token
459
+ end
460
+
461
+ it 'should use callback_url without query_string if this is not an AJAX request' do
462
+ allow(request).to receive(:xhr?).and_return(false)
463
+ allow(request).to receive(:params).and_return('code' => 'valid_code')
464
+ allow(request).to receive(:content_type).and_return('application/x-www-form-urlencoded')
465
+
466
+ client = double(:client)
467
+ auth_code = double(:auth_code)
468
+ allow(client).to receive(:auth_code).and_return(auth_code)
469
+ allow(subject).to receive(:callback_url).and_return('redirect_uri_without_query_string')
470
+
471
+ expect(subject).to receive(:client).and_return(client)
472
+ expect(auth_code).to receive(:get_token).with('valid_code', { redirect_uri: 'redirect_uri_without_query_string' }, {})
473
+ subject.build_access_token
474
+ end
475
+ end
476
+
477
+ describe 'verify_token' do
478
+ before(:each) do
479
+ subject.options.client_options[:connection_build] = proc do |builder|
480
+ builder.request :url_encoded
481
+ builder.adapter :test do |stub|
482
+ stub.get('/v2/oauth2/userinfo?access_token=valid_access_token') do
483
+ [200, { 'Content-Type' => 'application/json; charset=UTF-8' }, JSON.dump(
484
+ {
485
+ identity_provider_display_name: "ORCID",
486
+ sub: "abc",
487
+ preferred_username: "0000-0003-1419-2405@orcid.org",
488
+ identity_provider: "def",
489
+ organization: "DataCite",
490
+ email: "mfenner@datacite.org",
491
+ name: "Martin Fenner",
492
+ exp: 1568009648,
493
+ iss: "https://auth.globus.org"
494
+ }
495
+ )]
496
+ end
497
+ stub.get('/v2/oauth2/userinfo?access_token=invalid_access_token') do
498
+ [400, { 'Content-Type' => 'application/json; charset=UTF-8' }, JSON.dump(error_description: 'Invalid Value')]
499
+ end
500
+ end
501
+ end
502
+ end
503
+
504
+ # it 'should verify token if access_token is valid' do
505
+ # expect(subject.send(:verify_token, 'valid_access_token')).to eq(true)
506
+ # end
507
+
508
+ it 'should raise error if access_token is invalid' do
509
+ expect do
510
+ subject.send(:verify_token, 'invalid_access_token')
511
+ end.to raise_error(OAuth2::Error)
512
+ end
513
+ end
514
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'spec_helper'
4
+
5
+ describe 'Rubocop' do
6
+ it 'should pass with no offenses detected' do
7
+ expect(`rubocop`).to include('no offenses detected')
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ Bundler.setup
5
+
6
+ require "simplecov"
7
+ SimpleCov.start
8
+
9
+ require "rspec"
10
+ require "rack/test"
11
+ require "webmock/rspec"
12
+ require "omniauth-globus"
13
+
14
+ def fixture_path
15
+ File.expand_path("fixtures", __dir__) + "/"
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.include WebMock::API
20
+ config.include Rack::Test::Methods
21
+ config.extend OmniAuth::Test::StrategyMacros, type: :strategy
22
+ config.expect_with :rspec do |c|
23
+ c.syntax = :expect
24
+ end
25
+ end