oauth2 1.3.1 → 1.4.7
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 +5 -5
- data/CHANGELOG.md +170 -0
- data/CODE_OF_CONDUCT.md +133 -0
- data/LICENSE +22 -0
- data/README.md +137 -23
- data/lib/oauth2/access_token.rb +12 -4
- data/lib/oauth2/authenticator.rb +10 -0
- data/lib/oauth2/client.rb +63 -16
- data/lib/oauth2/mac_token.rb +11 -3
- data/lib/oauth2/response.rb +5 -3
- data/lib/oauth2/strategy/assertion.rb +3 -3
- data/lib/oauth2/strategy/password.rb +2 -2
- data/lib/oauth2/version.rb +10 -4
- data/spec/helper.rb +37 -0
- data/spec/oauth2/access_token_spec.rb +216 -0
- data/spec/oauth2/authenticator_spec.rb +84 -0
- data/spec/oauth2/client_spec.rb +506 -0
- data/spec/oauth2/mac_token_spec.rb +117 -0
- data/spec/oauth2/response_spec.rb +90 -0
- data/spec/oauth2/strategy/assertion_spec.rb +58 -0
- data/spec/oauth2/strategy/auth_code_spec.rb +107 -0
- data/spec/oauth2/strategy/base_spec.rb +5 -0
- data/spec/oauth2/strategy/client_credentials_spec.rb +69 -0
- data/spec/oauth2/strategy/implicit_spec.rb +26 -0
- data/spec/oauth2/strategy/password_spec.rb +55 -0
- data/spec/oauth2/version_spec.rb +23 -0
- metadata +219 -24
- data/.document +0 -5
- data/CONTRIBUTING.md +0 -18
- data/LICENSE.md +0 -20
- data/oauth2.gemspec +0 -24
@@ -0,0 +1,506 @@
|
|
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
|
@@ -0,0 +1,117 @@
|
|
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
|