oauth2 1.4.4 → 1.4.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -2
  3. data/CODE_OF_CONDUCT.md +105 -46
  4. data/LICENSE +1 -1
  5. data/README.md +277 -112
  6. data/lib/oauth2/access_token.rb +8 -7
  7. data/lib/oauth2/authenticator.rb +1 -1
  8. data/lib/oauth2/client.rb +64 -21
  9. data/lib/oauth2/error.rb +1 -1
  10. data/lib/oauth2/mac_token.rb +16 -10
  11. data/lib/oauth2/response.rb +5 -3
  12. data/lib/oauth2/strategy/assertion.rb +3 -3
  13. data/lib/oauth2/strategy/password.rb +2 -2
  14. data/lib/oauth2/version.rb +9 -3
  15. data/spec/helper.rb +30 -0
  16. data/spec/oauth2/access_token_spec.rb +216 -0
  17. data/spec/oauth2/authenticator_spec.rb +84 -0
  18. data/spec/oauth2/client_spec.rb +530 -0
  19. data/spec/oauth2/mac_token_spec.rb +120 -0
  20. data/spec/oauth2/response_spec.rb +90 -0
  21. data/spec/oauth2/strategy/assertion_spec.rb +59 -0
  22. data/spec/oauth2/strategy/auth_code_spec.rb +107 -0
  23. data/spec/oauth2/strategy/base_spec.rb +5 -0
  24. data/spec/oauth2/strategy/client_credentials_spec.rb +69 -0
  25. data/spec/oauth2/strategy/implicit_spec.rb +26 -0
  26. data/spec/oauth2/strategy/password_spec.rb +56 -0
  27. data/spec/oauth2/version_spec.rb +23 -0
  28. metadata +41 -57
  29. data/.document +0 -5
  30. data/.gitignore +0 -19
  31. data/.jrubyrc +0 -1
  32. data/.rspec +0 -2
  33. data/.rubocop.yml +0 -80
  34. data/.rubocop_rspec.yml +0 -26
  35. data/.rubocop_todo.yml +0 -15
  36. data/.ruby-version +0 -1
  37. data/.travis.yml +0 -87
  38. data/CONTRIBUTING.md +0 -18
  39. data/Gemfile +0 -40
  40. data/Rakefile +0 -45
  41. data/gemfiles/jruby_1.7.gemfile +0 -11
  42. data/gemfiles/jruby_9.0.gemfile +0 -7
  43. data/gemfiles/jruby_9.1.gemfile +0 -3
  44. data/gemfiles/jruby_9.2.gemfile +0 -3
  45. data/gemfiles/jruby_head.gemfile +0 -3
  46. data/gemfiles/ruby_1.9.gemfile +0 -11
  47. data/gemfiles/ruby_2.0.gemfile +0 -6
  48. data/gemfiles/ruby_2.1.gemfile +0 -6
  49. data/gemfiles/ruby_2.2.gemfile +0 -3
  50. data/gemfiles/ruby_2.3.gemfile +0 -3
  51. data/gemfiles/ruby_2.4.gemfile +0 -3
  52. data/gemfiles/ruby_2.5.gemfile +0 -3
  53. data/gemfiles/ruby_2.6.gemfile +0 -9
  54. data/gemfiles/ruby_2.7.gemfile +0 -9
  55. data/gemfiles/ruby_head.gemfile +0 -9
  56. data/gemfiles/truffleruby.gemfile +0 -3
  57. data/oauth2.gemspec +0 -52
@@ -0,0 +1,530 @@
1
+ # coding: utf-8
2
+
3
+ require 'helper'
4
+ require 'nkf'
5
+
6
+ describe OAuth2::Client do
7
+ subject do
8
+ described_class.new('abc', 'def', :site => 'https://api.example.com') do |builder|
9
+ builder.adapter :test do |stub|
10
+ stub.get('/success') { |env| [200, {'Content-Type' => 'text/awesome'}, 'yay'] }
11
+ stub.get('/reflect') { |env| [200, {}, env[:body]] }
12
+ stub.post('/reflect') { |env| [200, {}, env[:body]] }
13
+ stub.get('/unauthorized') { |env| [401, {'Content-Type' => 'application/json'}, MultiJson.encode(:error => error_value, :error_description => error_description_value)] }
14
+ stub.get('/conflict') { |env| [409, {'Content-Type' => 'text/plain'}, 'not authorized'] }
15
+ stub.get('/redirect') { |env| [302, {'Content-Type' => 'text/plain', 'location' => '/success'}, ''] }
16
+ stub.post('/redirect') { |env| [303, {'Content-Type' => 'text/plain', 'location' => '/reflect'}, ''] }
17
+ stub.get('/error') { |env| [500, {'Content-Type' => 'text/plain'}, 'unknown error'] }
18
+ stub.get('/empty_get') { |env| [204, {}, nil] }
19
+ stub.get('/different_encoding') { |env| [500, {'Content-Type' => 'application/json'}, NKF.nkf('-We', MultiJson.encode(:error => error_value, :error_description => '∞'))] }
20
+ stub.get('/ascii_8bit_encoding') { |env| [500, {'Content-Type' => 'application/json'}, MultiJson.encode(:error => 'invalid_request', :error_description => 'é').force_encoding('ASCII-8BIT')] }
21
+ end
22
+ end
23
+ end
24
+
25
+ let!(:error_value) { 'invalid_token' }
26
+ let!(:error_description_value) { 'bad bad token' }
27
+
28
+ describe '#initialize' do
29
+ it 'assigns id and secret' do
30
+ expect(subject.id).to eq('abc')
31
+ expect(subject.secret).to eq('def')
32
+ end
33
+
34
+ it 'assigns site from the options hash' do
35
+ expect(subject.site).to eq('https://api.example.com')
36
+ end
37
+
38
+ it 'assigns Faraday::Connection#host' do
39
+ expect(subject.connection.host).to eq('api.example.com')
40
+ end
41
+
42
+ it 'leaves Faraday::Connection#ssl unset' do
43
+ expect(subject.connection.ssl).to be_empty
44
+ end
45
+
46
+ it 'is able to pass a block to configure the connection' do
47
+ connection = double('connection')
48
+ builder = double('builder')
49
+ allow(Faraday).to receive(:new).and_yield(builder)
50
+ allow(Faraday::Connection).to receive(:new).and_return(connection)
51
+
52
+ expect(builder).to receive(:adapter).with(:test)
53
+
54
+ described_class.new('abc', 'def') do |client|
55
+ client.adapter :test
56
+ end.connection
57
+ end
58
+
59
+ it 'defaults raise_errors to true' do
60
+ expect(subject.options[:raise_errors]).to be true
61
+ end
62
+
63
+ it 'allows true/false for raise_errors option' do
64
+ client = described_class.new('abc', 'def', :site => 'https://api.example.com', :raise_errors => false)
65
+ expect(client.options[:raise_errors]).to be false
66
+ client = described_class.new('abc', 'def', :site => 'https://api.example.com', :raise_errors => true)
67
+ expect(client.options[:raise_errors]).to be true
68
+ end
69
+
70
+ it 'allows override of raise_errors option' do
71
+ client = described_class.new('abc', 'def', :site => 'https://api.example.com', :raise_errors => true) do |builder|
72
+ builder.adapter :test do |stub|
73
+ stub.get('/notfound') { |env| [404, {}, nil] }
74
+ end
75
+ end
76
+ expect(client.options[:raise_errors]).to be true
77
+ expect { client.request(:get, '/notfound') }.to raise_error(OAuth2::Error)
78
+ response = client.request(:get, '/notfound', :raise_errors => false)
79
+ expect(response.status).to eq(404)
80
+ end
81
+
82
+ it 'allows get/post for access_token_method option' do
83
+ client = described_class.new('abc', 'def', :site => 'https://api.example.com', :access_token_method => :get)
84
+ expect(client.options[:access_token_method]).to eq(:get)
85
+ client = described_class.new('abc', 'def', :site => 'https://api.example.com', :access_token_method => :post)
86
+ expect(client.options[:access_token_method]).to eq(:post)
87
+ end
88
+
89
+ it 'does not mutate the opts hash argument' do
90
+ opts = {:site => 'http://example.com/'}
91
+ opts2 = opts.dup
92
+ described_class.new 'abc', 'def', opts
93
+ expect(opts).to eq(opts2)
94
+ end
95
+ end
96
+
97
+ %w[authorize token].each do |url_type|
98
+ describe ":#{url_type}_url option" do
99
+ it "defaults to a path of /oauth/#{url_type}" do
100
+ expect(subject.send("#{url_type}_url")).to eq("https://api.example.com/oauth/#{url_type}")
101
+ end
102
+
103
+ it "is settable via the :#{url_type}_url option" do
104
+ subject.options[:"#{url_type}_url"] = '/oauth/custom'
105
+ expect(subject.send("#{url_type}_url")).to eq('https://api.example.com/oauth/custom')
106
+ end
107
+
108
+ it 'allows a different host than the site' do
109
+ subject.options[:"#{url_type}_url"] = 'https://api.foo.com/oauth/custom'
110
+ expect(subject.send("#{url_type}_url")).to eq('https://api.foo.com/oauth/custom')
111
+ end
112
+ end
113
+ end
114
+
115
+ describe ':redirect_uri option' do
116
+ let(:auth_code_params) do
117
+ {
118
+ 'client_id' => 'abc',
119
+ 'client_secret' => 'def',
120
+ 'code' => 'code',
121
+ 'grant_type' => 'authorization_code',
122
+ }
123
+ end
124
+
125
+ context 'when blank' do
126
+ it 'there is no redirect_uri param added to authorization URL' do
127
+ expect(subject.authorize_url('a' => 'b')).to eq('https://api.example.com/oauth/authorize?a=b')
128
+ end
129
+
130
+ it 'does not add the redirect_uri param to the auth_code token exchange request' do
131
+ client = described_class.new('abc', 'def', :site => 'https://api.example.com') do |builder|
132
+ builder.adapter :test do |stub|
133
+ stub.post('/oauth/token', auth_code_params) do
134
+ [200, {'Content-Type' => 'application/json'}, '{"access_token":"token"}']
135
+ end
136
+ end
137
+ end
138
+ client.auth_code.get_token('code')
139
+ end
140
+ end
141
+
142
+ context 'when set' do
143
+ before { subject.options[:redirect_uri] = 'https://site.com/oauth/callback' }
144
+
145
+ it 'adds the redirect_uri param to authorization URL' do
146
+ expect(subject.authorize_url('a' => 'b')).to eq('https://api.example.com/oauth/authorize?a=b&redirect_uri=https%3A%2F%2Fsite.com%2Foauth%2Fcallback')
147
+ end
148
+
149
+ it 'adds the redirect_uri param to the auth_code token exchange request' do
150
+ client = described_class.new('abc', 'def', :redirect_uri => 'https://site.com/oauth/callback', :site => 'https://api.example.com') do |builder|
151
+ builder.adapter :test do |stub|
152
+ stub.post('/oauth/token', auth_code_params.merge('redirect_uri' => 'https://site.com/oauth/callback')) do
153
+ [200, {'Content-Type' => 'application/json'}, '{"access_token":"token"}']
154
+ end
155
+ end
156
+ end
157
+ client.auth_code.get_token('code')
158
+ end
159
+ end
160
+
161
+ describe 'custom headers' do
162
+ context 'string key headers' do
163
+ it 'adds the custom headers to request' do
164
+ client = described_class.new('abc', 'def', :site => 'https://api.example.com', :auth_scheme => :request_body) do |builder|
165
+ builder.adapter :test do |stub|
166
+ stub.post('/oauth/token') do |env|
167
+ expect(env.request_headers).to include({'CustomHeader' => 'CustomHeader'})
168
+ [200, {'Content-Type' => 'application/json'}, '{"access_token":"token"}']
169
+ end
170
+ end
171
+ end
172
+ header_params = {'headers' => {'CustomHeader' => 'CustomHeader'}}
173
+ client.auth_code.get_token('code', header_params)
174
+ end
175
+ end
176
+
177
+ context 'symbol key headers' do
178
+ it 'adds the custom headers to request' do
179
+ client = described_class.new('abc', 'def', :site => 'https://api.example.com', :auth_scheme => :request_body) do |builder|
180
+ builder.adapter :test do |stub|
181
+ stub.post('/oauth/token') do |env|
182
+ expect(env.request_headers).to include({'CustomHeader' => 'CustomHeader'})
183
+ [200, {'Content-Type' => 'application/json'}, '{"access_token":"token"}']
184
+ end
185
+ end
186
+ end
187
+ header_params = {:headers => {'CustomHeader' => 'CustomHeader'}}
188
+ client.auth_code.get_token('code', header_params)
189
+ end
190
+ end
191
+
192
+ context 'string key custom headers with basic auth' do
193
+ it 'adds the custom headers to request' do
194
+ client = described_class.new('abc', 'def', :site => 'https://api.example.com') do |builder|
195
+ builder.adapter :test do |stub|
196
+ stub.post('/oauth/token') do |env|
197
+ expect(env.request_headers).to include({'CustomHeader' => 'CustomHeader'})
198
+ [200, {'Content-Type' => 'application/json'}, '{"access_token":"token"}']
199
+ end
200
+ end
201
+ end
202
+ header_params = {'headers' => {'CustomHeader' => 'CustomHeader'}}
203
+ client.auth_code.get_token('code', header_params)
204
+ end
205
+ end
206
+
207
+ context 'symbol key custom headers with basic auth' do
208
+ it 'adds the custom headers to request' do
209
+ client = described_class.new('abc', 'def', :site => 'https://api.example.com') do |builder|
210
+ builder.adapter :test do |stub|
211
+ stub.post('/oauth/token') do |env|
212
+ expect(env.request_headers).to include({'CustomHeader' => 'CustomHeader'})
213
+ [200, {'Content-Type' => 'application/json'}, '{"access_token":"token"}']
214
+ end
215
+ end
216
+ end
217
+ header_params = {:headers => {'CustomHeader' => 'CustomHeader'}}
218
+ client.auth_code.get_token('code', header_params)
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ describe '#request' do
225
+ it 'works with a null response body' do
226
+ expect(subject.request(:get, 'empty_get').body).to eq('')
227
+ end
228
+
229
+ it 'returns on a successful response' do
230
+ response = subject.request(:get, '/success')
231
+ expect(response.body).to eq('yay')
232
+ expect(response.status).to eq(200)
233
+ expect(response.headers).to eq('Content-Type' => 'text/awesome')
234
+ end
235
+
236
+ it 'posts a body' do
237
+ response = subject.request(:post, '/reflect', :body => 'foo=bar')
238
+ expect(response.body).to eq('foo=bar')
239
+ end
240
+
241
+ it 'follows redirects properly' do
242
+ response = subject.request(:get, '/redirect')
243
+ expect(response.body).to eq('yay')
244
+ expect(response.status).to eq(200)
245
+ expect(response.headers).to eq('Content-Type' => 'text/awesome')
246
+ end
247
+
248
+ it 'redirects using GET on a 303' do
249
+ response = subject.request(:post, '/redirect', :body => 'foo=bar')
250
+ expect(response.body).to be_empty
251
+ expect(response.status).to eq(200)
252
+ end
253
+
254
+ it 'obeys the :max_redirects option' do
255
+ max_redirects = subject.options[:max_redirects]
256
+ subject.options[:max_redirects] = 0
257
+ response = subject.request(:get, '/redirect')
258
+ expect(response.status).to eq(302)
259
+ subject.options[:max_redirects] = max_redirects
260
+ end
261
+
262
+ it 'returns if raise_errors is false' do
263
+ subject.options[:raise_errors] = false
264
+ response = subject.request(:get, '/unauthorized')
265
+
266
+ expect(response.status).to eq(401)
267
+ expect(response.headers).to eq('Content-Type' => 'application/json')
268
+ expect(response.error).not_to be_nil
269
+ end
270
+
271
+ %w[/unauthorized /conflict /error /different_encoding /ascii_8bit_encoding].each do |error_path|
272
+ it "raises OAuth2::Error on error response to path #{error_path}" do
273
+ expect { subject.request(:get, error_path) }.to raise_error(OAuth2::Error)
274
+ end
275
+ end
276
+
277
+ # rubocop:disable Style/RedundantBegin
278
+ it 're-encodes response body in the error message' do
279
+ begin
280
+ subject.request(:get, '/ascii_8bit_encoding')
281
+ rescue StandardError => e
282
+ expect(e.message.encoding.name).to eq('UTF-8')
283
+ expect(e.message).to eq("invalid_request: é\n{\"error\":\"invalid_request\",\"error_description\":\"��\"}")
284
+ end
285
+ end
286
+
287
+ it 'parses OAuth2 standard error response' do
288
+ begin
289
+ subject.request(:get, '/unauthorized')
290
+ rescue StandardError => e
291
+ expect(e.code).to eq(error_value)
292
+ expect(e.description).to eq(error_description_value)
293
+ expect(e.to_s).to match(/#{error_value}/)
294
+ expect(e.to_s).to match(/#{error_description_value}/)
295
+ end
296
+ end
297
+
298
+ it 'provides the response in the Exception' do
299
+ begin
300
+ subject.request(:get, '/error')
301
+ rescue StandardError => e
302
+ expect(e.response).not_to be_nil
303
+ expect(e.to_s).to match(/unknown error/)
304
+ end
305
+ end
306
+ # rubocop:enable Style/RedundantBegin
307
+
308
+ context 'with ENV' do
309
+ include_context 'with stubbed env'
310
+ before do
311
+ stub_env('OAUTH_DEBUG' => 'true')
312
+ end
313
+
314
+ it 'outputs to $stdout when OAUTH_DEBUG=true' do
315
+ output = capture(:stdout) do
316
+ subject.request(:get, '/success')
317
+ end
318
+ logs = [
319
+ '-- request: GET https://api.example.com/success',
320
+ '-- response: Status 200',
321
+ '-- response: Content-Type: "text/awesome"',
322
+ ]
323
+ expect(output).to include(*logs)
324
+ end
325
+ end
326
+ end
327
+
328
+ describe '#get_token' do
329
+ it 'returns a configured AccessToken' do
330
+ client = stubbed_client do |stub|
331
+ stub.post('/oauth/token') do
332
+ [200, {'Content-Type' => 'application/json'}, MultiJson.encode('access_token' => 'the-token')]
333
+ end
334
+ end
335
+
336
+ token = client.get_token({})
337
+ expect(token).to be_a OAuth2::AccessToken
338
+ expect(token.token).to eq('the-token')
339
+ end
340
+
341
+ it 'authenticates with request parameters' do
342
+ client = stubbed_client(:auth_scheme => :request_body) do |stub|
343
+ stub.post('/oauth/token', 'client_id' => 'abc', 'client_secret' => 'def') do |env|
344
+ [200, {'Content-Type' => 'application/json'}, MultiJson.encode('access_token' => 'the-token')]
345
+ end
346
+ end
347
+ client.get_token({})
348
+ end
349
+
350
+ it 'authenticates with Basic auth' do
351
+ client = stubbed_client(:auth_scheme => :basic_auth) do |stub|
352
+ stub.post('/oauth/token') do |env|
353
+ raise Faraday::Adapter::Test::Stubs::NotFound unless env[:request_headers]['Authorization'] == OAuth2::Authenticator.encode_basic_auth('abc', 'def')
354
+
355
+ [200, {'Content-Type' => 'application/json'}, MultiJson.encode('access_token' => 'the-token')]
356
+ end
357
+ end
358
+ client.get_token({})
359
+ end
360
+
361
+ describe 'extract_access_token option' do
362
+ let(:client) do
363
+ client = stubbed_client(:extract_access_token => extract_access_token) do |stub|
364
+ stub.post('/oauth/token') do
365
+ [200, {'Content-Type' => 'application/json'}, MultiJson.encode('data' => {'access_token' => 'the-token'})]
366
+ end
367
+ end
368
+ end
369
+
370
+ context 'with proc extract_access_token' do
371
+ let(:extract_access_token) do
372
+ proc do |client, hash|
373
+ token = hash['data']['access_token']
374
+ OAuth2::AccessToken.new(client, token, hash)
375
+ end
376
+ end
377
+
378
+ it 'returns a configured AccessToken' do
379
+ token = client.get_token({})
380
+ expect(token).to be_a OAuth2::AccessToken
381
+ expect(token.token).to eq('the-token')
382
+ end
383
+ end
384
+
385
+ context 'with depracted Class.from_hash option' do
386
+ let(:extract_access_token) do
387
+ CustomAccessToken = Class.new(OAuth2::AccessToken)
388
+ CustomAccessToken.define_singleton_method(:from_hash) do |client, hash|
389
+ token = hash['data']['access_token']
390
+ OAuth2::AccessToken.new(client, token, hash)
391
+ end
392
+ CustomAccessToken
393
+ end
394
+
395
+ it 'returns a configured AccessToken' do
396
+ token = client.get_token({})
397
+ expect(token).to be_a OAuth2::AccessToken
398
+ expect(token.token).to eq('the-token')
399
+ end
400
+ end
401
+ end
402
+
403
+ describe ':raise_errors flag' do
404
+ let(:options) { {} }
405
+ let(:token_response) { nil }
406
+ let(:post_args) { [] }
407
+
408
+ let(:client) do
409
+ stubbed_client(options.merge(:raise_errors => raise_errors)) do |stub|
410
+ stub.post('/oauth/token', *post_args) do
411
+ # stub 200 response so that we're testing the get_token handling of :raise_errors flag not request
412
+ [200, {'Content-Type' => 'application/json'}, token_response]
413
+ end
414
+ end
415
+ end
416
+
417
+ context 'when set to false' do
418
+ let(:raise_errors) { false }
419
+
420
+ context 'when the request body is nil' do
421
+ it 'returns a nil :access_token' do
422
+ expect(client.get_token({})).to eq(nil)
423
+ end
424
+ end
425
+
426
+ context 'when the request body is missing the access_token' do
427
+ let(:token_response) { MultiJson.encode('unexpected_access_token' => 'the-token') }
428
+
429
+ it 'returns a nil :access_token' do
430
+ expect(client.get_token({})).to eq(nil)
431
+ end
432
+ end
433
+
434
+ context 'when the request body has an access token' do
435
+ let(:token_response) { MultiJson.encode('access_token' => 'the-token') }
436
+
437
+ it 'returns the parsed :access_token from body' do
438
+ token = client.get_token({})
439
+ expect(token).to be_a OAuth2::AccessToken
440
+ expect(token.token).to eq('the-token')
441
+ end
442
+
443
+ context 'when :auth_scheme => :request_body' do
444
+ context 'when arbitrary params are present' do
445
+ let(:post_args) { ['arbitrary' => 'parameter', 'client_id' => 'abc', 'client_secret' => 'def'] }
446
+ let(:options) { {:auth_scheme => :request_body} }
447
+
448
+ it 'does not affect access token' do
449
+ token = client.get_token(*post_args)
450
+ expect(token).to be_a OAuth2::AccessToken
451
+ expect(token.token).to eq('the-token')
452
+ end
453
+ end
454
+ end
455
+ end
456
+
457
+ context 'when extract_access_token raises an exception' do
458
+ let(:options) do
459
+ {
460
+ :extract_access_token => proc { |client, hash| raise ArgumentError },
461
+ }
462
+ end
463
+
464
+ it 'returns a nil :access_token' do
465
+ expect(client.get_token({})).to eq(nil)
466
+ end
467
+ end
468
+ end
469
+
470
+ context 'when set to true' do
471
+ let(:raise_errors) { true }
472
+
473
+ context 'when the request body is nil' do
474
+ it 'raises an error' do
475
+ expect { client.get_token({}) }.to raise_error OAuth2::Error
476
+ end
477
+ end
478
+
479
+ context 'when the request body is missing the access_token' do
480
+ let(:token_response) { MultiJson.encode('unexpected_access_token' => 'the-token') }
481
+
482
+ it 'raises an error' do
483
+ expect { client.get_token({}) }.to raise_error OAuth2::Error
484
+ end
485
+ end
486
+
487
+ context 'when extract_access_token raises an exception' do
488
+ let(:options) do
489
+ {
490
+ :extract_access_token => proc { |client, hash| raise ArgumentError },
491
+ }
492
+ end
493
+
494
+ it 'raises an error' do
495
+ expect { client.get_token({}) }.to raise_error OAuth2::Error
496
+ end
497
+ end
498
+ end
499
+ end
500
+
501
+ def stubbed_client(params = {}, &stubs)
502
+ params = {:site => 'https://api.example.com'}.merge(params)
503
+ OAuth2::Client.new('abc', 'def', params) do |builder|
504
+ builder.adapter :test, &stubs
505
+ end
506
+ end
507
+ end
508
+
509
+ it 'instantiates an AuthCode strategy with this client' do
510
+ expect(subject.auth_code).to be_kind_of(OAuth2::Strategy::AuthCode)
511
+ end
512
+
513
+ it 'instantiates an Implicit strategy with this client' do
514
+ expect(subject.implicit).to be_kind_of(OAuth2::Strategy::Implicit)
515
+ end
516
+
517
+ context 'with SSL options' do
518
+ subject do
519
+ cli = described_class.new('abc', 'def', :site => 'https://api.example.com', :ssl => {:ca_file => 'foo.pem'})
520
+ cli.connection = Faraday.new(cli.site, cli.options[:connection_opts]) do |b|
521
+ b.adapter :test
522
+ end
523
+ cli
524
+ end
525
+
526
+ it 'passes the SSL options along to Faraday::Connection#ssl' do
527
+ expect(subject.connection.ssl.fetch(:ca_file)).to eq('foo.pem')
528
+ end
529
+ end
530
+ end
@@ -0,0 +1,120 @@
1
+ describe OAuth2::MACToken do
2
+ subject { described_class.new(client, token, 'abc123') }
3
+
4
+ let(:token) { 'monkey' }
5
+ let(:client) do
6
+ OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com') do |builder|
7
+ builder.request :url_encoded
8
+ builder.adapter :test do |stub|
9
+ VERBS.each do |verb|
10
+ stub.send(verb, '/token/header') { |env| [200, {}, env[:request_headers]['Authorization']] }
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ describe '#initialize' do
17
+ it 'assigns client and token' do
18
+ expect(subject.client).to eq(client)
19
+ expect(subject.token).to eq(token)
20
+ end
21
+
22
+ it 'assigns secret' do
23
+ expect(subject.secret).to eq('abc123')
24
+ end
25
+
26
+ it 'defaults algorithm to hmac-sha-256' do
27
+ pending_for(:engine => 'ruby', :versions => '1.9.3', :reason => "Ruby 1.9's OpenSSL uses instance of OpenSSL::Digest")
28
+ expect(subject.algorithm).to be_instance_of(OpenSSL::Digest::SHA256)
29
+ end
30
+
31
+ it 'handles hmac-sha-256' do
32
+ pending_for(:engine => 'ruby', :versions => '1.9.3', :reason => "Ruby 1.9's OpenSSL uses instance of OpenSSL::Digest")
33
+ mac = described_class.new(client, token, 'abc123', :algorithm => 'hmac-sha-256')
34
+ expect(mac.algorithm).to be_instance_of(OpenSSL::Digest::SHA256)
35
+ end
36
+
37
+ it 'handles hmac-sha-1' do
38
+ pending_for(:engine => 'ruby', :versions => '1.9.3', :reason => "Ruby 1.9's OpenSSL uses instance of OpenSSL::Digest")
39
+ mac = described_class.new(client, token, 'abc123', :algorithm => 'hmac-sha-1')
40
+ expect(mac.algorithm).to be_instance_of(OpenSSL::Digest::SHA1)
41
+ end
42
+
43
+ it 'raises on improper algorithm' do
44
+ expect { described_class.new(client, token, 'abc123', :algorithm => 'invalid-sha') }.to raise_error(ArgumentError)
45
+ end
46
+ end
47
+
48
+ describe '#request' do
49
+ VERBS.each do |verb|
50
+ it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do
51
+ expect(subject.post('/token/header').body).to include("MAC id=\"#{token}\"")
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '#header' do
57
+ it 'does not generate the same header twice' do
58
+ header = subject.header('get', 'https://www.example.com/hello')
59
+ duplicate_header = subject.header('get', 'https://www.example.com/hello')
60
+
61
+ expect(header).not_to eq(duplicate_header)
62
+ end
63
+
64
+ it 'generates the proper format' do
65
+ header = subject.header('get', 'https://www.example.com/hello?a=1')
66
+ expect(header).to match(/MAC id="#{token}", ts="[0-9]+", nonce="[^"]+", mac="[^"]+"/)
67
+ end
68
+
69
+ it 'passes ArgumentError with an invalid url' do
70
+ expect { subject.header('get', 'this-is-not-valid') }.to raise_error(ArgumentError)
71
+ end
72
+
73
+ it 'passes URI::InvalidURIError through' do
74
+ expect { subject.header('get', nil) }.to raise_error(URI::InvalidURIError)
75
+ end
76
+ end
77
+
78
+ describe '#signature' do
79
+ it 'generates properly' do
80
+ signature = subject.signature(0, 'random-string', 'get', URI('https://www.google.com'))
81
+ expect(signature).to eq('rMDjVA3VJj3v1OmxM29QQljKia6msl5rjN83x3bZmi8=')
82
+ end
83
+ end
84
+
85
+ describe '#headers' do
86
+ it 'is an empty hash' do
87
+ expect(subject.headers).to eq({})
88
+ end
89
+ end
90
+
91
+ describe '.from_access_token' do
92
+ subject { described_class.from_access_token(access_token, 'hello') }
93
+
94
+ let(:access_token) do
95
+ OAuth2::AccessToken.new(
96
+ client, token,
97
+ :expires_at => 1,
98
+ :expires_in => 1,
99
+ :refresh_token => 'abc',
100
+ :random => 1
101
+ )
102
+ end
103
+
104
+ it 'initializes client, token, and secret properly' do
105
+ expect(subject.client).to eq(client)
106
+ expect(subject.token).to eq(token)
107
+ expect(subject.secret).to eq('hello')
108
+ end
109
+
110
+ it 'initializes configuration options' do
111
+ expect(subject.expires_at).to eq(1)
112
+ expect(subject.expires_in).to eq(1)
113
+ expect(subject.refresh_token).to eq('abc')
114
+ end
115
+
116
+ it 'initializes params' do
117
+ expect(subject.params).to eq(:random => 1)
118
+ end
119
+ end
120
+ end