Merge branch 'bc/credential-scheme-enhancement'

The credential helper protocol, together with the HTTP layer, have
been enhanced to support authentication schemes different from
username & password pair, like Bearer and NTLM.

* bc/credential-scheme-enhancement:
  credential: add method for querying capabilities
  credential-cache: implement authtype capability
  t: add credential tests for authtype
  credential: add support for multistage credential rounds
  t5563: refactor for multi-stage authentication
  docs: set a limit on credential line length
  credential: enable state capability
  credential: add an argument to keep state
  http: add support for authtype and credential
  docs: indicate new credential protocol fields
  credential: add a field called "ephemeral"
  credential: gate new fields on capability
  credential: add a field for pre-encoded credentials
  http: use new headers for each object request
  remote-curl: reset headers on new request
  credential: add an authtype field
This commit is contained in:
Junio C Hamano
2024-05-08 10:18:44 -07:00
16 changed files with 1026 additions and 121 deletions

129
http.c
View File

@ -128,7 +128,6 @@ static unsigned long empty_auth_useless =
| CURLAUTH_DIGEST;
static struct curl_slist *pragma_header;
static struct curl_slist *no_pragma_header;
static struct string_list extra_http_headers = STRING_LIST_INIT_DUP;
static struct curl_slist *host_resolutions;
@ -299,6 +298,11 @@ size_t fwrite_null(char *ptr UNUSED, size_t eltsize UNUSED, size_t nmemb,
return nmemb;
}
static struct curl_slist *object_request_headers(void)
{
return curl_slist_append(http_copy_default_headers(), "Pragma:");
}
static void closedown_active_slot(struct active_request_slot *slot)
{
active_requests--;
@ -557,18 +561,34 @@ static int curl_empty_auth_enabled(void)
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)
{
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())
curl_easy_setopt(result, CURLOPT_USERPWD, ":");
return;
}
credential_fill(&http_auth);
credential_fill(&http_auth, 1);
curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);
curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);
if (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 */
@ -582,17 +602,22 @@ static void var_override(const char **var, char *value)
static void set_proxyauth_name_password(CURL *result)
{
if (proxy_auth.password) {
curl_easy_setopt(result, CURLOPT_PROXYUSERNAME,
proxy_auth.username);
curl_easy_setopt(result, CURLOPT_PROXYPASSWORD,
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)
{
if (proxy_auth.username) {
if (!proxy_auth.password)
credential_fill(&proxy_auth);
if (!proxy_auth.password && !proxy_auth.credential)
credential_fill(&proxy_auth, 1);
set_proxyauth_name_password(result);
}
@ -626,7 +651,7 @@ static int has_cert_password(void)
cert_auth.host = xstrdup("");
cert_auth.username = xstrdup("");
cert_auth.path = xstrdup(ssl_cert);
credential_fill(&cert_auth);
credential_fill(&cert_auth, 0);
}
return 1;
}
@ -641,7 +666,7 @@ static int has_proxy_cert_password(void)
proxy_cert_auth.host = xstrdup("");
proxy_cert_auth.username = xstrdup("");
proxy_cert_auth.path = xstrdup(http_proxy_ssl_cert);
credential_fill(&proxy_cert_auth);
credential_fill(&proxy_cert_auth, 0);
}
return 1;
}
@ -1275,8 +1300,6 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
pragma_header = curl_slist_append(http_copy_default_headers(),
"Pragma: no-cache");
no_pragma_header = curl_slist_append(http_copy_default_headers(),
"Pragma:");
{
char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
@ -1360,9 +1383,6 @@ void http_cleanup(void)
curl_slist_free_all(pragma_header);
pragma_header = NULL;
curl_slist_free_all(no_pragma_header);
no_pragma_header = NULL;
curl_slist_free_all(host_resolutions);
host_resolutions = NULL;
@ -1470,7 +1490,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_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);
return slot;
@ -1759,7 +1779,12 @@ static int handle_curl_result(struct slot_results *results)
} else if (missing_target(results))
return HTTP_MISSING_TARGET;
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)) {
if (http_auth.multistage) {
credential_clear_secrets(&http_auth);
return HTTP_REAUTH;
}
credential_reject(&http_auth);
return HTTP_NOAUTH;
} else {
@ -2067,11 +2092,15 @@ static int http_request(const char *url,
/* Add additional headers here */
if (options && options->extra_headers) {
const struct string_list_item *item;
for_each_string_list_item(item, options->extra_headers) {
headers = curl_slist_append(headers, item->string);
if (options && options->extra_headers) {
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_HTTPHEADER, headers);
curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
@ -2153,6 +2182,7 @@ static int http_request_reauth(const char *url,
void *result, int target,
struct http_get_options *options)
{
int i = 3;
int ret = http_request(url, result, target, options);
if (ret != HTTP_OK && ret != HTTP_REAUTH)
@ -2166,35 +2196,35 @@ static int http_request_reauth(const char *url,
}
}
if (ret != HTTP_REAUTH)
return ret;
while (ret == HTTP_REAUTH && --i) {
/*
* The previous request may have put cruft into our output stream; we
* should clear it out before making our next request.
*/
switch (target) {
case HTTP_REQUEST_STRBUF:
strbuf_reset(result);
break;
case HTTP_REQUEST_FILE:
if (fflush(result)) {
error_errno("unable to flush a file");
return HTTP_START_FAILED;
}
rewind(result);
if (ftruncate(fileno(result), 0) < 0) {
error_errno("unable to truncate a file");
return HTTP_START_FAILED;
}
break;
default:
BUG("Unknown http_request target");
}
/*
* The previous request may have put cruft into our output stream; we
* should clear it out before making our next request.
*/
switch (target) {
case HTTP_REQUEST_STRBUF:
strbuf_reset(result);
break;
case HTTP_REQUEST_FILE:
if (fflush(result)) {
error_errno("unable to flush a file");
return HTTP_START_FAILED;
}
rewind(result);
if (ftruncate(fileno(result), 0) < 0) {
error_errno("unable to truncate a file");
return HTTP_START_FAILED;
}
break;
default:
BUG("Unknown http_request target");
credential_fill(&http_auth, 1);
ret = http_request(url, result, target, options);
}
credential_fill(&http_auth);
return http_request(url, result, target, options);
return ret;
}
int http_get_strbuf(const char *url,
@ -2371,6 +2401,7 @@ void release_http_pack_request(struct http_pack_request *preq)
}
preq->slot = NULL;
strbuf_release(&preq->tmpfile);
curl_slist_free_all(preq->headers);
free(preq->url);
free(preq);
}
@ -2455,11 +2486,11 @@ struct http_pack_request *new_direct_http_pack_request(
}
preq->slot = get_active_slot();
preq->headers = object_request_headers();
curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEDATA, preq->packfile);
curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url);
curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER,
no_pragma_header);
curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, preq->headers);
/*
* If there is data present from a previous transfer attempt,
@ -2625,13 +2656,14 @@ struct http_object_request *new_http_object_request(const char *base_url,
}
freq->slot = get_active_slot();
freq->headers = object_request_headers();
curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEDATA, freq);
curl_easy_setopt(freq->slot->curl, CURLOPT_FAILONERROR, 0);
curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
curl_easy_setopt(freq->slot->curl, CURLOPT_ERRORBUFFER, freq->errorstr);
curl_easy_setopt(freq->slot->curl, CURLOPT_URL, freq->url);
curl_easy_setopt(freq->slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
curl_easy_setopt(freq->slot->curl, CURLOPT_HTTPHEADER, freq->headers);
/*
* If we have successfully processed data from a previous fetch
@ -2719,5 +2751,6 @@ void release_http_object_request(struct http_object_request *freq)
release_active_slot(freq->slot);
freq->slot = NULL;
}
curl_slist_free_all(freq->headers);
strbuf_release(&freq->tmpfile);
}