Skip to content

Commit 610cbc1

Browse files
bk2204gitster
authored andcommitted
http: allow authenticating proactively
When making a request over HTTP(S), Git only sends authentication if it receives a 401 response. Thus, if a repository is open to the public for reading, Git will typically never ask for authentication for fetches and clones. However, there may be times when a user would like to authenticate nevertheless. For example, a forge may give higher rate limits to users who authenticate because they are easier to contact in case of excessive use. Or it may be useful for a known heavy user, such as an internal service, to proactively authenticate so its use can be monitored and, if necessary, throttled. Let's make this possible with a new option, "http.proactiveAuth". This option specifies a type of authentication which can be used to authenticate against the host in question. This is necessary because we lack the WWW-Authenticate header to provide us details; similarly, we cannot accept certain types of authentication because we require information from the server, such as a nonce or challenge, to successfully authenticate. If we're in auto mode and we got a username and password, set the authentication scheme to Basic. libcurl will not send authentication proactively unless there's a single choice of allowed authentication, and we know in this case we didn't get an authtype entry telling us what scheme to use, or we would have taken a different codepath and written the header ourselves. In any event, of the other schemes that libcurl supports, Digest and NTLM require a nonce or challenge, which means that they cannot work with proactive auth, and GSSAPI does not use a username and password at all, so Basic is the only logical choice among the built-in options. Note that the existing http_proactive_auth variable signifies proactive auth if there are already credentials, which is different from the functionality we're adding, which always seeks credentials even if none are provided. Nonetheless, t5540 tests the existing behavior for WebDAV-based pushes to an open repository without credentials, so we preserve it. While at first this may seem an insecure and bizarre decision, it may be that authentication is done with TLS certificates, in which case it might actually provide a quite high level of security. Expand the variable to use an enum to handle the additional cases and a helper function to distinguish our new cases from the old ones. Signed-off-by: brian m. carlson <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 4216329 commit 610cbc1

File tree

3 files changed

+192
-6
lines changed

3 files changed

+192
-6
lines changed

Documentation/config/http.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,26 @@ http.emptyAuth::
5656
a username in the URL, as libcurl normally requires a username for
5757
authentication.
5858

59+
http.proactiveAuth::
60+
Attempt authentication without first making an unauthenticated attempt and
61+
receiving a 401 response. This can be used to ensure that all requests are
62+
authenticated. If `http.emptyAuth` is set to true, this value has no effect.
63+
+
64+
If the credential helper used specifies an authentication scheme (i.e., via the
65+
`authtype` field), that value will be used; if a username and password is
66+
provided without a scheme, then Basic authentication is used. The value of the
67+
option determines the scheme requested from the helper. Possible values are:
68+
+
69+
--
70+
* `basic` - Request Basic authentication from the helper.
71+
* `auto` - Allow the helper to pick an appropriate scheme.
72+
* `none` - Disable proactive authentication.
73+
--
74+
+
75+
Note that TLS should always be used with this configuration, since otherwise it
76+
is easy to accidentally expose plaintext credentials if Basic authentication
77+
is selected.
78+
5979
http.delegation::
6080
Control GSSAPI credential delegation. The delegation is disabled
6181
by default in libcurl since version 7.21.7. Set parameter to tell

http.c

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,19 @@ static struct {
106106
};
107107
#endif
108108

109+
enum proactive_auth {
110+
PROACTIVE_AUTH_NONE = 0,
111+
PROACTIVE_AUTH_IF_CREDENTIALS,
112+
PROACTIVE_AUTH_AUTO,
113+
PROACTIVE_AUTH_BASIC,
114+
};
115+
109116
static struct credential proxy_auth = CREDENTIAL_INIT;
110117
static const char *curl_proxyuserpwd;
111118
static char *curl_cookie_file;
112119
static int curl_save_cookies;
113120
struct credential http_auth = CREDENTIAL_INIT;
114-
static int http_proactive_auth;
121+
static enum proactive_auth http_proactive_auth;
115122
static char *user_agent;
116123
static int curl_empty_auth = -1;
117124

@@ -146,6 +153,12 @@ static int http_schannel_check_revoke = 1;
146153
*/
147154
static int http_schannel_use_ssl_cainfo;
148155

156+
static int always_auth_proactively(void)
157+
{
158+
return http_proactive_auth != PROACTIVE_AUTH_NONE &&
159+
http_proactive_auth != PROACTIVE_AUTH_IF_CREDENTIALS;
160+
}
161+
149162
size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
150163
{
151164
size_t size = eltsize * nmemb;
@@ -537,6 +550,20 @@ static int http_options(const char *var, const char *value,
537550
return 0;
538551
}
539552

553+
if (!strcmp("http.proactiveauth", var)) {
554+
if (!value)
555+
return config_error_nonbool(var);
556+
if (!strcmp(value, "auto"))
557+
http_proactive_auth = PROACTIVE_AUTH_AUTO;
558+
else if (!strcmp(value, "basic"))
559+
http_proactive_auth = PROACTIVE_AUTH_BASIC;
560+
else if (!strcmp(value, "none"))
561+
http_proactive_auth = PROACTIVE_AUTH_NONE;
562+
else
563+
warning(_("Unknown value for http.proactiveauth"));
564+
return 0;
565+
}
566+
540567
/* Fall back on the default ones */
541568
return git_default_config(var, value, ctx, data);
542569
}
@@ -578,14 +605,29 @@ static void init_curl_http_auth(CURL *result)
578605
{
579606
if ((!http_auth.username || !*http_auth.username) &&
580607
(!http_auth.credential || !*http_auth.credential)) {
581-
if (curl_empty_auth_enabled())
608+
int empty_auth = curl_empty_auth_enabled();
609+
if ((empty_auth != -1 && !always_auth_proactively()) || empty_auth == 1) {
582610
curl_easy_setopt(result, CURLOPT_USERPWD, ":");
583-
return;
611+
return;
612+
} else if (!always_auth_proactively()) {
613+
return;
614+
} else if (http_proactive_auth == PROACTIVE_AUTH_BASIC) {
615+
strvec_push(&http_auth.wwwauth_headers, "Basic");
616+
}
584617
}
585618

586619
credential_fill(&http_auth, 1);
587620

588621
if (http_auth.password) {
622+
if (always_auth_proactively()) {
623+
/*
624+
* We got a credential without an authtype and we don't
625+
* know what's available. Since our only two options at
626+
* the moment are auto (which defaults to basic) and
627+
* basic, use basic for now.
628+
*/
629+
curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
630+
}
589631
curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);
590632
curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);
591633
}
@@ -1048,7 +1090,7 @@ static CURL *get_curl_handle(void)
10481090
#endif
10491091
}
10501092

1051-
if (http_proactive_auth)
1093+
if (http_proactive_auth != PROACTIVE_AUTH_NONE)
10521094
init_curl_http_auth(result);
10531095

10541096
if (getenv("GIT_SSL_VERSION"))
@@ -1292,7 +1334,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
12921334
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
12931335
die("curl_global_init failed");
12941336

1295-
http_proactive_auth = proactive_auth;
1337+
if (proactive_auth && http_proactive_auth == PROACTIVE_AUTH_NONE)
1338+
http_proactive_auth = PROACTIVE_AUTH_IF_CREDENTIALS;
12961339

12971340
if (remote && remote->http_proxy)
12981341
curl_http_proxy = xstrdup(remote->http_proxy);
@@ -1788,6 +1831,8 @@ static int handle_curl_result(struct slot_results *results)
17881831
return HTTP_REAUTH;
17891832
}
17901833
credential_reject(&http_auth);
1834+
if (always_auth_proactively())
1835+
http_proactive_auth = PROACTIVE_AUTH_NONE;
17911836
return HTTP_NOAUTH;
17921837
} else {
17931838
http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
@@ -2184,7 +2229,12 @@ static int http_request_reauth(const char *url,
21842229
struct http_get_options *options)
21852230
{
21862231
int i = 3;
2187-
int ret = http_request(url, result, target, options);
2232+
int ret;
2233+
2234+
if (always_auth_proactively())
2235+
credential_fill(&http_auth, 1);
2236+
2237+
ret = http_request(url, result, target, options);
21882238

21892239
if (ret != HTTP_OK && ret != HTTP_REAUTH)
21902240
return ret;

t/t5563-simple-http-auth.sh

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,122 @@ test_expect_success 'access using basic auth invalid credentials' '
178178
EOF
179179
'
180180

181+
test_expect_success 'access using basic proactive auth' '
182+
test_when_finished "per_test_cleanup" &&
183+
184+
set_credential_reply get <<-EOF &&
185+
username=alice
186+
password=secret-passwd
187+
EOF
188+
189+
# Basic base64(alice:secret-passwd)
190+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
191+
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
192+
EOF
193+
194+
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
195+
id=1 status=200
196+
id=default status=403
197+
EOF
198+
199+
test_config_global credential.helper test-helper &&
200+
test_config_global http.proactiveAuth basic &&
201+
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
202+
203+
expect_credential_query get <<-EOF &&
204+
capability[]=authtype
205+
capability[]=state
206+
protocol=http
207+
host=$HTTPD_DEST
208+
wwwauth[]=Basic
209+
EOF
210+
211+
expect_credential_query store <<-EOF
212+
protocol=http
213+
host=$HTTPD_DEST
214+
username=alice
215+
password=secret-passwd
216+
EOF
217+
'
218+
219+
test_expect_success 'access using auto proactive auth with basic default' '
220+
test_when_finished "per_test_cleanup" &&
221+
222+
set_credential_reply get <<-EOF &&
223+
username=alice
224+
password=secret-passwd
225+
EOF
226+
227+
# Basic base64(alice:secret-passwd)
228+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
229+
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
230+
EOF
231+
232+
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
233+
id=1 status=200
234+
id=default status=403
235+
EOF
236+
237+
test_config_global credential.helper test-helper &&
238+
test_config_global http.proactiveAuth auto &&
239+
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
240+
241+
expect_credential_query get <<-EOF &&
242+
capability[]=authtype
243+
capability[]=state
244+
protocol=http
245+
host=$HTTPD_DEST
246+
EOF
247+
248+
expect_credential_query store <<-EOF
249+
protocol=http
250+
host=$HTTPD_DEST
251+
username=alice
252+
password=secret-passwd
253+
EOF
254+
'
255+
256+
test_expect_success 'access using auto proactive auth with authtype from credential helper' '
257+
test_when_finished "per_test_cleanup" &&
258+
259+
set_credential_reply get <<-EOF &&
260+
capability[]=authtype
261+
authtype=Bearer
262+
credential=YS1naXQtdG9rZW4=
263+
EOF
264+
265+
# Basic base64(a-git-token)
266+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
267+
id=1 creds=Bearer YS1naXQtdG9rZW4=
268+
EOF
269+
270+
CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
271+
272+
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
273+
id=1 status=200
274+
id=default status=403
275+
EOF
276+
277+
test_config_global credential.helper test-helper &&
278+
test_config_global http.proactiveAuth auto &&
279+
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
280+
281+
expect_credential_query get <<-EOF &&
282+
capability[]=authtype
283+
capability[]=state
284+
protocol=http
285+
host=$HTTPD_DEST
286+
EOF
287+
288+
expect_credential_query store <<-EOF
289+
capability[]=authtype
290+
authtype=Bearer
291+
credential=YS1naXQtdG9rZW4=
292+
protocol=http
293+
host=$HTTPD_DEST
294+
EOF
295+
'
296+
181297
test_expect_success 'access using basic auth with extra challenges' '
182298
test_when_finished "per_test_cleanup" &&
183299

0 commit comments

Comments
 (0)