diff --git a/docs/middleware/request/authentication.md b/docs/middleware/request/authentication.md index 3342f1fb5..70da6e317 100644 --- a/docs/middleware/request/authentication.md +++ b/docs/middleware/request/authentication.md @@ -10,12 +10,7 @@ top_link: ./list --- The `Faraday::Request::Authorization` middleware allows you to automatically add an `Authorization` header -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. +to your requests. It also features a handy helper to manage Basic authentication. ```ruby Faraday.new(...) do |conn| @@ -23,24 +18,22 @@ Faraday.new(...) do |conn| end ``` -### Basic Authentication +### With a proc -`BasicAuthentication` adds a 'Basic' type Authorization header to a Faraday request. +You can also provide a proc, which will be evaluated on each request: ```ruby Faraday.new(...) do |conn| - conn.request :basic_auth, 'username', 'password' + conn.request :authorization, 'Bearer', -> { MyAuthStorage.get_auth_token } end ``` -### Token Authentication +### Basic Authentication -`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. +The middleware will automatically Base64 encode your Basic username and password: ```ruby Faraday.new(...) do |conn| - conn.request :token_auth, 'authentication-token', **options + conn.request :authorization, :basic, 'username', 'password' end ``` diff --git a/lib/faraday/connection.rb b/lib/faraday/connection.rb index 6344f6a0c..1b035677b 100644 --- a/lib/faraday/connection.rb +++ b/lib/faraday/connection.rb @@ -283,77 +283,6 @@ 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. @@ -579,14 +508,6 @@ 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 diff --git a/lib/faraday/request/authorization.rb b/lib/faraday/request/authorization.rb index f9b56c871..d6aeaf2f7 100644 --- a/lib/faraday/request/authorization.rb +++ b/lib/faraday/request/authorization.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'base64' + module Faraday class Request # Request middleware for the Authorization HTTP header @@ -8,47 +10,46 @@ 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 token [String, Symbol, Hash] Token value for the Authorization - def initialize(app, type, token) - @header_value = self.class.header(type, token) + # @param params [Array] parameters to build the Authorization header. + # If the type is `:basic`, then these can be a login and password pair. + # Otherwise, a single value is expected that will be appended after the type. + # This value can be a proc, in which case it will be invoked on each request. + def initialize(app, type, *params) + @type = type + @params = params super(app) end # @param env [Faraday::Env] - def call(env) - env.request_headers[KEY] = @header_value unless env.request_headers[KEY] - @app.call(env) + def on_request(env) + return if env.request_headers[KEY] + + env.request_headers[KEY] = header_from(@type, *@params) + end + + private + + # @param type [String, Symbol] + # @param params [Array] + # @return [String] a header value + def header_from(type, *params) + if type.to_s.casecmp('basic').zero? && params.size == 2 + basic_header_from(*params) + elsif params.size != 1 + raise ArgumentError, "Unexpected params received (got #{params.size} instead of 1)" + else + value = params.first + value = value.call if value.is_a?(Proc) + "#{type} #{value}" + end + end + + def basic_header_from(login, pass) + value = Base64.encode64("#{login}:#{pass}") + value.delete!("\n") + "Basic #{value}" end end end diff --git a/lib/faraday/request/basic_authentication.rb b/lib/faraday/request/basic_authentication.rb deleted file mode 100644 index 61c9a5bc3..000000000 --- a/lib/faraday/request/basic_authentication.rb +++ /dev/null @@ -1,20 +0,0 @@ -# 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 diff --git a/lib/faraday/request/token_authentication.rb b/lib/faraday/request/token_authentication.rb deleted file mode 100644 index f28264b1c..000000000 --- a/lib/faraday/request/token_authentication.rb +++ /dev/null @@ -1,20 +0,0 @@ -# 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 diff --git a/spec/faraday/connection_spec.rb b/spec/faraday/connection_spec.rb index 0a1298117..4ff56335e 100644 --- a/spec/faraday/connection_spec.rb +++ b/spec/faraday/connection_spec.rb @@ -141,28 +141,6 @@ 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') } @@ -605,7 +583,6 @@ context 'after manual changes' do before do - subject.basic_auth('', '') subject.headers['content-length'] = 12 subject.params['b'] = '2' subject.options[:open_timeout] = 10 diff --git a/spec/faraday/request/authorization_spec.rb b/spec/faraday/request/authorization_spec.rb index 3f13392e6..234861d68 100644 --- a/spec/faraday/request/authorization_spec.rb +++ b/spec/faraday/request/authorization_spec.rb @@ -3,7 +3,7 @@ RSpec.describe Faraday::Request::Authorization do let(:conn) do Faraday.new do |b| - b.request auth_type, *auth_config + b.request :authorization, auth_type, *auth_config b.adapter :test do |stub| stub.get('/auth-echo') do |env| [200, {}, env[:request_headers]['Authorization']] @@ -14,10 +14,10 @@ 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: 'Token token="bar"') } + let(:response) { conn.get('/auth-echo', nil, authorization: 'OAuth oauth_token') } it 'does not interfere with existing authorization' do - expect(response.body).to eq('Token token="bar"') + expect(response.body).to eq('OAuth oauth_token') end end end @@ -25,7 +25,7 @@ let(:response) { conn.get('/auth-echo') } describe 'basic_auth' do - let(:auth_type) { :basic_auth } + let(:auth_type) { :basic } context 'when passed correct params' do let(:auth_config) { %w[aladdin opensesame] } @@ -44,43 +44,29 @@ 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 + describe 'authorization' do + let(:auth_type) { :Bearer } - context 'when other values are provided' do - let(:auth_config) { ['baz', { foo: 42 }] } + context 'when passed a string' do + let(:auth_config) { ['custom'] } - it { expect(response.body).to match(/^Token /) } - it { expect(response.body).to match(/token="baz"/) } - it { expect(response.body).to match(/foo="42"/) } + it { expect(response.body).to eq('Bearer custom') } include_examples 'does not interfere with existing authentication' end - end - - describe 'authorization' do - let(:auth_type) { :authorization } - context 'when passed two strings' do - let(:auth_config) { ['custom', 'abc def'] } + context 'when passed a proc' do + let(:auth_config) { [-> { 'custom_from_proc' }] } - it { expect(response.body).to eq('custom abc def') } + it { expect(response.body).to eq('Bearer custom_from_proc') } include_examples 'does not interfere with existing authentication' end - context 'when passed a string and a hash' do - let(:auth_config) { ['baz', { foo: 42 }] } + context 'when passed too many arguments' do + let(:auth_config) { %w[baz foo] } - it { expect(response.body).to eq('baz foo="42"') } + it { expect { response }.to raise_error(ArgumentError) } include_examples 'does not interfere with existing authentication' end