Skip to content

Commit

Permalink
Fixup backward compatibility on top of cherry-pick commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jarl-dk committed Sep 16, 2021
1 parent 3fee015 commit 6c99a94
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 19 deletions.
27 changes: 22 additions & 5 deletions docs/middleware/request/authentication.md
Expand Up @@ -10,7 +10,12 @@ top_link: ./list
---

The `Faraday::Request::Authorization` middleware allows you to automatically add an `Authorization` header
to your requests. It also features a handy helper to manage Basic authentication.
to your requests. It also features 2 specialised sub-classes that provide useful extra features for Basic Authentication
and Token Authentication requests.

### Any Authentication

The generic `Authorization` middleware allows you to add any type of Authorization header.

```ruby
Faraday.new(...) do |conn|
Expand All @@ -22,18 +27,30 @@ end

You can also provide a proc, which will be evaluated on each request:

```ruby
Faraday.new(...) do |conn|
conn.request :authorization, 'Bearer', -> { MyAuthStorage.get_auth_token }
end
```

### Basic Authentication

`BasicAuthentication` adds a 'Basic' type Authorization header to a Faraday request.

```ruby
Faraday.new(...) do |conn|
conn.request :authorization, 'Bearer', -> { MyAuthStorage.get_auth_token }
conn.request :basic_auth, 'username', 'password'
end
```

### Basic Authentication
### Token Authentication

The middleware will automatically Base64 encode your Basic username and password:
`TokenAuthentication` adds a 'Token' type Authorization header to a Faraday request.
You can optionally provide a hash of `options` that will be appended to the token.
This is not used anymore in modern web and have been replaced by Bearer tokens.

```ruby
Faraday.new(...) do |conn|
conn.request :authorization, :basic, 'username', 'password'
conn.request :token_auth, 'authentication-token', **options
end
```
79 changes: 79 additions & 0 deletions lib/faraday/connection.rb
Expand Up @@ -283,6 +283,77 @@ def #{method}(url = nil, body = nil, headers = nil, &block)
RUBY
end

# Sets up the Authorization header with these credentials, encoded
# with base64.
#
# @param login [String] The authentication login.
# @param pass [String] The authentication password.
#
# @example
#
# conn.basic_auth 'Aladdin', 'open sesame'
# conn.headers['Authorization']
# # => "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
#
# @return [void]
def basic_auth(login, pass)
warn <<~TEXT
WARNING: `Faraday::Connection#basic_auth` is deprecated; it will be removed in version 2.0.
While initializing your connection, use `#request(:basic_auth, ...)` instead.
See https://lostisland.github.io/faraday/middleware/authentication for more usage info.
TEXT
set_authorization_header(:basic_auth, login, pass)
end

# Sets up the Authorization header with the given token.
#
# @param token [String]
# @param options [Hash] extra token options.
#
# @example
#
# conn.token_auth 'abcdef', foo: 'bar'
# conn.headers['Authorization']
# # => "Token token=\"abcdef\",
# foo=\"bar\""
#
# @return [void]
def token_auth(token, options = nil)
warn <<~TEXT
WARNING: `Faraday::Connection#token_auth` is deprecated; it will be removed in version 2.0.
While initializing your connection, use `#request(:token_auth, ...)` instead.
See https://lostisland.github.io/faraday/middleware/authentication for more usage info.
TEXT
set_authorization_header(:token_auth, token, options)
end

# Sets up a custom Authorization header.
#
# @param type [String] authorization type
# @param token [String, Hash] token. A String value is taken literally, and
# a Hash is encoded into comma-separated key/value pairs.
#
# @example
#
# conn.authorization :Bearer, 'mF_9.B5f-4.1JqM'
# conn.headers['Authorization']
# # => "Bearer mF_9.B5f-4.1JqM"
#
# conn.authorization :Token, token: 'abcdef', foo: 'bar'
# conn.headers['Authorization']
# # => "Token token=\"abcdef\",
# foo=\"bar\""
#
# @return [void]
def authorization(type, token)
warn <<~TEXT
WARNING: `Faraday::Connection#authorization` is deprecated; it will be removed in version 2.0.
While initializing your connection, use `#request(:authorization, ...)` instead.
See https://lostisland.github.io/faraday/middleware/authentication for more usage info.
TEXT
set_authorization_header(:authorization, type, token)
end

# Check if the adapter is parallel-capable.
#
# @yield if the adapter isn't parallel-capable, or if no adapter is set yet.
Expand Down Expand Up @@ -508,6 +579,14 @@ def with_uri_credentials(uri)
yield(Utils.unescape(uri.user), Utils.unescape(uri.password))
end

def set_authorization_header(header_type, *args)
header = Faraday::Request
.lookup_middleware(header_type)
.header(*args)

headers[Faraday::Request::Authorization::KEY] = header
end

def proxy_from_env(url)
return if Faraday.ignore_env_proxy

Expand Down
32 changes: 32 additions & 0 deletions lib/faraday/request/authorization.rb
Expand Up @@ -10,6 +10,35 @@ class Authorization < Faraday::Middleware
KEY = 'Authorization'
end

# @param type [String, Symbol]
# @param token [String, Symbol, Hash]
# @return [String] a header value
def self.header(type, token)
case token
when String, Symbol
"#{type} #{token}"
when Hash
build_hash(type.to_s, token)
else
raise ArgumentError,
"Can't build an Authorization #{type}" \
"header from #{token.inspect}"
end
end

# @param type [String]
# @param hash [Hash]
# @return [String] type followed by comma-separated key=value pairs
# @api private
def self.build_hash(type, hash)
comma = ', '
values = []
hash.each do |key, value|
values << "#{key}=#{value.to_s.inspect}"
end
"#{type} #{values * comma}"
end

# @param app [#call]
# @param type [String, Symbol] Type of Authorization
# @param params [Array<String, Proc>] parameters to build the Authorization header.
Expand All @@ -19,6 +48,7 @@ class Authorization < Faraday::Middleware
def initialize(app, type, *params)
@type = type
@params = params
@header_value = self.class.header(type, params[0]) unless params[0].is_a? Proc
super(app)
end

Expand All @@ -35,6 +65,8 @@ def on_request(env)
# @param params [Array]
# @return [String] a header value
def header_from(type, *params)
return @header_value if @header_value

if type.to_s.casecmp('basic').zero? && params.size == 2
basic_header_from(*params)
elsif params.size != 1
Expand Down
20 changes: 20 additions & 0 deletions lib/faraday/request/basic_authentication.rb
@@ -0,0 +1,20 @@
# frozen_string_literal: true

require 'base64'

module Faraday
class Request
# Authorization middleware for Basic Authentication.
class BasicAuthentication < load_middleware(:authorization)
# @param login [String]
# @param pass [String]
#
# @return [String] a Basic Authentication header line
def self.header(login, pass)
value = Base64.encode64([login, pass].join(':'))
value.delete!("\n")
super(:Basic, value)
end
end
end
end
20 changes: 20 additions & 0 deletions lib/faraday/request/token_authentication.rb
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module Faraday
class Request
# TokenAuthentication is a middleware that adds a 'Token' header to a
# Faraday request.
class TokenAuthentication < load_middleware(:authorization)
# Public
def self.header(token, options = nil)
options ||= {}
options[:token] = token
super(:Token, options)
end

def initialize(app, token, options = nil)
super(app, token, options)
end
end
end
end
23 changes: 23 additions & 0 deletions spec/faraday/connection_spec.rb
Expand Up @@ -141,6 +141,28 @@
end
end

describe 'basic_auth' do
subject { conn }

context 'calling the #basic_auth method' do
before { subject.basic_auth 'Aladdin', 'open sesame' }

it { expect(subject.headers['Authorization']).to eq('Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==') }
end

context 'adding basic auth info to url' do
let(:url) { 'http://Aladdin:open%20sesame@sushi.com/fish' }

it { expect(subject.headers['Authorization']).to eq('Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==') }
end
end

describe '#token_auth' do
before { subject.token_auth('abcdef', nonce: 'abc') }

it { expect(subject.headers['Authorization']).to eq('Token nonce="abc", token="abcdef"') }
end

describe '#build_exclusive_url' do
context 'with relative path' do
subject { conn.build_exclusive_url('sake.html') }
Expand Down Expand Up @@ -583,6 +605,7 @@

context 'after manual changes' do
before do
subject.basic_auth('', '')
subject.headers['content-length'] = 12
subject.params['b'] = '2'
subject.options[:open_timeout] = 10
Expand Down
50 changes: 36 additions & 14 deletions spec/faraday/request/authorization_spec.rb
Expand Up @@ -3,7 +3,7 @@
RSpec.describe Faraday::Request::Authorization do
let(:conn) do
Faraday.new do |b|
b.request :authorization, auth_type, *auth_config
b.request auth_type, *auth_config
b.adapter :test do |stub|
stub.get('/auth-echo') do |env|
[200, {}, env[:request_headers]['Authorization']]
Expand All @@ -14,18 +14,18 @@

shared_examples 'does not interfere with existing authentication' do
context 'and request already has an authentication header' do
let(:response) { conn.get('/auth-echo', nil, authorization: 'OAuth oauth_token') }
let(:response) { conn.get('/auth-echo', nil, authorization: 'Token token="bar"') }

it 'does not interfere with existing authorization' do
expect(response.body).to eq('OAuth oauth_token')
expect(response.body).to eq('Token token="bar"')
end
end
end

let(:response) { conn.get('/auth-echo') }

describe 'basic_auth' do
let(:auth_type) { :basic }
let(:auth_type) { :basic_auth }

context 'when passed correct params' do
let(:auth_config) { %w[aladdin opensesame] }
Expand All @@ -44,29 +44,51 @@
end
end

describe 'token_auth' do
let(:auth_type) { :token_auth }

context 'when passed correct params' do
let(:auth_config) { 'quux' }

it { expect(response.body).to eq('Token token="quux"') }

include_examples 'does not interfere with existing authentication'
end

context 'when other values are provided' do
let(:auth_config) { ['baz', { foo: 42 }] }

it { expect(response.body).to match(/^Token /) }
it { expect(response.body).to match(/token="baz"/) }
it { expect(response.body).to match(/foo="42"/) }

include_examples 'does not interfere with existing authentication'
end
end

describe 'authorization' do
let(:auth_type) { :Bearer }
let(:auth_type) { :authorization }

context 'when passed a string' do
let(:auth_config) { ['custom'] }
context 'when passed two strings' do
let(:auth_config) { ['custom', 'abc def'] }

it { expect(response.body).to eq('Bearer custom') }
it { expect(response.body).to eq('custom abc def') }

include_examples 'does not interfere with existing authentication'
end

context 'when passed a proc' do
let(:auth_config) { [-> { 'custom_from_proc' }] }
context 'when passed a string and a hash' do
let(:auth_config) { ['baz', { foo: 42 }] }

it { expect(response.body).to eq('Bearer custom_from_proc') }
it { expect(response.body).to eq('baz foo="42"') }

include_examples 'does not interfere with existing authentication'
end

context 'when passed too many arguments' do
let(:auth_config) { %w[baz foo] }
context 'when passed a string and a proc' do
let(:auth_config) { ['Bearer', -> { 'custom_from_proc' }] }

it { expect { response }.to raise_error(ArgumentError) }
it { expect(response.body).to eq('Bearer custom_from_proc') }

include_examples 'does not interfere with existing authentication'
end
Expand Down

0 comments on commit 6c99a94

Please sign in to comment.