Merge branch 'bc/http-proactive-auth'

The http transport can now be told to send request with
authentication material without first getting a 401 response.

* bc/http-proactive-auth:
  http: allow authenticating proactively
This commit is contained in:
Junio C Hamano
2024-07-16 11:18:57 -07:00
3 changed files with 192 additions and 6 deletions

View File

@ -56,6 +56,26 @@ http.emptyAuth::
a username in the URL, as libcurl normally requires a username for a username in the URL, as libcurl normally requires a username for
authentication. authentication.
http.proactiveAuth::
Attempt authentication without first making an unauthenticated attempt and
receiving a 401 response. This can be used to ensure that all requests are
authenticated. If `http.emptyAuth` is set to true, this value has no effect.
+
If the credential helper used specifies an authentication scheme (i.e., via the
`authtype` field), that value will be used; if a username and password is
provided without a scheme, then Basic authentication is used. The value of the
option determines the scheme requested from the helper. Possible values are:
+
--
* `basic` - Request Basic authentication from the helper.
* `auto` - Allow the helper to pick an appropriate scheme.
* `none` - Disable proactive authentication.
--
+
Note that TLS should always be used with this configuration, since otherwise it
is easy to accidentally expose plaintext credentials if Basic authentication
is selected.
http.delegation:: http.delegation::
Control GSSAPI credential delegation. The delegation is disabled Control GSSAPI credential delegation. The delegation is disabled
by default in libcurl since version 7.21.7. Set parameter to tell by default in libcurl since version 7.21.7. Set parameter to tell

62
http.c
View File

@ -108,12 +108,19 @@ static struct {
}; };
#endif #endif
enum proactive_auth {
PROACTIVE_AUTH_NONE = 0,
PROACTIVE_AUTH_IF_CREDENTIALS,
PROACTIVE_AUTH_AUTO,
PROACTIVE_AUTH_BASIC,
};
static struct credential proxy_auth = CREDENTIAL_INIT; static struct credential proxy_auth = CREDENTIAL_INIT;
static const char *curl_proxyuserpwd; static const char *curl_proxyuserpwd;
static char *curl_cookie_file; static char *curl_cookie_file;
static int curl_save_cookies; static int curl_save_cookies;
struct credential http_auth = CREDENTIAL_INIT; struct credential http_auth = CREDENTIAL_INIT;
static int http_proactive_auth; static enum proactive_auth http_proactive_auth;
static char *user_agent; static char *user_agent;
static int curl_empty_auth = -1; static int curl_empty_auth = -1;
@ -148,6 +155,12 @@ static int http_schannel_check_revoke = 1;
*/ */
static int http_schannel_use_ssl_cainfo; static int http_schannel_use_ssl_cainfo;
static int always_auth_proactively(void)
{
return http_proactive_auth != PROACTIVE_AUTH_NONE &&
http_proactive_auth != PROACTIVE_AUTH_IF_CREDENTIALS;
}
size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
{ {
size_t size = eltsize * nmemb; size_t size = eltsize * nmemb;
@ -539,6 +552,20 @@ static int http_options(const char *var, const char *value,
return 0; return 0;
} }
if (!strcmp("http.proactiveauth", var)) {
if (!value)
return config_error_nonbool(var);
if (!strcmp(value, "auto"))
http_proactive_auth = PROACTIVE_AUTH_AUTO;
else if (!strcmp(value, "basic"))
http_proactive_auth = PROACTIVE_AUTH_BASIC;
else if (!strcmp(value, "none"))
http_proactive_auth = PROACTIVE_AUTH_NONE;
else
warning(_("Unknown value for http.proactiveauth"));
return 0;
}
/* Fall back on the default ones */ /* Fall back on the default ones */
return git_default_config(var, value, ctx, data); return git_default_config(var, value, ctx, data);
} }
@ -580,14 +607,29 @@ 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)) { (!http_auth.credential || !*http_auth.credential)) {
if (curl_empty_auth_enabled()) int empty_auth = curl_empty_auth_enabled();
if ((empty_auth != -1 && !always_auth_proactively()) || empty_auth == 1) {
curl_easy_setopt(result, CURLOPT_USERPWD, ":"); curl_easy_setopt(result, CURLOPT_USERPWD, ":");
return; return;
} else if (!always_auth_proactively()) {
return;
} else if (http_proactive_auth == PROACTIVE_AUTH_BASIC) {
strvec_push(&http_auth.wwwauth_headers, "Basic");
}
} }
credential_fill(&http_auth, 1); credential_fill(&http_auth, 1);
if (http_auth.password) { if (http_auth.password) {
if (always_auth_proactively()) {
/*
* We got a credential without an authtype and we don't
* know what's available. Since our only two options at
* the moment are auto (which defaults to basic) and
* basic, use basic for now.
*/
curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
}
curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username); curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);
curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password); curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);
} }
@ -1050,7 +1092,7 @@ static CURL *get_curl_handle(void)
#endif #endif
} }
if (http_proactive_auth) if (http_proactive_auth != PROACTIVE_AUTH_NONE)
init_curl_http_auth(result); init_curl_http_auth(result);
if (getenv("GIT_SSL_VERSION")) if (getenv("GIT_SSL_VERSION"))
@ -1294,7 +1336,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
die("curl_global_init failed"); die("curl_global_init failed");
http_proactive_auth = proactive_auth; if (proactive_auth && http_proactive_auth == PROACTIVE_AUTH_NONE)
http_proactive_auth = PROACTIVE_AUTH_IF_CREDENTIALS;
if (remote && remote->http_proxy) if (remote && remote->http_proxy)
curl_http_proxy = xstrdup(remote->http_proxy); curl_http_proxy = xstrdup(remote->http_proxy);
@ -1790,6 +1833,8 @@ static int handle_curl_result(struct slot_results *results)
return HTTP_REAUTH; return HTTP_REAUTH;
} }
credential_reject(&http_auth); credential_reject(&http_auth);
if (always_auth_proactively())
http_proactive_auth = PROACTIVE_AUTH_NONE;
return HTTP_NOAUTH; return HTTP_NOAUTH;
} else { } else {
http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE; http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
@ -2186,7 +2231,12 @@ static int http_request_reauth(const char *url,
struct http_get_options *options) struct http_get_options *options)
{ {
int i = 3; int i = 3;
int ret = http_request(url, result, target, options); int ret;
if (always_auth_proactively())
credential_fill(&http_auth, 1);
ret = http_request(url, result, target, options);
if (ret != HTTP_OK && ret != HTTP_REAUTH) if (ret != HTTP_OK && ret != HTTP_REAUTH)
return ret; return ret;

View File

@ -178,6 +178,122 @@ test_expect_success 'access using basic auth invalid credentials' '
EOF EOF
' '
test_expect_success 'access using basic proactive auth' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default status=403
EOF
test_config_global credential.helper test-helper &&
test_config_global http.proactiveAuth basic &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=Basic
EOF
expect_credential_query store <<-EOF
protocol=http
host=$HTTPD_DEST
username=alice
password=secret-passwd
EOF
'
test_expect_success 'access using auto proactive auth with basic default' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default status=403
EOF
test_config_global credential.helper test-helper &&
test_config_global http.proactiveAuth auto &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
EOF
expect_credential_query store <<-EOF
protocol=http
host=$HTTPD_DEST
username=alice
password=secret-passwd
EOF
'
test_expect_success 'access using auto proactive auth with authtype from credential helper' '
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 &&
id=1 creds=Bearer YS1naXQtdG9rZW4=
EOF
CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default status=403
EOF
test_config_global credential.helper test-helper &&
test_config_global http.proactiveAuth auto &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
EOF
expect_credential_query store <<-EOF
capability[]=authtype
authtype=Bearer
credential=YS1naXQtdG9rZW4=
protocol=http
host=$HTTPD_DEST
EOF
'
test_expect_success 'access using basic auth with extra challenges' ' test_expect_success 'access using basic auth with extra challenges' '
test_when_finished "per_test_cleanup" && test_when_finished "per_test_cleanup" &&