1
0
mirror of https://github.com/git/git.git synced 2024-11-08 15:19:28 +01:00

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>
This commit is contained in:
brian m. carlson 2024-04-17 00:02:32 +00:00 committed by Junio C Hamano
parent 5af5cc68aa
commit ad9bb6dfe6
4 changed files with 176 additions and 12 deletions

48
http.c

@ -561,18 +561,34 @@ static int curl_empty_auth_enabled(void)
return 0; return 0;
} }
struct curl_slist *http_append_auth_header(const struct credential *c,
struct curl_slist *headers)
{
if (c->authtype && c->credential) {
struct strbuf auth = STRBUF_INIT;
strbuf_addf(&auth, "Authorization: %s %s",
c->authtype, c->credential);
headers = curl_slist_append(headers, auth.buf);
strbuf_release(&auth);
}
return headers;
}
static void init_curl_http_auth(CURL *result) static void init_curl_http_auth(CURL *result)
{ {
if (!http_auth.username || !*http_auth.username) { if ((!http_auth.username || !*http_auth.username) &&
(!http_auth.credential || !*http_auth.credential)) {
if (curl_empty_auth_enabled()) if (curl_empty_auth_enabled())
curl_easy_setopt(result, CURLOPT_USERPWD, ":"); curl_easy_setopt(result, CURLOPT_USERPWD, ":");
return; return;
} }
credential_fill(&http_auth, 0); credential_fill(&http_auth, 1);
curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username); if (http_auth.password) {
curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password); curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);
curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);
}
} }
/* *var must be free-able */ /* *var must be free-able */
@ -586,17 +602,22 @@ static void var_override(const char **var, char *value)
static void set_proxyauth_name_password(CURL *result) static void set_proxyauth_name_password(CURL *result)
{ {
if (proxy_auth.password) {
curl_easy_setopt(result, CURLOPT_PROXYUSERNAME, curl_easy_setopt(result, CURLOPT_PROXYUSERNAME,
proxy_auth.username); proxy_auth.username);
curl_easy_setopt(result, CURLOPT_PROXYPASSWORD, curl_easy_setopt(result, CURLOPT_PROXYPASSWORD,
proxy_auth.password); proxy_auth.password);
} else if (proxy_auth.authtype && proxy_auth.credential) {
curl_easy_setopt(result, CURLOPT_PROXYHEADER,
http_append_auth_header(&proxy_auth, NULL));
}
} }
static void init_curl_proxy_auth(CURL *result) static void init_curl_proxy_auth(CURL *result)
{ {
if (proxy_auth.username) { if (proxy_auth.username) {
if (!proxy_auth.password) if (!proxy_auth.password && !proxy_auth.credential)
credential_fill(&proxy_auth, 0); credential_fill(&proxy_auth, 1);
set_proxyauth_name_password(result); set_proxyauth_name_password(result);
} }
@ -1468,7 +1489,7 @@ struct active_request_slot *get_active_slot(void)
curl_easy_setopt(slot->curl, CURLOPT_IPRESOLVE, git_curl_ipresolve); curl_easy_setopt(slot->curl, CURLOPT_IPRESOLVE, git_curl_ipresolve);
curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, http_auth_methods); curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, http_auth_methods);
if (http_auth.password || curl_empty_auth_enabled()) if (http_auth.password || http_auth.credential || curl_empty_auth_enabled())
init_curl_http_auth(slot->curl); init_curl_http_auth(slot->curl);
return slot; return slot;
@ -1757,7 +1778,8 @@ static int handle_curl_result(struct slot_results *results)
} else if (missing_target(results)) } else if (missing_target(results))
return HTTP_MISSING_TARGET; return HTTP_MISSING_TARGET;
else if (results->http_code == 401) { else if (results->http_code == 401) {
if (http_auth.username && http_auth.password) { if ((http_auth.username && http_auth.password) ||\
(http_auth.authtype && http_auth.credential)) {
credential_reject(&http_auth); credential_reject(&http_auth);
return HTTP_NOAUTH; return HTTP_NOAUTH;
} else { } else {
@ -2065,11 +2087,15 @@ static int http_request(const char *url,
/* Add additional headers here */ /* Add additional headers here */
if (options && options->extra_headers) { if (options && options->extra_headers) {
const struct string_list_item *item; const struct string_list_item *item;
for_each_string_list_item(item, options->extra_headers) { if (options && options->extra_headers) {
headers = curl_slist_append(headers, item->string); for_each_string_list_item(item, options->extra_headers) {
headers = curl_slist_append(headers, item->string);
}
} }
} }
headers = http_append_auth_header(&http_auth, headers);
curl_easy_setopt(slot->curl, CURLOPT_URL, url); curl_easy_setopt(slot->curl, CURLOPT_URL, url);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(slot->curl, CURLOPT_ENCODING, ""); curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
@ -2190,7 +2216,7 @@ static int http_request_reauth(const char *url,
BUG("Unknown http_request target"); BUG("Unknown http_request target");
} }
credential_fill(&http_auth, 0); credential_fill(&http_auth, 1);
return http_request(url, result, target, options); return http_request(url, result, target, options);
} }

3
http.h

@ -175,6 +175,9 @@ int http_get_file(const char *url, const char *filename,
int http_fetch_ref(const char *base, struct ref *ref); int http_fetch_ref(const char *base, struct ref *ref);
struct curl_slist *http_append_auth_header(const struct credential *c,
struct curl_slist *headers);
/* Helpers for fetching packs */ /* Helpers for fetching packs */
int http_get_info_packs(const char *base_url, int http_get_info_packs(const char *base_url,
struct packed_git **packs_head); struct packed_git **packs_head);

@ -931,7 +931,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
if (err != HTTP_OK) if (err != HTTP_OK)
return -1; return -1;
if (results.auth_avail & CURLAUTH_GSSNEGOTIATE) if (results.auth_avail & CURLAUTH_GSSNEGOTIATE || http_auth.authtype)
needs_100_continue = 1; needs_100_continue = 1;
} }
@ -942,6 +942,8 @@ retry:
headers = curl_slist_append(headers, needs_100_continue ? headers = curl_slist_append(headers, needs_100_continue ?
"Expect: 100-continue" : "Expect:"); "Expect: 100-continue" : "Expect:");
headers = http_append_auth_header(&http_auth, headers);
/* Add Accept-Language header */ /* Add Accept-Language header */
if (rpc->hdr_accept_language) if (rpc->hdr_accept_language)
headers = curl_slist_append(headers, rpc->hdr_accept_language); headers = curl_slist_append(headers, rpc->hdr_accept_language);

@ -74,6 +74,7 @@ test_expect_success 'access using basic auth' '
git ls-remote "$HTTPD_URL/custom_auth/repo.git" && git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF && expect_credential_query get <<-EOF &&
capability[]=authtype
protocol=http protocol=http
host=$HTTPD_DEST host=$HTTPD_DEST
wwwauth[]=Basic realm="example.com" wwwauth[]=Basic realm="example.com"
@ -87,6 +88,43 @@ test_expect_success 'access using basic auth' '
EOF EOF
' '
test_expect_success 'access using basic auth via authtype' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
capability[]=authtype
authtype=Basic
credential=YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
WWW-Authenticate: Basic realm="example.com"
EOF
test_config_global credential.helper test-helper &&
GIT_CURL_VERBOSE=1 git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
protocol=http
host=$HTTPD_DEST
wwwauth[]=Basic realm="example.com"
EOF
expect_credential_query store <<-EOF
capability[]=authtype
authtype=Basic
credential=YWxpY2U6c2VjcmV0LXBhc3N3ZA==
protocol=http
host=$HTTPD_DEST
EOF
'
test_expect_success 'access using basic auth invalid credentials' ' test_expect_success 'access using basic auth invalid credentials' '
test_when_finished "per_test_cleanup" && test_when_finished "per_test_cleanup" &&
@ -108,6 +146,7 @@ test_expect_success 'access using basic auth invalid credentials' '
test_must_fail git ls-remote "$HTTPD_URL/custom_auth/repo.git" && test_must_fail git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF && expect_credential_query get <<-EOF &&
capability[]=authtype
protocol=http protocol=http
host=$HTTPD_DEST host=$HTTPD_DEST
wwwauth[]=Basic realm="example.com" wwwauth[]=Basic realm="example.com"
@ -145,6 +184,7 @@ test_expect_success 'access using basic auth with extra challenges' '
git ls-remote "$HTTPD_URL/custom_auth/repo.git" && git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF && expect_credential_query get <<-EOF &&
capability[]=authtype
protocol=http protocol=http
host=$HTTPD_DEST host=$HTTPD_DEST
wwwauth[]=FooBar param1="value1" param2="value2" wwwauth[]=FooBar param1="value1" param2="value2"
@ -183,6 +223,7 @@ test_expect_success 'access using basic auth mixed-case wwwauth header name' '
git ls-remote "$HTTPD_URL/custom_auth/repo.git" && git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF && expect_credential_query get <<-EOF &&
capability[]=authtype
protocol=http protocol=http
host=$HTTPD_DEST host=$HTTPD_DEST
wwwauth[]=foobar param1="value1" param2="value2" wwwauth[]=foobar param1="value1" param2="value2"
@ -226,6 +267,7 @@ test_expect_success 'access using basic auth with wwwauth header continuations'
git ls-remote "$HTTPD_URL/custom_auth/repo.git" && git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF && expect_credential_query get <<-EOF &&
capability[]=authtype
protocol=http protocol=http
host=$HTTPD_DEST host=$HTTPD_DEST
wwwauth[]=FooBar param1="value1" param2="value2" wwwauth[]=FooBar param1="value1" param2="value2"
@ -271,6 +313,7 @@ test_expect_success 'access using basic auth with wwwauth header empty continuat
git ls-remote "$HTTPD_URL/custom_auth/repo.git" && git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF && expect_credential_query get <<-EOF &&
capability[]=authtype
protocol=http protocol=http
host=$HTTPD_DEST host=$HTTPD_DEST
wwwauth[]=FooBar param1="value1" param2="value2" wwwauth[]=FooBar param1="value1" param2="value2"
@ -312,6 +355,7 @@ test_expect_success 'access using basic auth with wwwauth header mixed line-endi
git ls-remote "$HTTPD_URL/custom_auth/repo.git" && git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF && expect_credential_query get <<-EOF &&
capability[]=authtype
protocol=http protocol=http
host=$HTTPD_DEST host=$HTTPD_DEST
wwwauth[]=FooBar param1="value1" param2="value2" wwwauth[]=FooBar param1="value1" param2="value2"
@ -326,4 +370,93 @@ test_expect_success 'access using basic auth with wwwauth header mixed line-endi
EOF EOF
' '
test_expect_success 'access using bearer auth' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
capability[]=authtype
authtype=Bearer
credential=YS1naXQtdG9rZW4=
EOF
# Basic base64(a-git-token)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
Bearer YS1naXQtdG9rZW4=
EOF
CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
WWW-Authenticate: FooBar param1="value1" param2="value2"
WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0
WWW-Authenticate: Basic realm="example.com"
EOF
test_config_global credential.helper test-helper &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
protocol=http
host=$HTTPD_DEST
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
EOF
'
test_expect_success 'access using bearer auth with invalid credentials' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
capability[]=authtype
authtype=Bearer
credential=incorrect-token
EOF
# Basic base64(a-git-token)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
Bearer YS1naXQtdG9rZW4=
EOF
CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
WWW-Authenticate: FooBar param1="value1" param2="value2"
WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0
WWW-Authenticate: Basic realm="example.com"
EOF
test_config_global credential.helper test-helper &&
test_must_fail git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
protocol=http
host=$HTTPD_DEST
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 erase <<-EOF
capability[]=authtype
authtype=Bearer
credential=incorrect-token
protocol=http
host=$HTTPD_DEST
wwwauth[]=FooBar param1="value1" param2="value2"
wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
wwwauth[]=Basic realm="example.com"
EOF
'
test_done test_done