Skip to content

Commit a47d7db

Browse files
committed
Add Faraday extensions
1 parent 8a56bb9 commit a47d7db

File tree

4 files changed

+125
-0
lines changed

4 files changed

+125
-0
lines changed

lib/faraday/connection.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
require 'faraday'
2+
require_relative 'request/digest_authentication'
3+
4+
module Faraday
5+
class Connection
6+
def digest_auth(user, password)
7+
self.builder.insert(0, Faraday::Request::DigestAuth, user, password)
8+
end
9+
end
10+
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
require 'faraday'
2+
require 'net/http/digest_auth'
3+
4+
module Faraday
5+
class Request::DigestAuth < Faraday::Middleware
6+
def initialize(app, user, password)
7+
super(app)
8+
@user, @password = user, password
9+
end
10+
11+
def call(env)
12+
response = handshake(env)
13+
return response unless response.status == 401
14+
15+
env[:request_headers]['Authorization'] = header(response)
16+
@app.call(env)
17+
end
18+
19+
private
20+
def handshake(env)
21+
env_without_body = env.dup
22+
env_without_body.delete(:body)
23+
24+
@app.call(env_without_body)
25+
end
26+
27+
def header(response)
28+
uri = response.env[:url]
29+
uri.user = @user
30+
uri.password = @password
31+
32+
realm = response.headers['www-authenticate']
33+
method = response.env[:method].to_s.upcase
34+
35+
digest_auth_header(uri, realm, method)
36+
end
37+
38+
def digest_auth_header(uri, realm, method)
39+
digest_auth = Net::HTTP::DigestAuth.new
40+
digest_auth.auth_header(uri, realm, method)
41+
end
42+
end
43+
end
44+
45+
Faraday.register_middleware :request, digest: Faraday::Request::DigestAuth

test/faraday/connection_test.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
require_relative '../test_helper'
2+
require 'faraday/connection'
3+
4+
module Faraday
5+
describe Connection do
6+
describe 'digest_auth' do
7+
let(:connection) do
8+
Faraday.new('http://api.example.org/') do |builder|
9+
builder.request :url_encoded
10+
builder.adapter :net_http
11+
end
12+
end
13+
14+
it 'inserts the DigestAuth middleware at the top' do
15+
connection.digest_auth('user', 'password')
16+
17+
connection.builder.handlers.first.klass.must_equal Faraday::Request::DigestAuth
18+
end
19+
20+
it 'passes the user and password to the middleware' do
21+
connection.digest_auth('user', 'password')
22+
23+
Faraday::Request::DigestAuth.expects(:new).with(anything, 'user', 'password').returns(stub_everything)
24+
25+
connection.get('/')
26+
end
27+
end
28+
end
29+
end
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
require_relative '../test_helper'
2+
require 'faraday/request/digest_authentication'
3+
4+
module Faraday
5+
describe Request::DigestAuth do
6+
let(:connection) do
7+
Faraday.new('http://api.example.org/') do |builder|
8+
builder.request :digest, 'USER', 'PASS'
9+
builder.adapter :net_http
10+
end
11+
end
12+
13+
describe 'when the server does not return a 401' do
14+
it 'does nothing' do
15+
stub_request(:get, 'http://api.example.org/productions/1').
16+
to_return(status: 500, body: 'Foo body')
17+
18+
response = connection.get('/productions/1')
19+
response.body.must_equal 'Foo body'
20+
end
21+
end
22+
23+
describe 'when the server returns a 401' do
24+
let(:first_call_headers) { 'Digest realm="MyApp", algorithm=MD5' }
25+
let(:second_call_headers) { 'Digest username="USER", realm="MyApp", uri="/", algorithm="MD5"' }
26+
it 'authenticates using digest' do
27+
stub_request(:get, 'http://api.example.org/productions/1').
28+
with(body: nil).
29+
to_return(status: 401, headers: {'www-authenticate' => first_call_headers})
30+
31+
stub_request(:get, 'http://api.example.org/productions/1').
32+
with(body: "{\"foo\":1}",
33+
headers: {'Authorization' => %r{second_call_headers}}).
34+
to_return(body: '{"resource": "This is the resource"}',
35+
headers: {content_type: 'application/json'})
36+
37+
connection.get('/productions/1')
38+
end
39+
end
40+
end
41+
end

0 commit comments

Comments
 (0)