Skip to content

Commit 6539524

Browse files
committed
Merge branch 'mc/http-emptyauth-negotiate-fix' into next
The 'http.emptyAuth=auto' configuration now correctly attempts Negotiate authentication before falling back to manual credentials. This allows seamless Kerberos ticket-based authentication without requiring users to explicitly set 'http.emptyAuth=true'. * mc/http-emptyauth-negotiate-fix: t5563: add tests for http.emptyAuth with Negotiate http: attempt Negotiate auth in http.emptyAuth=auto mode http: extract http_reauth_prepare() from retry paths
2 parents 5897c04 + 9b1630b commit 6539524

File tree

4 files changed

+112
-4
lines changed

4 files changed

+112
-4
lines changed

http.c

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ static unsigned long empty_auth_useless =
138138
CURLAUTH_BASIC
139139
| CURLAUTH_DIGEST_IE
140140
| CURLAUTH_DIGEST;
141+
static int empty_auth_try_negotiate;
141142

142143
static struct curl_slist *pragma_header;
143144
static struct string_list extra_http_headers = STRING_LIST_INIT_DUP;
@@ -665,6 +666,22 @@ static void init_curl_http_auth(CURL *result)
665666
}
666667
}
667668

669+
void http_reauth_prepare(int all_capabilities)
670+
{
671+
/*
672+
* If we deferred stripping Negotiate to give empty auth a
673+
* chance (auto mode), skip credential_fill on this retry so
674+
* that init_curl_http_auth() sends empty credentials and
675+
* libcurl can attempt Negotiate with the system ticket cache.
676+
*/
677+
if (empty_auth_try_negotiate &&
678+
!http_auth.password && !http_auth.credential &&
679+
(http_auth_methods & CURLAUTH_GSSNEGOTIATE))
680+
return;
681+
682+
credential_fill(the_repository, &http_auth, all_capabilities);
683+
}
684+
668685
/* *var must be free-able */
669686
static void var_override(char **var, char *value)
670687
{
@@ -1890,7 +1907,18 @@ static int handle_curl_result(struct slot_results *results)
18901907
http_proactive_auth = PROACTIVE_AUTH_NONE;
18911908
return HTTP_NOAUTH;
18921909
} else {
1893-
http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
1910+
if (curl_empty_auth == -1 &&
1911+
!empty_auth_try_negotiate &&
1912+
(results->auth_avail & CURLAUTH_GSSNEGOTIATE)) {
1913+
/*
1914+
* In auto mode, give Negotiate a chance via
1915+
* empty auth before stripping it. If it fails,
1916+
* we will strip it on the next 401.
1917+
*/
1918+
empty_auth_try_negotiate = 1;
1919+
} else {
1920+
http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
1921+
}
18941922
if (results->auth_avail) {
18951923
http_auth_methods &= results->auth_avail;
18961924
http_auth_methods_restricted = 1;
@@ -2398,7 +2426,7 @@ static int http_request_recoverable(const char *url,
23982426
sleep(retry_delay);
23992427
}
24002428
} else if (ret == HTTP_REAUTH) {
2401-
credential_fill(the_repository, &http_auth, 1);
2429+
http_reauth_prepare(1);
24022430
}
24032431

24042432
ret = http_request(url, result, target, options);

http.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ extern int http_is_verbose;
7676
extern ssize_t http_post_buffer;
7777
extern struct credential http_auth;
7878

79+
/**
80+
* Prepare for an HTTP re-authentication retry. This fills credentials
81+
* via credential_fill() so the next request can include them.
82+
*/
83+
void http_reauth_prepare(int all_capabilities);
84+
7985
extern char curl_errorstr[CURL_ERROR_SIZE];
8086

8187
enum http_follow_config {

remote-curl.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
946946
do {
947947
err = probe_rpc(rpc, &results);
948948
if (err == HTTP_REAUTH)
949-
credential_fill(the_repository, &http_auth, 0);
949+
http_reauth_prepare(0);
950950
} while (err == HTTP_REAUTH);
951951
if (err != HTTP_OK)
952952
return -1;
@@ -1068,7 +1068,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
10681068
rpc->any_written = 0;
10691069
err = run_slot(slot, NULL);
10701070
if (err == HTTP_REAUTH && !large_request) {
1071-
credential_fill(the_repository, &http_auth, 0);
1071+
http_reauth_prepare(0);
10721072
curl_slist_free_all(headers);
10731073
goto retry;
10741074
}

t/t5563-simple-http-auth.sh

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,4 +719,78 @@ test_expect_success 'access using three-legged auth' '
719719
EOF
720720
'
721721

722+
test_lazy_prereq SPNEGO 'curl --version | grep -qi "SPNEGO\|GSS-API\|Kerberos\|negotiate"'
723+
724+
test_expect_success SPNEGO 'http.emptyAuth=auto attempts Negotiate before credential_fill' '
725+
test_when_finished "per_test_cleanup" &&
726+
727+
set_credential_reply get <<-EOF &&
728+
username=alice
729+
password=secret-passwd
730+
EOF
731+
732+
# Basic base64(alice:secret-passwd)
733+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
734+
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
735+
EOF
736+
737+
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
738+
id=1 status=200
739+
id=default response=WWW-Authenticate: Negotiate
740+
id=default response=WWW-Authenticate: Basic realm="example.com"
741+
EOF
742+
743+
test_config_global credential.helper test-helper &&
744+
GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-auto" \
745+
git -c http.emptyAuth=auto \
746+
ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
747+
748+
# In auto mode with a Negotiate+Basic server, there should be
749+
# three 401 responses: (1) initial no-auth request, (2) empty-auth
750+
# retry where Negotiate fails (no Kerberos ticket), (3) libcurl
751+
# internal Negotiate retry. The fourth attempt uses Basic
752+
# credentials from credential_fill and succeeds.
753+
grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-auto" >actual_401s &&
754+
test_line_count = 3 actual_401s &&
755+
756+
expect_credential_query get <<-EOF
757+
capability[]=authtype
758+
capability[]=state
759+
protocol=http
760+
host=$HTTPD_DEST
761+
wwwauth[]=Negotiate
762+
wwwauth[]=Basic realm="example.com"
763+
EOF
764+
'
765+
766+
test_expect_success SPNEGO 'http.emptyAuth=false skips Negotiate' '
767+
test_when_finished "per_test_cleanup" &&
768+
769+
set_credential_reply get <<-EOF &&
770+
username=alice
771+
password=secret-passwd
772+
EOF
773+
774+
# Basic base64(alice:secret-passwd)
775+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
776+
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
777+
EOF
778+
779+
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
780+
id=1 status=200
781+
id=default response=WWW-Authenticate: Negotiate
782+
id=default response=WWW-Authenticate: Basic realm="example.com"
783+
EOF
784+
785+
test_config_global credential.helper test-helper &&
786+
GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-false" \
787+
git -c http.emptyAuth=false \
788+
ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
789+
790+
# With emptyAuth=false, Negotiate is stripped immediately and
791+
# credential_fill is called right away. Only one 401 response.
792+
grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-false" >actual_401s &&
793+
test_line_count = 1 actual_401s
794+
'
795+
722796
test_done

0 commit comments

Comments
 (0)