Do not filter 'Authorization' header if host doesn't change while following redirection

This commit is contained in:
Jean-Christophe Amiel 2024-05-21 18:26:45 +02:00
parent 9dee51c22d
commit af0417d25d
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
5 changed files with 107 additions and 32 deletions

View File

@ -25,7 +25,7 @@ HTTP 200
url == "http://localhost:8000/followed-redirect"
`Followed redirect!`
# Check relative redirection
GET http://localhost:8000/follow-redirect/relative/foo
Accept: text/plain
HTTP 200
@ -34,34 +34,61 @@ url == "http://localhost:8000/follow-redirect/bar"
`Followed relative redirect!`
# Do not forward authorization header by default to a different host
GET http://localhost:8000/follow-redirect-basic-auth
# Do not forward `Authorization` header to a different host
GET http://localhost:8000/follow-redirect-basic-auth?change_host=true
Authorization: Basic Ym9iQGVtYWlsLmNvbTpzZWNyZXQ=
HTTP 200
[Asserts]
header "Location" not exists
`Followed redirect Basic Auth!`
`Followed redirect without Authorization header!`
# Another kinds of user authentication:
GET http://localhost:8000/follow-redirect-basic-auth
# Same has previous but the host doesn't change during redirection.
# Back checks will insure that `Authorization` is forwarded.
GET http://localhost:8000/follow-redirect-basic-auth?change_host=false
Authorization: Basic Ym9iQGVtYWlsLmNvbTpzZWNyZXQ=
HTTP 200
[Asserts]
header "Location" not exists
`Followed redirect with Authorization header!`
# Another kinds of user authentication with `--user` in `[Options]` section:
GET http://localhost:8000/follow-redirect-basic-auth?change_host=true
[Options]
user: bob@email.com:secret
HTTP 200
[Asserts]
header "Location" not exists
`Followed redirect Basic Auth!`
`Followed redirect without Authorization header!`
GET http://localhost:8000/follow-redirect-basic-auth
GET http://localhost:8000/follow-redirect-basic-auth?change_host=false
[Options]
user: bob@email.com:secret
HTTP 200
[Asserts]
header "Location" not exists
`Followed redirect with Authorization header!`
# Another kinds of user authentication with `[BasicAuth]` section:
GET http://localhost:8000/follow-redirect-basic-auth?change_host=true
[BasicAuth]
bob@email.com: secret
HTTP 200
[Asserts]
header "Location" not exists
`Followed redirect Basic Auth!`
`Followed redirect without Authorization header!`
GET http://localhost:8000/follow-redirect-basic-auth?change_host=false
[BasicAuth]
bob@email.com: secret
HTTP 200
[Asserts]
header "Location" not exists
`Followed redirect with Authorization header!`
POST http://localhost:8000/follow-redirect-308

View File

@ -43,13 +43,22 @@ def follow_redirect_308():
@app.route("/follow-redirect-basic-auth")
def follow_redirect_basic_auth():
assert "Authorization" in request.headers
return redirect("http://127.0.0.1:8000/followed-redirect-basic-auth")
change_host = request.args.get("change_host") == "true"
if change_host:
return redirect("http://127.0.0.1:8000/followed-redirect-basic-auth")
else:
return redirect("http://localhost:8000/followed-redirect-basic-auth")
@app.route("/followed-redirect-basic-auth")
def followed_redirect_basic_auth():
assert "Authorization" not in request.headers
return "Followed redirect Basic Auth!"
# When host changes, authorization should be filtered
if request.headers["Host"] == "localhost:8000":
assert "Authorization" in request.headers
return "Followed redirect with Authorization header!"
else:
assert "Authorization" not in request.headers
return "Followed redirect without Authorization header!"
@app.route("/follow-redirect-basic-auth-trusted")

View File

@ -57,29 +57,52 @@ url == "http://localhost:8000/follow-redirect/bar"
`Followed relative redirect!`
# Do not forward authorization header by default to a different host
GET http://localhost:8000/follow-redirect-basic-auth
# Do not forward `Authorization` header to a different host
GET http://localhost:8000/follow-redirect-basic-auth?change_host=true
Authorization: Basic Ym9iQGVtYWlsLmNvbTpzZWNyZXQ=
[Options]
location: true
HTTP 200
[Asserts]
header "Location" not exists
`Followed redirect Basic Auth!`
`Followed redirect without Authorization header!`
# Another kinds of user authentication:
GET http://localhost:8000/follow-redirect-basic-auth
# Same has previous but the host doesn't change during redirection.
# Back checks will insure that `Authorization` is forwarded.
GET http://localhost:8000/follow-redirect-basic-auth?change_host=false
Authorization: Basic Ym9iQGVtYWlsLmNvbTpzZWNyZXQ=
[Options]
location: true
HTTP 200
[Asserts]
header "Location" not exists
`Followed redirect with Authorization header!`
# Another kinds of user authentication with `--user` in `[Options]` section:
GET http://localhost:8000/follow-redirect-basic-auth?change_host=true
[Options]
location: true
user: bob@email.com:secret
HTTP 200
[Asserts]
header "Location" not exists
`Followed redirect Basic Auth!`
`Followed redirect without Authorization header!`
GET http://localhost:8000/follow-redirect-basic-auth
GET http://localhost:8000/follow-redirect-basic-auth?change_host=false
[Options]
location: true
user: bob@email.com:secret
HTTP 200
[Asserts]
header "Location" not exists
`Followed redirect with Authorization header!`
# Another kinds of user authentication with `[BasicAuth]` section:
GET http://localhost:8000/follow-redirect-basic-auth?change_host=true
[Options]
location: true
[BasicAuth]
@ -87,7 +110,18 @@ bob@email.com: secret
HTTP 200
[Asserts]
header "Location" not exists
`Followed redirect Basic Auth!`
`Followed redirect without Authorization header!`
GET http://localhost:8000/follow-redirect-basic-auth?change_host=false
[Options]
location: true
[BasicAuth]
bob@email.com: secret
HTTP 200
[Asserts]
header "Location" not exists
`Followed redirect with Authorization header!`
# Forward authorization header to a different host explicitly

View File

@ -115,7 +115,8 @@ impl Client {
let mut redirect_count = 0;
loop {
let call = self.execute(&request_spec, &options, logger)?;
let redirect_url = self.get_follow_location(&call.request, &call.response)?;
let request_url = call.request.url.clone();
let redirect_url = self.get_follow_location(&request_url, &call.response)?;
let status = call.response.status;
calls.push(call);
if !options.follow_location || redirect_url.is_none() {
@ -132,16 +133,13 @@ impl Client {
}
}
let redirect_method = get_redirect_method(status, request_spec.method);
let mut headers = request_spec.headers;
// When following redirection, we filter `AUTHORIZATION` header unless explicitly told
// to trust the redirected host.
// FIXME: we should filter only if we're changing host
let headers = if options.follow_location_trusted {
request_spec.headers
} else {
request_spec.headers.retain(|h| !h.name_eq(AUTHORIZATION));
request_spec.headers
};
if options.user.is_some() && !options.follow_location_trusted {
// to trust the redirected host with `--location-trusted`.
let host_changed = request_url.host() != redirect_url.host();
if host_changed && !options.follow_location_trusted {
headers.retain(|h| !h.name_eq(AUTHORIZATION));
options.user = None;
}
request_spec = RequestSpec {
@ -692,7 +690,7 @@ impl Client {
/// 3. a header Location
fn get_follow_location(
&mut self,
request: &Request,
request_url: &Url,
response: &Response,
) -> Result<Option<Url>, HttpError> {
let response_code = response.status;
@ -702,7 +700,7 @@ impl Client {
let Some(location) = response.headers.get(LOCATION) else {
return Ok(None);
};
let url = request.url.join(&location.value)?;
let url = request_url.join(&location.value)?;
Ok(Some(url))
}

View File

@ -50,6 +50,13 @@ impl Url {
.collect()
}
pub fn host(&self) -> String {
self.inner
.host()
.expect("HTTP and HTTPS URL must have a domain")
.to_string()
}
/// Parse a string `input` as an URL, with this URL as the base URL.
pub fn join(&self, input: &str) -> Result<Url, HttpError> {
let new_inner = self.inner.join(input);