oauth2 1.4.6 → 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 +4 -4
- data/CHANGELOG.md +5 -1
- data/README.md +1 -0
- data/lib/oauth2/access_token.rb +3 -5
- data/lib/oauth2/version.rb +7 -3
- 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 +31 -31
- data/.document +0 -5
- data/.github/dependabot.yml +0 -8
- data/.github/workflows/style.yml +0 -37
- data/.github/workflows/test.yml +0 -58
- data/.gitignore +0 -19
- data/.jrubyrc +0 -1
- data/.rspec +0 -4
- data/.rubocop.yml +0 -112
- data/.rubocop_rspec.yml +0 -26
- data/.rubocop_todo.yml +0 -113
- data/.ruby-version +0 -1
- data/.travis.yml +0 -75
- data/CONTRIBUTING.md +0 -18
- data/Gemfile +0 -61
- data/Rakefile +0 -45
- data/gemfiles/jruby_1.7.gemfile +0 -11
- data/gemfiles/jruby_9.0.gemfile +0 -7
- data/gemfiles/jruby_9.1.gemfile +0 -3
- data/gemfiles/jruby_9.2.gemfile +0 -3
- data/gemfiles/jruby_head.gemfile +0 -3
- data/gemfiles/ruby_1.9.gemfile +0 -11
- data/gemfiles/ruby_2.0.gemfile +0 -6
- data/gemfiles/ruby_head.gemfile +0 -9
- data/gemfiles/truffleruby.gemfile +0 -3
- data/maintenance-branch +0 -1
- data/oauth2.gemspec +0 -52
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74ca15f9b1935885fe123c9b0801b9f0605c1bfa904bebe371d201a61b559f31
|
4
|
+
data.tar.gz: 3b719ec6748493cba5112dfca8ebff22b13c0c9690330d4422f9d81b1abf5185
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ec3c9c311effac7f8864c2dc3d7332761ee6a986f2924adec3b77620abe3dc63984d1c01e9ca0a1283907a5b2ba23ee31dfd4bd630fa7d803fba116ba1e652d
|
7
|
+
data.tar.gz: 1e1d37d5953bdd7e03e13f697648e56afdb63ea54bbdbaee7ee229302149c5047c533a15d6498a355ef78d17d991d79ce8f94fe8728229431e3fad5c3132a9ad
|
data/CHANGELOG.md
CHANGED
@@ -3,9 +3,13 @@ All notable changes to this project will be documented in this file.
|
|
3
3
|
|
4
4
|
## unreleased
|
5
5
|
|
6
|
-
## [1.4.
|
6
|
+
## [1.4.7] - 2021-03-18
|
7
|
+
|
8
|
+
- [#541](https://github.com/oauth-xx/oauth2/pull/541) - Backport fix to expires_at handling [#533](https://github.com/oauth-xx/oauth2/pull/533) to 1-4-stable branch. (@dobon)
|
7
9
|
|
10
|
+
## [1.4.6] - 2021-03-18
|
8
11
|
|
12
|
+
- [#540](https://github.com/oauth-xx/oauth2/pull/540) - Add VERSION constant (@pboling)
|
9
13
|
- [#537](https://github.com/oauth-xx/oauth2/pull/537) - Fix crash in OAuth2::Client#get_token (@anderscarling)
|
10
14
|
- [#538](https://github.com/oauth-xx/oauth2/pull/538) - Remove reliance on globally included OAuth2 in tests for version 1.4 (@anderscarling)
|
11
15
|
|
data/README.md
CHANGED
@@ -4,6 +4,7 @@ If you need the readme for a released version of the gem please find it below:
|
|
4
4
|
|
5
5
|
| Version | Release Date | Readme |
|
6
6
|
|----------|--------------|----------------------------------------------------------|
|
7
|
+
| 1.4.7 | Mar 18, 2021 | https://github.com/oauth-xx/oauth2/blob/v1.4.7/README.md |
|
7
8
|
| 1.4.6 | Mar 18, 2021 | https://github.com/oauth-xx/oauth2/blob/v1.4.6/README.md |
|
8
9
|
| 1.4.5 | Mar 18, 2021 | https://github.com/oauth-xx/oauth2/blob/v1.4.5/README.md |
|
9
10
|
| 1.4.4 | Feb 12, 2020 | https://github.com/oauth-xx/oauth2/blob/v1.4.4/README.md |
|
data/lib/oauth2/access_token.rb
CHANGED
@@ -173,11 +173,9 @@ module OAuth2
|
|
173
173
|
end
|
174
174
|
|
175
175
|
def convert_expires_at(expires_at)
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
expires_at_i
|
176
|
+
Time.iso8601(expires_at.to_s).to_i
|
177
|
+
rescue ArgumentError
|
178
|
+
expires_at.to_i
|
181
179
|
end
|
182
180
|
end
|
183
181
|
end
|
data/lib/oauth2/version.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module OAuth2
|
2
4
|
module Version
|
3
5
|
VERSION = to_s
|
@@ -22,12 +24,12 @@ module OAuth2
|
|
22
24
|
#
|
23
25
|
# @return [Integer]
|
24
26
|
def patch
|
25
|
-
|
27
|
+
7
|
26
28
|
end
|
27
29
|
|
28
30
|
# The pre-release version, if any
|
29
31
|
#
|
30
|
-
# @return [
|
32
|
+
# @return [String, NilClass]
|
31
33
|
def pre
|
32
34
|
nil
|
33
35
|
end
|
@@ -55,7 +57,9 @@ module OAuth2
|
|
55
57
|
#
|
56
58
|
# @return [String]
|
57
59
|
def to_s
|
58
|
-
|
60
|
+
v = [major, minor, patch].compact.join('.')
|
61
|
+
v += "-#{pre}" if pre
|
62
|
+
v
|
59
63
|
end
|
60
64
|
end
|
61
65
|
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
DEBUG = ENV['DEBUG'] == 'true'
|
2
|
+
|
3
|
+
ruby_version = Gem::Version.new(RUBY_VERSION)
|
4
|
+
|
5
|
+
if ruby_version >= Gem::Version.new('2.7')
|
6
|
+
require 'simplecov'
|
7
|
+
require 'coveralls'
|
8
|
+
|
9
|
+
SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter]
|
10
|
+
|
11
|
+
SimpleCov.start do
|
12
|
+
add_filter '/spec'
|
13
|
+
minimum_coverage(95)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'byebug' if DEBUG && ruby_version >= Gem::Version.new('2.4')
|
18
|
+
|
19
|
+
require 'oauth2'
|
20
|
+
require 'addressable/uri'
|
21
|
+
require 'rspec'
|
22
|
+
require 'rspec/stubbed_env'
|
23
|
+
require 'silent_stream'
|
24
|
+
|
25
|
+
RSpec.configure do |config|
|
26
|
+
config.expect_with :rspec do |c|
|
27
|
+
c.syntax = :expect
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Faraday.default_adapter = :test
|
32
|
+
|
33
|
+
RSpec.configure do |conf|
|
34
|
+
conf.include SilentStream
|
35
|
+
end
|
36
|
+
|
37
|
+
VERBS = [:get, :post, :put, :delete].freeze
|
@@ -0,0 +1,216 @@
|
|
1
|
+
describe OAuth2::AccessToken do
|
2
|
+
subject { described_class.new(client, token) }
|
3
|
+
|
4
|
+
let(:token) { 'monkey' }
|
5
|
+
let(:refresh_body) { MultiJson.encode(:access_token => 'refreshed_foo', :expires_in => 600, :refresh_token => 'refresh_bar') }
|
6
|
+
let(:client) do
|
7
|
+
OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com') do |builder|
|
8
|
+
builder.request :url_encoded
|
9
|
+
builder.adapter :test do |stub|
|
10
|
+
VERBS.each do |verb|
|
11
|
+
stub.send(verb, '/token/header') { |env| [200, {}, env[:request_headers]['Authorization']] }
|
12
|
+
stub.send(verb, "/token/query?access_token=#{token}") { |env| [200, {}, Addressable::URI.parse(env[:url]).query_values['access_token']] }
|
13
|
+
stub.send(verb, '/token/query_string') { |env| [200, {}, CGI.unescape(Addressable::URI.parse(env[:url]).query)] }
|
14
|
+
stub.send(verb, '/token/body') { |env| [200, {}, env[:body]] }
|
15
|
+
end
|
16
|
+
stub.post('/oauth/token') { |env| [200, {'Content-Type' => 'application/json'}, refresh_body] }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#initialize' do
|
22
|
+
it 'assigns client and token' do
|
23
|
+
expect(subject.client).to eq(client)
|
24
|
+
expect(subject.token).to eq(token)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'assigns extra params' do
|
28
|
+
target = described_class.new(client, token, 'foo' => 'bar')
|
29
|
+
expect(target.params).to include('foo')
|
30
|
+
expect(target.params['foo']).to eq('bar')
|
31
|
+
end
|
32
|
+
|
33
|
+
def assert_initialized_token(target) # rubocop:disable Metrics/AbcSize
|
34
|
+
expect(target.token).to eq(token)
|
35
|
+
expect(target).to be_expires
|
36
|
+
expect(target.params.keys).to include('foo')
|
37
|
+
expect(target.params['foo']).to eq('bar')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'initializes with a Hash' do
|
41
|
+
hash = {:access_token => token, :expires_at => Time.now.to_i + 200, 'foo' => 'bar'}
|
42
|
+
target = described_class.from_hash(client, hash)
|
43
|
+
assert_initialized_token(target)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'from_hash does not modify opts hash' do
|
47
|
+
hash = {:access_token => token, :expires_at => Time.now.to_i}
|
48
|
+
hash_before = hash.dup
|
49
|
+
described_class.from_hash(client, hash)
|
50
|
+
expect(hash).to eq(hash_before)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'initializes with a form-urlencoded key/value string' do
|
54
|
+
kvform = "access_token=#{token}&expires_at=#{Time.now.to_i + 200}&foo=bar"
|
55
|
+
target = described_class.from_kvform(client, kvform)
|
56
|
+
assert_initialized_token(target)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'sets options' do
|
60
|
+
target = described_class.new(client, token, :param_name => 'foo', :header_format => 'Bearer %', :mode => :body)
|
61
|
+
expect(target.options[:param_name]).to eq('foo')
|
62
|
+
expect(target.options[:header_format]).to eq('Bearer %')
|
63
|
+
expect(target.options[:mode]).to eq(:body)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'does not modify opts hash' do
|
67
|
+
opts = {:param_name => 'foo', :header_format => 'Bearer %', :mode => :body}
|
68
|
+
opts_before = opts.dup
|
69
|
+
described_class.new(client, token, opts)
|
70
|
+
expect(opts).to eq(opts_before)
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'expires_at' do
|
74
|
+
let(:expires_at) { 1_361_396_829 }
|
75
|
+
let(:hash) do
|
76
|
+
{
|
77
|
+
:access_token => token,
|
78
|
+
:expires_at => expires_at.to_s,
|
79
|
+
'foo' => 'bar',
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'initializes with an integer timestamp expires_at' do
|
84
|
+
target = described_class.from_hash(client, hash.merge(:expires_at => expires_at))
|
85
|
+
assert_initialized_token(target)
|
86
|
+
expect(target.expires_at).to eql(expires_at)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'initializes with a string timestamp expires_at' do
|
90
|
+
target = described_class.from_hash(client, hash)
|
91
|
+
assert_initialized_token(target)
|
92
|
+
expect(target.expires_at).to eql(expires_at)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'initializes with a string time expires_at' do
|
96
|
+
target = described_class.from_hash(client, hash.merge(:expires_at => Time.at(expires_at).iso8601))
|
97
|
+
assert_initialized_token(target)
|
98
|
+
expect(target.expires_at).to eql(expires_at)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe '#request' do
|
104
|
+
context 'with :mode => :header' do
|
105
|
+
before do
|
106
|
+
subject.options[:mode] = :header
|
107
|
+
end
|
108
|
+
|
109
|
+
VERBS.each do |verb|
|
110
|
+
it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do
|
111
|
+
expect(subject.post('/token/header').body).to include(token)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'with :mode => :query' do
|
117
|
+
before do
|
118
|
+
subject.options[:mode] = :query
|
119
|
+
end
|
120
|
+
|
121
|
+
VERBS.each do |verb|
|
122
|
+
it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do
|
123
|
+
expect(subject.post('/token/query').body).to eq(token)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "sends a #{verb.to_s.upcase} request and options[:param_name] include [number]." do
|
127
|
+
subject.options[:param_name] = 'auth[1]'
|
128
|
+
expect(subject.__send__(verb, '/token/query_string').body).to include("auth[1]=#{token}")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'with :mode => :body' do
|
134
|
+
before do
|
135
|
+
subject.options[:mode] = :body
|
136
|
+
end
|
137
|
+
|
138
|
+
VERBS.each do |verb|
|
139
|
+
it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do
|
140
|
+
expect(subject.post('/token/body').body.split('=').last).to eq(token)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'params include [number]' do
|
146
|
+
VERBS.each do |verb|
|
147
|
+
it "sends #{verb.to_s.upcase} correct query" do
|
148
|
+
expect(subject.__send__(verb, '/token/query_string', :params => {'foo[bar][1]' => 'val'}).body).to include('foo[bar][1]=val')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe '#expires?' do
|
155
|
+
it 'is false if there is no expires_at' do
|
156
|
+
expect(described_class.new(client, token)).not_to be_expires
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'is true if there is an expires_in' do
|
160
|
+
expect(described_class.new(client, token, :refresh_token => 'abaca', :expires_in => 600)).to be_expires
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'is true if there is an expires_at' do
|
164
|
+
expect(described_class.new(client, token, :refresh_token => 'abaca', :expires_in => Time.now.getutc.to_i + 600)).to be_expires
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe '#expired?' do
|
169
|
+
it 'is false if there is no expires_in or expires_at' do
|
170
|
+
expect(described_class.new(client, token)).not_to be_expired
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'is false if expires_in is in the future' do
|
174
|
+
expect(described_class.new(client, token, :refresh_token => 'abaca', :expires_in => 10_800)).not_to be_expired
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'is true if expires_at is in the past' do
|
178
|
+
access = described_class.new(client, token, :refresh_token => 'abaca', :expires_in => 600)
|
179
|
+
@now = Time.now + 10_800
|
180
|
+
allow(Time).to receive(:now).and_return(@now)
|
181
|
+
expect(access).to be_expired
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe '#refresh!' do
|
186
|
+
let(:access) do
|
187
|
+
described_class.new(client, token, :refresh_token => 'abaca',
|
188
|
+
:expires_in => 600,
|
189
|
+
:param_name => 'o_param')
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'returns a refresh token with appropriate values carried over' do
|
193
|
+
refreshed = access.refresh!
|
194
|
+
expect(access.client).to eq(refreshed.client)
|
195
|
+
expect(access.options[:param_name]).to eq(refreshed.options[:param_name])
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'with a nil refresh_token in the response' do
|
199
|
+
let(:refresh_body) { MultiJson.encode(:access_token => 'refreshed_foo', :expires_in => 600, :refresh_token => nil) }
|
200
|
+
|
201
|
+
it 'copies the refresh_token from the original token' do
|
202
|
+
refreshed = access.refresh!
|
203
|
+
|
204
|
+
expect(refreshed.refresh_token).to eq(access.refresh_token)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe '#to_hash' do
|
210
|
+
it 'return a hash equals to the hash used to initialize access token' do
|
211
|
+
hash = {:access_token => token, :refresh_token => 'foobar', :expires_at => Time.now.to_i + 200, 'foo' => 'bar'}
|
212
|
+
access_token = described_class.from_hash(client, hash.clone)
|
213
|
+
expect(access_token.to_hash).to eq(hash)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
describe OAuth2::Authenticator do
|
2
|
+
subject do
|
3
|
+
described_class.new(client_id, client_secret, mode)
|
4
|
+
end
|
5
|
+
|
6
|
+
let(:client_id) { 'foo' }
|
7
|
+
let(:client_secret) { 'bar' }
|
8
|
+
let(:mode) { :undefined }
|
9
|
+
|
10
|
+
it 'raises NotImplementedError for unknown authentication mode' do
|
11
|
+
expect { subject.apply({}) }.to raise_error(NotImplementedError)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#apply' do
|
15
|
+
context 'with parameter-based authentication' do
|
16
|
+
let(:mode) { :request_body }
|
17
|
+
|
18
|
+
it 'adds client_id and client_secret to params' do
|
19
|
+
output = subject.apply({})
|
20
|
+
expect(output).to eq('client_id' => 'foo', 'client_secret' => 'bar')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'does not overwrite existing credentials' do
|
24
|
+
input = {'client_secret' => 's3cr3t'}
|
25
|
+
output = subject.apply(input)
|
26
|
+
expect(output).to eq('client_id' => 'foo', 'client_secret' => 's3cr3t')
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'preserves other parameters' do
|
30
|
+
input = {'state' => '42', :headers => {'A' => 'b'}}
|
31
|
+
output = subject.apply(input)
|
32
|
+
expect(output).to eq(
|
33
|
+
'client_id' => 'foo',
|
34
|
+
'client_secret' => 'bar',
|
35
|
+
'state' => '42',
|
36
|
+
:headers => {'A' => 'b'}
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'using tls client authentication' do
|
41
|
+
let(:mode) { :tls_client_auth }
|
42
|
+
|
43
|
+
it 'does not add client_secret' do
|
44
|
+
output = subject.apply({})
|
45
|
+
expect(output).to eq('client_id' => 'foo')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'using private key jwt authentication' do
|
50
|
+
let(:mode) { :private_key_jwt }
|
51
|
+
|
52
|
+
it 'does not add client_secret or client_id' do
|
53
|
+
output = subject.apply({})
|
54
|
+
expect(output).to eq({})
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'with Basic authentication' do
|
60
|
+
let(:mode) { :basic_auth }
|
61
|
+
let(:header) { 'Basic ' + Base64.encode64("#{client_id}:#{client_secret}").delete("\n") }
|
62
|
+
|
63
|
+
it 'encodes credentials in headers' do
|
64
|
+
output = subject.apply({})
|
65
|
+
expect(output).to eq(:headers => {'Authorization' => header})
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'does not overwrite existing credentials' do
|
69
|
+
input = {:headers => {'Authorization' => 'Bearer abc123'}}
|
70
|
+
output = subject.apply(input)
|
71
|
+
expect(output).to eq(:headers => {'Authorization' => 'Bearer abc123'})
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'does not overwrite existing params or headers' do
|
75
|
+
input = {'state' => '42', :headers => {'A' => 'b'}}
|
76
|
+
output = subject.apply(input)
|
77
|
+
expect(output).to eq(
|
78
|
+
'state' => '42',
|
79
|
+
:headers => {'A' => 'b', 'Authorization' => header}
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -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
|