diff --git a/credential.c b/credential.c index 2594c0c422..035399d7ee 100644 --- a/credential.c +++ b/credential.c @@ -708,3 +708,19 @@ void credential_from_url(struct credential *c, const char *url) if (credential_from_url_gently(c, url, 0) < 0) die(_("credential url cannot be parsed: %s"), url); } + +void credential_update_url(struct credential *c, const char *url) +{ + struct strvec wwwauth_headers = STRVEC_INIT; + + /* + * credential_from_url() clears the whole credential. Preserve the + * WWW-Authenticate list, which is derived from the server's original + * response rather than from the URL and is required to authenticate to + * the new URL. + */ + SWAP(wwwauth_headers, c->wwwauth_headers); + credential_from_url(c, url); + SWAP(c->wwwauth_headers, wwwauth_headers); + strvec_clear(&wwwauth_headers); +} diff --git a/credential.h b/credential.h index c78b72d110..b90f666e33 100644 --- a/credential.h +++ b/credential.h @@ -305,6 +305,14 @@ void credential_write(const struct credential *, FILE *, void credential_from_url(struct credential *, const char *url); int credential_from_url_gently(struct credential *, const char *url, int quiet); +/* + * Update the URL-derived fields (protocol, host, path) of an existing + * credential to match a new URL. Unlike credential_from_url(), this function + * preserves state that was derived from a server's HTTP redirect response, + * such as the WWW-Authenticate headers. + */ +void credential_update_url(struct credential *c, const char *url); + int credential_match(const struct credential *want, const struct credential *have, int match_password); diff --git a/http.c b/http.c index c8fcfd7693..510204e45f 100644 --- a/http.c +++ b/http.c @@ -2429,7 +2429,14 @@ static int http_request_recoverable(const char *url, if (options->effective_url && options->base_url) { if (update_url_from_redirect(options->base_url, url, options->effective_url)) { - credential_from_url(&http_auth, options->base_url->buf); + /* + * Use credential_update_url() rather than + * credential_from_url() so that the WWW-Authenticate + * challenge the server sent with the redirect target's + * response is preserved and handed to the credential + * helper. + */ + credential_update_url(&http_auth, options->base_url->buf); url = options->effective_url->buf; } } diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 4149fc1078..0627ef1433 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -203,6 +203,7 @@ RewriteRule ^/dumb-redir/(.*)$ /dumb/$1 [R=301] RewriteRule ^/smart-redir-perm/(.*)$ /smart/$1 [R=301] RewriteRule ^/smart-redir-temp/(.*)$ /smart/$1 [R=302] RewriteRule ^/smart-redir-auth/(.*)$ /auth/smart/$1 [R=301] +RewriteRule ^/custom_auth_redir/(.*)$ /custom_auth/$1 [R=302] RewriteRule ^/smart-redir-limited/(.*)/info/refs$ /smart/$1/info/refs [R=301] RewriteRule ^/ftp-redir/(.*)$ ftp://localhost:1000/$1 [R=302] diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh index a7d475dd68..349ae4ab39 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh @@ -557,6 +557,51 @@ test_expect_success 'access using bearer auth' ' EOF ' +test_expect_success 'bearer auth after redirect preserves wwwauth headers' ' + test_when_finished "per_test_cleanup" && + + set_credential_reply get <<-EOF && + capability[]=authtype + authtype=Bearer + credential=YS1naXQtdG9rZW4= + EOF + + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && + id=1 creds=Bearer YS1naXQtdG9rZW4= + EOF + + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && + id=1 status=200 + id=default response=WWW-Authenticate: FooBar param1="value1" param2="value2" + id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + id=default response=WWW-Authenticate: Basic realm="example.com" + EOF + + test_config_global credential.helper test-helper && + test_config_global credential.useHttpPath true && + git ls-remote "$HTTPD_URL/custom_auth_redir/repo.git" && + + expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state + protocol=http + host=$HTTPD_DEST + path=custom_auth/repo.git + wwwauth[]=FooBar param1="value1" param2="value2" + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0 + wwwauth[]=Basic realm="example.com" + EOF + + expect_credential_query store <<-EOF + capability[]=authtype + authtype=Bearer + credential=YS1naXQtdG9rZW4= + protocol=http + host=$HTTPD_DEST + path=custom_auth/repo.git + EOF +' + test_expect_success 'access using bearer auth with invalid credentials' ' test_when_finished "per_test_cleanup" &&