]> git.ipfire.org Git - thirdparty/git.git/commitdiff
http: preserve wwwauth_headers across redirects
authorAaron Plattner <aplattner@nvidia.com>
Tue, 2 Jun 2026 16:11:48 +0000 (09:11 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 2 Jun 2026 22:38:45 +0000 (07:38 +0900)
When cURL follows a redirect, it calls the CURLOPT_HEADERFUNCTION for
each header received including ones from a redirect. http_request() sets
fwrite_wwwauth() as the header function, which will record the wwwauth[]
entries for the last step in the redirection chain.

However, when http_request_recoverable() sees that cURL followed a
redirect, it attempts to update the credentials for the request from the
new URL using credential_from_url(). The first thing that does is call
credential_clear(), which clears everything including wwwauth_headers.

If the new URL should use a credential helper rather than credentials
embedded in the URL, this loses the list of authentication methods that
the server provided in the redirect.

For example, I have a server that supports HTTP but always redirects to
HTTPS before handling requests. This redirect breaks OAuth
authentication:

  $ git ls-remote http://server/git
  => Send header: GET /git/info/refs?service=git-upload-pack HTTP/1.1
  <= Recv header: HTTP/1.1 302 Found
  <= Recv header: Location: https://server.nvidia.com/git/info/refs?service=git-upload-pack
  == Info: Issue another request to this URL: 'https://server.nvidia.com/git/info/refs?service=git-upload-pack'
  => Send header: GET /git/info/refs?service=git-upload-pack HTTP/1.1
  <= Recv header: HTTP/1.1 401 Unauthorized
  <= Recv header: WWW-Authenticate: Bearer error="invalid_request", error_description="No bearer token found in the request", msal-tenant-id="<tenant>", msal-client-id="<client>"
  trace: run_command: 'git credential-cache --timeout 7200 get'
  trace: start_command: /bin/sh -c 'git credential-cache --timeout 7200 get' 'git credential-cache --timeout 7200 get'
  trace: built-in: git credential-cache --timeout 7200 get
  trace: run_command: 'git credential-msal get'
  trace: start_command: /bin/sh -c 'git credential-msal get' 'git credential-msal get'
  trace: exec: git-credential-msal get
  trace: run_command: git-credential-msal get
  trace: start_command: /usr/bin/git-credential-msal get
  Username for 'https://server.nvidia.com': ^C

When git invokes the credential helper, it doesn't include the wwwauth[]
array, so git-credential-msal doesn't think that OAuth is supported [1].

Fix the problem by preserving the wwwauth_headers strvec across the call
to credential_from_url().

[1] https://github.com/Binary-Eater/git-credential-msal/blob/trunk/src/git_credential_msal/main.py#L69

Signed-off-by: Aaron Plattner <aplattner@nvidia.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
http.c
t/lib-httpd/apache.conf
t/t5563-simple-http-auth.sh

diff --git a/http.c b/http.c
index 67c9c6fc60673d8f536979560b78a0aeabe71a0d..82f2562f30da50516e538589bdb20be57586171c 100644 (file)
--- a/http.c
+++ b/http.c
@@ -2354,7 +2354,21 @@ 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)) {
+                       struct strvec wwwauth_headers = STRVEC_INIT;
+
+                       /*
+                        * Preserve wwwauth_headers across the call to
+                        * credential_from_url(): if the effective URL doesn't
+                        * specify its own credentials, a credential helper
+                        * might need the wwwauth[] array from the server's
+                        * redirect response in order to authenticate.
+                        */
+                       strvec_pushv(&wwwauth_headers,
+                                    http_auth.wwwauth_headers.v);
                        credential_from_url(&http_auth, options->base_url->buf);
+                       strvec_pushv(&http_auth.wwwauth_headers,
+                                    wwwauth_headers.v);
+                       strvec_clear(&wwwauth_headers);
                        url = options->effective_url->buf;
                }
        }
index 40a690b0bb7c9b6b42b44d73546d6816ee2015a5..664f23fc6cdde2195906cb171ae273c5ce1253dd 100644 (file)
@@ -202,6 +202,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]
 
index 00635816156ba35feea882b48bfc79d93ff9338a..04c8b15324cedb9b4ce1de4ceb982d4ac2dd02da 100755 (executable)
@@ -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" &&