Skip to content

Commit

Permalink
Clear Authorization header when redirecting cross-site (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyoshidajp authored and iMacTia committed Feb 4, 2019
1 parent f53b0ca commit a46ae1a
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 4 deletions.
37 changes: 33 additions & 4 deletions lib/faraday_middleware/response/follow_redirects.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,24 @@ class FollowRedirects < Faraday::Middleware
# the "%" character which we assume already represents an escaped sequence.
URI_UNSAFE = /[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]%]/

AUTH_HEADER = 'Authorization'.freeze

# Public: Initialize the middleware.
#
# options - An options Hash (default: {}):
# :limit - A Numeric redirect limit (default: 3)
# :standards_compliant - A Boolean indicating whether to respect
# :limit - A Numeric redirect limit (default: 3)
# :standards_compliant - A Boolean indicating whether to respect
# the HTTP spec when following 301/302
# (default: false)
# :callback - A callable that will be called on redirects
# :callback - A callable that will be called on redirects
# with the old and new envs
# :cookies - An Array of Strings (e.g.
# ['cookie1', 'cookie2']) to choose
# cookies to be kept, or :all to keep
# all cookies (default: []).
# :clear_authorization_header - A Boolean indicating whether the request
# Authorization header should be cleared on
# redirects (default: true)
def initialize(app, options = {})
super(app)
@options = options
Expand Down Expand Up @@ -89,7 +98,9 @@ def perform_with_redirection(env, follows)
end

def update_env(env, request_body, response)
env[:url] += safe_escape(response['location'] || '')
redirect_from_url = env[:url].to_s
redirect_to_url = safe_escape(response['location'] || '')
env[:url] += redirect_to_url

if convert_to_get?(response)
env[:method] = :get
Expand All @@ -98,6 +109,8 @@ def update_env(env, request_body, response)
env[:body] = request_body
end

clear_authorization_header(env, redirect_from_url, redirect_to_url)

ENV_TO_CLEAR.each {|key| env.delete key }

env
Expand Down Expand Up @@ -130,5 +143,21 @@ def safe_escape(uri)
'%' + match.unpack('H2' * match.bytesize).join('%').upcase
}
end

def clear_authorization_header(env, from_url, to_url)
return env if redirect_to_same_host?(from_url, to_url)
return env unless @options.fetch(:clear_authorization_header, true)

env[:request_headers].delete(AUTH_HEADER)
end

def redirect_to_same_host?(from_url, to_url)
return true if to_url.start_with?('/')

from_uri = URI.parse(from_url)
to_uri = URI.parse(to_url)

[from_uri.scheme, from_uri.host, from_uri.port] == [to_uri.scheme, to_uri.host, to_uri.port]
end
end
end
112 changes: 112 additions & 0 deletions spec/unit/follow_redirects_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,118 @@
end
end

context "when clear_authorization_header option" do
context "is false" do
it "redirects with the original authorization headers" do
conn = connection(:clear_authorization_header => false) do |stub|
stub.get('/redirect') {
[301, {'Location' => '/found'}, '']
}
stub.get('/found') { |env|
[200, {'Content-Type' => 'text/plain'}, env[:request_headers]['Authorization']]
}
end
response = conn.get('/redirect') { |req|
req.headers['Authorization'] = 'success'
}

expect(response.body).to eq 'success'
end
end

context "is true" do
context "redirect to same host" do
it "redirects with the original authorization headers" do
conn = connection do |stub|
stub.get('http://localhost/redirect') do
[301, {'Location' => '/found'}, '']
end
stub.get('http://localhost/found') do |env|
[200, {}, env.request_headers["Authorization"]]
end
end
response = conn.get('http://localhost/redirect') do |req|
req.headers['Authorization'] = 'success'
end

expect(response.body).to eq 'success'
end
end

context "redirect to same host with explicitly port" do
it "redirects with the original authorization headers" do
conn = connection do |stub|
stub.get('http://localhost/redirect') do
[301, {'Location' => 'http://localhost:80/found'}, '']
end
stub.get('http://localhost/found') do |env|
[200, {}, env.request_headers["Authorization"]]
end
end
response = conn.get('http://localhost/redirect') { |req|
req.headers['Authorization'] = 'success'
}

expect(response.body).to eq 'success'
end
end

context "redirect to different scheme" do
it "redirects without original authorization headers" do
conn = connection do |stub|
stub.get('http://localhost/redirect') do
[301, {'Location' => 'https://localhost2/found'}, '']
end
stub.get('https://localhost2/found') do |env|
[200, {}, env.request_headers["Authorization"]]
end
end
response = conn.get('http://localhost/redirect') { |req|
req.headers['Authorization'] = 'failed'
}

expect(response.body).to eq nil
end
end

context "redirect to different host" do
it "redirects without original authorization headers" do
conn = connection do |stub|
stub.get('http://localhost/redirect') do
[301, {'Location' => 'http://localhost2/found'}, '']
end
stub.get('https://localhost2/found') do |env|
[200, {}, env.request_headers["Authorization"]]
end
end
response = conn.get('http://localhost/redirect') { |req|
req.headers['Authorization'] = 'failed'
}

expect(response.body).to eq nil
end
end

context "redirect to different port" do
it "redirects without original authorization headers" do
conn = connection do |stub|
stub.get('http://localhost:9090/redirect') do
[301, {'Location' => 'http://localhost:9091/found'}, '']
end
stub.get('http://localhost:9091/found') do |env|
[200, {}, env.request_headers["Authorization"]]
end
end
response = conn.get('http://localhost:9090/redirect') { |req|
req.headers['Authorization'] = 'failed'
}

expect(response.body).to eq nil
end
end
end
end

[301, 302].each do |code|
context "for an HTTP #{code} response" do
it_behaves_like 'a successful redirection', code
Expand Down

0 comments on commit a46ae1a

Please sign in to comment.