Skip to content

Commit a46ae1a

Browse files
kyoshidajpiMacTia
authored andcommitted
Clear Authorization header when redirecting cross-site (#183)
1 parent f53b0ca commit a46ae1a

File tree

2 files changed

+145
-4
lines changed

2 files changed

+145
-4
lines changed

lib/faraday_middleware/response/follow_redirects.rb

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,24 @@ class FollowRedirects < Faraday::Middleware
4545
# the "%" character which we assume already represents an escaped sequence.
4646
URI_UNSAFE = /[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]%]/
4747

48+
AUTH_HEADER = 'Authorization'.freeze
49+
4850
# Public: Initialize the middleware.
4951
#
5052
# options - An options Hash (default: {}):
51-
# :limit - A Numeric redirect limit (default: 3)
52-
# :standards_compliant - A Boolean indicating whether to respect
53+
# :limit - A Numeric redirect limit (default: 3)
54+
# :standards_compliant - A Boolean indicating whether to respect
5355
# the HTTP spec when following 301/302
5456
# (default: false)
55-
# :callback - A callable that will be called on redirects
57+
# :callback - A callable that will be called on redirects
5658
# with the old and new envs
59+
# :cookies - An Array of Strings (e.g.
60+
# ['cookie1', 'cookie2']) to choose
61+
# cookies to be kept, or :all to keep
62+
# all cookies (default: []).
63+
# :clear_authorization_header - A Boolean indicating whether the request
64+
# Authorization header should be cleared on
65+
# redirects (default: true)
5766
def initialize(app, options = {})
5867
super(app)
5968
@options = options
@@ -89,7 +98,9 @@ def perform_with_redirection(env, follows)
8998
end
9099

91100
def update_env(env, request_body, response)
92-
env[:url] += safe_escape(response['location'] || '')
101+
redirect_from_url = env[:url].to_s
102+
redirect_to_url = safe_escape(response['location'] || '')
103+
env[:url] += redirect_to_url
93104

94105
if convert_to_get?(response)
95106
env[:method] = :get
@@ -98,6 +109,8 @@ def update_env(env, request_body, response)
98109
env[:body] = request_body
99110
end
100111

112+
clear_authorization_header(env, redirect_from_url, redirect_to_url)
113+
101114
ENV_TO_CLEAR.each {|key| env.delete key }
102115

103116
env
@@ -130,5 +143,21 @@ def safe_escape(uri)
130143
'%' + match.unpack('H2' * match.bytesize).join('%').upcase
131144
}
132145
end
146+
147+
def clear_authorization_header(env, from_url, to_url)
148+
return env if redirect_to_same_host?(from_url, to_url)
149+
return env unless @options.fetch(:clear_authorization_header, true)
150+
151+
env[:request_headers].delete(AUTH_HEADER)
152+
end
153+
154+
def redirect_to_same_host?(from_url, to_url)
155+
return true if to_url.start_with?('/')
156+
157+
from_uri = URI.parse(from_url)
158+
to_uri = URI.parse(to_url)
159+
160+
[from_uri.scheme, from_uri.host, from_uri.port] == [to_uri.scheme, to_uri.host, to_uri.port]
161+
end
133162
end
134163
end

spec/unit/follow_redirects_spec.rb

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,118 @@
164164
end
165165
end
166166

167+
context "when clear_authorization_header option" do
168+
context "is false" do
169+
it "redirects with the original authorization headers" do
170+
conn = connection(:clear_authorization_header => false) do |stub|
171+
stub.get('/redirect') {
172+
[301, {'Location' => '/found'}, '']
173+
}
174+
stub.get('/found') { |env|
175+
[200, {'Content-Type' => 'text/plain'}, env[:request_headers]['Authorization']]
176+
}
177+
end
178+
response = conn.get('/redirect') { |req|
179+
req.headers['Authorization'] = 'success'
180+
}
181+
182+
expect(response.body).to eq 'success'
183+
end
184+
end
185+
186+
context "is true" do
187+
context "redirect to same host" do
188+
it "redirects with the original authorization headers" do
189+
conn = connection do |stub|
190+
stub.get('http://localhost/redirect') do
191+
[301, {'Location' => '/found'}, '']
192+
end
193+
stub.get('http://localhost/found') do |env|
194+
[200, {}, env.request_headers["Authorization"]]
195+
end
196+
end
197+
response = conn.get('http://localhost/redirect') do |req|
198+
req.headers['Authorization'] = 'success'
199+
end
200+
201+
expect(response.body).to eq 'success'
202+
end
203+
end
204+
205+
context "redirect to same host with explicitly port" do
206+
it "redirects with the original authorization headers" do
207+
conn = connection do |stub|
208+
stub.get('http://localhost/redirect') do
209+
[301, {'Location' => 'http://localhost:80/found'}, '']
210+
end
211+
stub.get('http://localhost/found') do |env|
212+
[200, {}, env.request_headers["Authorization"]]
213+
end
214+
end
215+
response = conn.get('http://localhost/redirect') { |req|
216+
req.headers['Authorization'] = 'success'
217+
}
218+
219+
expect(response.body).to eq 'success'
220+
end
221+
end
222+
223+
context "redirect to different scheme" do
224+
it "redirects without original authorization headers" do
225+
conn = connection do |stub|
226+
stub.get('http://localhost/redirect') do
227+
[301, {'Location' => 'https://localhost2/found'}, '']
228+
end
229+
stub.get('https://localhost2/found') do |env|
230+
[200, {}, env.request_headers["Authorization"]]
231+
end
232+
end
233+
response = conn.get('http://localhost/redirect') { |req|
234+
req.headers['Authorization'] = 'failed'
235+
}
236+
237+
expect(response.body).to eq nil
238+
end
239+
end
240+
241+
context "redirect to different host" do
242+
it "redirects without original authorization headers" do
243+
conn = connection do |stub|
244+
stub.get('http://localhost/redirect') do
245+
[301, {'Location' => 'http://localhost2/found'}, '']
246+
end
247+
stub.get('https://localhost2/found') do |env|
248+
[200, {}, env.request_headers["Authorization"]]
249+
end
250+
end
251+
response = conn.get('http://localhost/redirect') { |req|
252+
req.headers['Authorization'] = 'failed'
253+
}
254+
255+
expect(response.body).to eq nil
256+
end
257+
end
258+
259+
context "redirect to different port" do
260+
it "redirects without original authorization headers" do
261+
conn = connection do |stub|
262+
stub.get('http://localhost:9090/redirect') do
263+
[301, {'Location' => 'http://localhost:9091/found'}, '']
264+
end
265+
stub.get('http://localhost:9091/found') do |env|
266+
[200, {}, env.request_headers["Authorization"]]
267+
end
268+
end
269+
response = conn.get('http://localhost:9090/redirect') { |req|
270+
req.headers['Authorization'] = 'failed'
271+
}
272+
273+
expect(response.body).to eq nil
274+
end
275+
end
276+
end
277+
end
278+
167279
[301, 302].each do |code|
168280
context "for an HTTP #{code} response" do
169281
it_behaves_like 'a successful redirection', code

0 commit comments

Comments
 (0)