http: add support for authtype and credential

Now that we have the credential helper code set up to handle arbitrary authentications schemes, let's add support for this in the HTTP code, where we really want to use it. If we're using this new functionality, don't set a username and password, and instead set a header wherever we'd normally do so, including for proxy authentication. Since we can now handle this case, ask the credential helper to enable the appropriate capabilities. Finally, if we're using the authtype value, set "Expect: 100-continue". Any type of authentication that requires multiple rounds (such as NTLM or Kerberos) requires a 100 Continue (if we're larger than http.postBuffer) because otherwise we send the pack data before we're authenticated, the push gets a 401 response, and we can't rewind the stream. We don't know for certain what other custom schemes might require this, the HTTP/1.1 standard has required handling this since 1999, the broken HTTP server for which we disabled this (Google's) is now fixed and has been for some time, and libcurl has a 1-second fallback in case the HTTP server is still broken. In addition, it is not unreasonable to require compliance with a 25-year old standard to use new Git features. For all of these reasons, do so here. Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

brian m. carlson committed Apr 17, 2024 at 00:02 UTC ad9bb6dfe6e598d87ffe6e2285b4b86dac3bc726
4 files changed +176 -12
http.c
+37 -11
@@ -561,18 +561,34 @@ static int curl_empty_auth_enabled(void)
561 return 0;
562 }
563
564 +struct curl_slist *http_append_auth_header(const struct credential *c,
565 + struct curl_slist *headers)
566 +{
567 + if (c->authtype && c->credential) {
568 + struct strbuf auth = STRBUF_INIT;
569 + strbuf_addf(&auth, "Authorization: %s %s",
570 + c->authtype, c->credential);
571 + headers = curl_slist_append(headers, auth.buf);
572 + strbuf_release(&auth);
573 + }
574 + return headers;
575 +}
576 +
577 static void init_curl_http_auth(CURL *result)
578 {
566 - if (!http_auth.username || !*http_auth.username) {
579 + if ((!http_auth.username || !*http_auth.username) &&
580 + (!http_auth.credential || !*http_auth.credential)) {
581 if (curl_empty_auth_enabled())
582 curl_easy_setopt(result, CURLOPT_USERPWD, ":");
583 return;
584 }
585
572 - credential_fill(&http_auth, 0);
586 + credential_fill(&http_auth, 1);
587
574 - curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);
575 - curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);
588 + if (http_auth.password) {
589 + curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);
590 + curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);
591 + }
592 }
593
594 /* *var must be free-able */
@@ -586,17 +602,22 @@ static void var_override(const char **var, char *value)
602
603 static void set_proxyauth_name_password(CURL *result)
604 {
605 + if (proxy_auth.password) {
606 curl_easy_setopt(result, CURLOPT_PROXYUSERNAME,
607 proxy_auth.username);
608 curl_easy_setopt(result, CURLOPT_PROXYPASSWORD,
609 proxy_auth.password);
610 + } else if (proxy_auth.authtype && proxy_auth.credential) {
611 + curl_easy_setopt(result, CURLOPT_PROXYHEADER,
612 + http_append_auth_header(&proxy_auth, NULL));
613 + }
614 }
615
616 static void init_curl_proxy_auth(CURL *result)
617 {
618 if (proxy_auth.username) {
598 - if (!proxy_auth.password)
599 - credential_fill(&proxy_auth, 0);
619 + if (!proxy_auth.password && !proxy_auth.credential)
620 + credential_fill(&proxy_auth, 1);
621 set_proxyauth_name_password(result);
622 }
623
@@ -1468,7 +1489,7 @@ struct active_request_slot *get_active_slot(void)
1489
1490 curl_easy_setopt(slot->curl, CURLOPT_IPRESOLVE, git_curl_ipresolve);
1491 curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, http_auth_methods);
1471 - if (http_auth.password || curl_empty_auth_enabled())
1492 + if (http_auth.password || http_auth.credential || curl_empty_auth_enabled())
1493 init_curl_http_auth(slot->curl);
1494
1495 return slot;
@@ -1757,7 +1778,8 @@ static int handle_curl_result(struct slot_results *results)
1778 } else if (missing_target(results))
1779 return HTTP_MISSING_TARGET;
1780 else if (results->http_code == 401) {
1760 - if (http_auth.username && http_auth.password) {
1781 + if ((http_auth.username && http_auth.password) ||\
1782 + (http_auth.authtype && http_auth.credential)) {
1783 credential_reject(&http_auth);
1784 return HTTP_NOAUTH;
1785 } else {
@@ -2065,11 +2087,15 @@ static int http_request(const char *url,
2087 /* Add additional headers here */
2088 if (options && options->extra_headers) {
2089 const struct string_list_item *item;
2068 - for_each_string_list_item(item, options->extra_headers) {
2069 - headers = curl_slist_append(headers, item->string);
2090 + if (options && options->extra_headers) {
2091 + for_each_string_list_item(item, options->extra_headers) {
2092 + headers = curl_slist_append(headers, item->string);
2093 + }
2094 }
2095 }
2096
2097 + headers = http_append_auth_header(&http_auth, headers);
2098 +
2099 curl_easy_setopt(slot->curl, CURLOPT_URL, url);
2100 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
2101 curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
@@ -2190,7 +2216,7 @@ static int http_request_reauth(const char *url,
2216 BUG("Unknown http_request target");
2217 }
2218
2193 - credential_fill(&http_auth, 0);
2219 + credential_fill(&http_auth, 1);
2220
2221 return http_request(url, result, target, options);
2222 }
http.h
+3
@@ -175,6 +175,9 @@ int http_get_file(const char *url, const char *filename,
175
176 int http_fetch_ref(const char *base, struct ref *ref);
177
178 +struct curl_slist *http_append_auth_header(const struct credential *c,
179 + struct curl_slist *headers);
180 +
181 /* Helpers for fetching packs */
182 int http_get_info_packs(const char *base_url,
183 struct packed_git **packs_head);
remote-curl.c
+3 -1
@@ -931,7 +931,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
931 if (err != HTTP_OK)
932 return -1;
933
934 - if (results.auth_avail & CURLAUTH_GSSNEGOTIATE)
934 + if (results.auth_avail & CURLAUTH_GSSNEGOTIATE || http_auth.authtype)
935 needs_100_continue = 1;
936 }
937
@@ -942,6 +942,8 @@ retry:
942 headers = curl_slist_append(headers, needs_100_continue ?
943 "Expect: 100-continue" : "Expect:");
944
945 + headers = http_append_auth_header(&http_auth, headers);
946 +
947 /* Add Accept-Language header */
948 if (rpc->hdr_accept_language)
949 headers = curl_slist_append(headers, rpc->hdr_accept_language);
t/t5563-simple-http-auth.sh
+133
@@ -74,6 +74,7 @@ test_expect_success 'access using basic auth' '
74 git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
75
76 expect_credential_query get <<-EOF &&
77 + capability[]=authtype
78 protocol=http
79 host=$HTTPD_DEST
80 wwwauth[]=Basic realm="example.com"
@@ -87,6 +88,43 @@ test_expect_success 'access using basic auth' '
88 EOF
89 '
90
91 +test_expect_success 'access using basic auth via authtype' '
92 + test_when_finished "per_test_cleanup" &&
93 +
94 + set_credential_reply get <<-EOF &&
95 + capability[]=authtype
96 + authtype=Basic
97 + credential=YWxpY2U6c2VjcmV0LXBhc3N3ZA==
98 + EOF
99 +
100 + # Basic base64(alice:secret-passwd)
101 + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
102 + Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
103 + EOF
104 +
105 + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
106 + WWW-Authenticate: Basic realm="example.com"
107 + EOF
108 +
109 + test_config_global credential.helper test-helper &&
110 + GIT_CURL_VERBOSE=1 git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
111 +
112 + expect_credential_query get <<-EOF &&
113 + capability[]=authtype
114 + protocol=http
115 + host=$HTTPD_DEST
116 + wwwauth[]=Basic realm="example.com"
117 + EOF
118 +
119 + expect_credential_query store <<-EOF
120 + capability[]=authtype
121 + authtype=Basic
122 + credential=YWxpY2U6c2VjcmV0LXBhc3N3ZA==
123 + protocol=http
124 + host=$HTTPD_DEST
125 + EOF
126 +'
127 +
128 test_expect_success 'access using basic auth invalid credentials' '
129 test_when_finished "per_test_cleanup" &&
130
@@ -108,6 +146,7 @@ test_expect_success 'access using basic auth invalid credentials' '
146 test_must_fail git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
147
148 expect_credential_query get <<-EOF &&
149 + capability[]=authtype
150 protocol=http
151 host=$HTTPD_DEST
152 wwwauth[]=Basic realm="example.com"
@@ -145,6 +184,7 @@ test_expect_success 'access using basic auth with extra challenges' '
184 git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
185
186 expect_credential_query get <<-EOF &&
187 + capability[]=authtype
188 protocol=http
189 host=$HTTPD_DEST
190 wwwauth[]=FooBar param1="value1" param2="value2"
@@ -183,6 +223,7 @@ test_expect_success 'access using basic auth mixed-case wwwauth header name' '
223 git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
224
225 expect_credential_query get <<-EOF &&
226 + capability[]=authtype
227 protocol=http
228 host=$HTTPD_DEST
229 wwwauth[]=foobar param1="value1" param2="value2"
@@ -226,6 +267,7 @@ test_expect_success 'access using basic auth with wwwauth header continuations'
267 git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
268
269 expect_credential_query get <<-EOF &&
270 + capability[]=authtype
271 protocol=http
272 host=$HTTPD_DEST
273 wwwauth[]=FooBar param1="value1" param2="value2"
@@ -271,6 +313,7 @@ test_expect_success 'access using basic auth with wwwauth header empty continuat
313 git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
314
315 expect_credential_query get <<-EOF &&
316 + capability[]=authtype
317 protocol=http
318 host=$HTTPD_DEST
319 wwwauth[]=FooBar param1="value1" param2="value2"
@@ -312,6 +355,7 @@ test_expect_success 'access using basic auth with wwwauth header mixed line-endi
355 git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
356
357 expect_credential_query get <<-EOF &&
358 + capability[]=authtype
359 protocol=http
360 host=$HTTPD_DEST
361 wwwauth[]=FooBar param1="value1" param2="value2"
@@ -326,4 +370,93 @@ test_expect_success 'access using basic auth with wwwauth header mixed line-endi
370 EOF
371 '
372
373 +test_expect_success 'access using bearer auth' '
374 + test_when_finished "per_test_cleanup" &&
375 +
376 + set_credential_reply get <<-EOF &&
377 + capability[]=authtype
378 + authtype=Bearer
379 + credential=YS1naXQtdG9rZW4=
380 + EOF
381 +
382 + # Basic base64(a-git-token)
383 + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
384 + Bearer YS1naXQtdG9rZW4=
385 + EOF
386 +
387 + CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
388 +
389 + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
390 + WWW-Authenticate: FooBar param1="value1" param2="value2"
391 + WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0
392 + WWW-Authenticate: Basic realm="example.com"
393 + EOF
394 +
395 + test_config_global credential.helper test-helper &&
396 + git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
397 +
398 + expect_credential_query get <<-EOF &&
399 + capability[]=authtype
400 + protocol=http
401 + host=$HTTPD_DEST
402 + wwwauth[]=FooBar param1="value1" param2="value2"
403 + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
404 + wwwauth[]=Basic realm="example.com"
405 + EOF
406 +
407 + expect_credential_query store <<-EOF
408 + capability[]=authtype
409 + authtype=Bearer
410 + credential=YS1naXQtdG9rZW4=
411 + protocol=http
412 + host=$HTTPD_DEST
413 + EOF
414 +'
415 +
416 +test_expect_success 'access using bearer auth with invalid credentials' '
417 + test_when_finished "per_test_cleanup" &&
418 +
419 + set_credential_reply get <<-EOF &&
420 + capability[]=authtype
421 + authtype=Bearer
422 + credential=incorrect-token
423 + EOF
424 +
425 + # Basic base64(a-git-token)
426 + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
427 + Bearer YS1naXQtdG9rZW4=
428 + EOF
429 +
430 + CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
431 +
432 + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
433 + WWW-Authenticate: FooBar param1="value1" param2="value2"
434 + WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0
435 + WWW-Authenticate: Basic realm="example.com"
436 + EOF
437 +
438 + test_config_global credential.helper test-helper &&
439 + test_must_fail git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
440 +
441 + expect_credential_query get <<-EOF &&
442 + capability[]=authtype
443 + protocol=http
444 + host=$HTTPD_DEST
445 + wwwauth[]=FooBar param1="value1" param2="value2"
446 + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
447 + wwwauth[]=Basic realm="example.com"
448 + EOF
449 +
450 + expect_credential_query erase <<-EOF
451 + capability[]=authtype
452 + authtype=Bearer
453 + credential=incorrect-token
454 + protocol=http
455 + host=$HTTPD_DEST
456 + wwwauth[]=FooBar param1="value1" param2="value2"
457 + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
458 + wwwauth[]=Basic realm="example.com"
459 + EOF
460 +'
461 +
462 test_done