Skip to content

Commit

Permalink
Merge branch 'bc/http-proactive-auth'
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gitster committed Jul 16, 2024
2 parents 12d49fd + 610cbc1 commit fe5ba89
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 6 deletions.
20 changes: 20 additions & 0 deletions Documentation/config/http.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,26 @@ http.emptyAuth::
a username in the URL, as libcurl normally requires a username for
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::
Control GSSAPI credential delegation. The delegation is disabled
by default in libcurl since version 7.21.7. Set parameter to tell
Expand Down
62 changes: 56 additions & 6 deletions http.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,19 @@ static struct {
};
#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 const char *curl_proxyuserpwd;
static char *curl_cookie_file;
static int curl_save_cookies;
struct credential http_auth = CREDENTIAL_INIT;
static int http_proactive_auth;
static enum proactive_auth http_proactive_auth;
static char *user_agent;
static int curl_empty_auth = -1;

Expand Down Expand Up @@ -148,6 +155,12 @@ static int http_schannel_check_revoke = 1;
*/
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 size = eltsize * nmemb;
Expand Down Expand Up @@ -539,6 +552,20 @@ static int http_options(const char *var, const char *value,
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 */
return git_default_config(var, value, ctx, data);
}
Expand Down Expand Up @@ -580,14 +607,29 @@ static void init_curl_http_auth(CURL *result)
{
if ((!http_auth.username || !*http_auth.username) &&
(!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, ":");
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);

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_PASSWORD, http_auth.password);
}
Expand Down Expand Up @@ -1050,7 +1092,7 @@ static CURL *get_curl_handle(void)
#endif
}

if (http_proactive_auth)
if (http_proactive_auth != PROACTIVE_AUTH_NONE)
init_curl_http_auth(result);

if (getenv("GIT_SSL_VERSION"))
Expand Down Expand Up @@ -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)
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)
curl_http_proxy = xstrdup(remote->http_proxy);
Expand Down Expand Up @@ -1790,6 +1833,8 @@ static int handle_curl_result(struct slot_results *results)
return HTTP_REAUTH;
}
credential_reject(&http_auth);
if (always_auth_proactively())
http_proactive_auth = PROACTIVE_AUTH_NONE;
return HTTP_NOAUTH;
} else {
http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
Expand Down Expand Up @@ -2186,7 +2231,12 @@ static int http_request_reauth(const char *url,
struct http_get_options *options)
{
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)
return ret;
Expand Down
116 changes: 116 additions & 0 deletions t/t5563-simple-http-auth.sh
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,122 @@ test_expect_success 'access using basic auth invalid credentials' '
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_when_finished "per_test_cleanup" &&
Expand Down

0 comments on commit fe5ba89

Please sign in to comment.