oauth2 1.4.7 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,506 +0,0 @@
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(connection).to receive(:build).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
-
407
- let(:client) do
408
- stubbed_client(options.merge(:raise_errors => raise_errors)) do |stub|
409
- stub.post('/oauth/token') do
410
- # stub 200 response so that we're testing the get_token handling of :raise_errors flag not request
411
- [200, {'Content-Type' => 'application/json'}, token_response]
412
- end
413
- end
414
- end
415
-
416
- context 'when set to false' do
417
- let(:raise_errors) { false }
418
-
419
- context 'when the request body is nil' do
420
- it 'returns a nil :access_token' do
421
- expect(client.get_token({})).to eq(nil)
422
- end
423
- end
424
-
425
- context 'when the request body is missing the access_token' do
426
- let(:token_response) { MultiJson.encode('unexpected_access_token' => 'the-token') }
427
-
428
- it 'returns a nil :access_token' do
429
- expect(client.get_token({})).to eq(nil)
430
- end
431
- end
432
-
433
- context 'when extract_access_token raises an exception' do
434
- let(:options) do
435
- {
436
- :extract_access_token => proc { |client, hash| raise ArgumentError },
437
- }
438
- end
439
-
440
- it 'returns a nil :access_token' do
441
- expect(client.get_token({})).to eq(nil)
442
- end
443
- end
444
- end
445
-
446
- context 'when set to true' do
447
- let(:raise_errors) { true }
448
-
449
- context 'when the request body is nil' do
450
- it 'raises an error' do
451
- expect { client.get_token({}) }.to raise_error OAuth2::Error
452
- end
453
- end
454
-
455
- context 'when the request body is missing the access_token' do
456
- let(:token_response) { MultiJson.encode('unexpected_access_token' => 'the-token') }
457
-
458
- it 'raises an error' do
459
- expect { client.get_token({}) }.to raise_error OAuth2::Error
460
- end
461
- end
462
-
463
- context 'when extract_access_token raises an exception' do
464
- let(:options) do
465
- {
466
- :extract_access_token => proc { |client, hash| raise ArgumentError },
467
- }
468
- end
469
-
470
- it 'raises an error' do
471
- expect { client.get_token({}) }.to raise_error OAuth2::Error
472
- end
473
- end
474
- end
475
- end
476
-
477
- def stubbed_client(params = {}, &stubs)
478
- params = {:site => 'https://api.example.com'}.merge(params)
479
- OAuth2::Client.new('abc', 'def', params) do |builder|
480
- builder.adapter :test, &stubs
481
- end
482
- end
483
- end
484
-
485
- it 'instantiates an AuthCode strategy with this client' do
486
- expect(subject.auth_code).to be_kind_of(OAuth2::Strategy::AuthCode)
487
- end
488
-
489
- it 'instantiates an Implicit strategy with this client' do
490
- expect(subject.implicit).to be_kind_of(OAuth2::Strategy::Implicit)
491
- end
492
-
493
- context 'with SSL options' do
494
- subject do
495
- cli = described_class.new('abc', 'def', :site => 'https://api.example.com', :ssl => {:ca_file => 'foo.pem'})
496
- cli.connection.build do |b|
497
- b.adapter :test
498
- end
499
- cli
500
- end
501
-
502
- it 'passes the SSL options along to Faraday::Connection#ssl' do
503
- expect(subject.connection.ssl.fetch(:ca_file)).to eq('foo.pem')
504
- end
505
- end
506
- end
@@ -1,117 +0,0 @@
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
- expect(subject.algorithm).to be_instance_of(OpenSSL::Digest::SHA256)
28
- end
29
-
30
- it 'handles hmac-sha-256' do
31
- mac = described_class.new(client, token, 'abc123', :algorithm => 'hmac-sha-256')
32
- expect(mac.algorithm).to be_instance_of(OpenSSL::Digest::SHA256)
33
- end
34
-
35
- it 'handles hmac-sha-1' do
36
- mac = described_class.new(client, token, 'abc123', :algorithm => 'hmac-sha-1')
37
- expect(mac.algorithm).to be_instance_of(OpenSSL::Digest::SHA1)
38
- end
39
-
40
- it 'raises on improper algorithm' do
41
- expect { described_class.new(client, token, 'abc123', :algorithm => 'invalid-sha') }.to raise_error(ArgumentError)
42
- end
43
- end
44
-
45
- describe '#request' do
46
- VERBS.each do |verb|
47
- it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do
48
- expect(subject.post('/token/header').body).to include("MAC id=\"#{token}\"")
49
- end
50
- end
51
- end
52
-
53
- describe '#header' do
54
- it 'does not generate the same header twice' do
55
- header = subject.header('get', 'https://www.example.com/hello')
56
- duplicate_header = subject.header('get', 'https://www.example.com/hello')
57
-
58
- expect(header).not_to eq(duplicate_header)
59
- end
60
-
61
- it 'generates the proper format' do
62
- header = subject.header('get', 'https://www.example.com/hello?a=1')
63
- expect(header).to match(/MAC id="#{token}", ts="[0-9]+", nonce="[^"]+", mac="[^"]+"/)
64
- end
65
-
66
- it 'passes ArgumentError with an invalid url' do
67
- expect { subject.header('get', 'this-is-not-valid') }.to raise_error(ArgumentError)
68
- end
69
-
70
- it 'passes URI::InvalidURIError through' do
71
- expect { subject.header('get', nil) }.to raise_error(URI::InvalidURIError)
72
- end
73
- end
74
-
75
- describe '#signature' do
76
- it 'generates properly' do
77
- signature = subject.signature(0, 'random-string', 'get', URI('https://www.google.com'))
78
- expect(signature).to eq('rMDjVA3VJj3v1OmxM29QQljKia6msl5rjN83x3bZmi8=')
79
- end
80
- end
81
-
82
- describe '#headers' do
83
- it 'is an empty hash' do
84
- expect(subject.headers).to eq({})
85
- end
86
- end
87
-
88
- describe '.from_access_token' do
89
- subject { described_class.from_access_token(access_token, 'hello') }
90
-
91
- let(:access_token) do
92
- OAuth2::AccessToken.new(
93
- client, token,
94
- :expires_at => 1,
95
- :expires_in => 1,
96
- :refresh_token => 'abc',
97
- :random => 1
98
- )
99
- end
100
-
101
- it 'initializes client, token, and secret properly' do
102
- expect(subject.client).to eq(client)
103
- expect(subject.token).to eq(token)
104
- expect(subject.secret).to eq('hello')
105
- end
106
-
107
- it 'initializes configuration options' do
108
- expect(subject.expires_at).to eq(1)
109
- expect(subject.expires_in).to eq(1)
110
- expect(subject.refresh_token).to eq('abc')
111
- end
112
-
113
- it 'initializes params' do
114
- expect(subject.params).to eq(:random => 1)
115
- end
116
- end
117
- end