{"thread":{"id":"61688","subject":"[PATCH 1/1] http: allow authenticating proactively","startedAt":"2024-06-28T00:37:36Z","lastAt":"2024-07-10T00:01:59Z","messageCount":12,"participants":["brian m. carlson","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":1},"messages":[{"id":"497771","messageId":"20240628002742.3421311-1-sandals@crustytoothpaste.net","threadId":"61688","inReplyTo":null,"subject":"[PATCH 0/1] Proactive authentication over HTTP","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2024-06-28T00:27:41Z","receivedAt":"2024-06-28T00:37:36Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Currently Git only sends authentication over HTTP once it's received a\n401 response from the server.  This series allows users to indicate that\nthey (or the credential helper) know what kind of authentication is to\nbe performed and avoid the extra round trip.\n\nThis has a couple of use cases:\n\n* Some connections are high-latency or potentially unreliable and the\n  extra round trip is expensive or flaky, so avoiding it can be\n  desirable.\n* Users can authenticate even to public repositories for fetches and\n  clones, such as to access higher rate limits or to allow better\n  identification of the requesting party.  This can be useful to help\n  identify, say, an internal service that is requesting excessive\n  amounts of resources and throttle it appropriately.  (An incident\n  involving this very situation is the impetus for this series.)\n* Some environments have very unusual proxy setups which require all\n  requests to be authenticated, and this might be useful there.  (This\n  is not a goal of this series and not a problem I'm generally\n  interested in solving, but it happens to benefit those people as well,\n  so I thought I'd mention it.)\n\nNote that the types of authentication for which we can do proactive auth\nare relatively limited.  Of the standard forms provided by libcurl, I\nbelieve only Basic is possible, because the others require a nonce or\nchallenge from the server that is used in the computation of the\nauthentication value.  Due to the new credential functionality recently\nintroduced, Bearer is also a useful possibility.\n\nbrian m. carlson (1):\n  http: allow authenticating proactively\n\n Documentation/config/http.txt |  15 +++++\n http.c                        |  59 +++++++++++++++--\n t/t5563-simple-http-auth.sh   | 116 ++++++++++++++++++++++++++++++++++\n 3 files changed, 184 insertions(+), 6 deletions(-)\n\n"},{"id":"497770","messageId":"20240628002742.3421311-2-sandals@crustytoothpaste.net","threadId":"61688","inReplyTo":"20240628002742.3421311-1-sandals@crustytoothpaste.net","subject":"[PATCH 1/1] http: allow authenticating proactively","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2024-06-28T00:27:42Z","receivedAt":"2024-06-28T00:37:37Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"When making a request over HTTP(S), Git only sends authentication if it\nreceives a 401 response.  Thus, if a repository is open to the public\nfor reading, Git will typically never ask for authentication for fetches\nand clones.\n\nHowever, there may be times when a user would like to authenticate\nnevertheless.  For example, a forge may give higher rate limits to users\nwho authenticate because they are easier to contact in case of excessive\nuse.  Or it may be useful for a known heavy user, such as an internal\nservice, to proactively authenticate so its use can be monitored and, if\nnecessary, throttled.\n\nLet's make this possible with a new option, \"http.proactiveAuth\".  This\noption specifies a type of authentication which can be used to\nauthenticate against the host in question.  This is necessary because we\nlack the WWW-Authenticate header to provide us details; similarly, we\ncannot accept certain types of authentication because we require\ninformation from the server, such as a nonce or challenge, to\nsuccessfully authenticate.\n\nNote that the existing http_proactive_auth variable signifies proactive\nauth if there are already credentials, which is different from the\nfunctionality we're adding, which always seeks credentials even if none\nare provided.  Nonetheless, t5540 tests the existing behavior for\nWebDAV-based pushes to an open repository without credentials, so we\npreserve it.  While at first this may seem an insecure and bizarre\ndecision, it may be that authentication is done with TLS certificates,\nin which case it might actually provide a quite high level of security.\nExpand the variable to use an enum to handle the additional cases and a\nhelper function to distinguish our new cases from the old ones.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n Documentation/config/http.txt |  15 +++++\n http.c                        |  59 +++++++++++++++--\n t/t5563-simple-http-auth.sh   | 116 ++++++++++++++++++++++++++++++++++\n 3 files changed, 184 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/http.txt b/Documentation/config/http.txt\nindex 2d4e0c9b86..2bacb2b862 100644\n--- a/Documentation/config/http.txt\n+++ b/Documentation/config/http.txt\n@@ -56,6 +56,21 @@ http.emptyAuth::\n \ta username in the URL, as libcurl normally requires a username for\n \tauthentication.\n \n+http.proactiveAuth::\n+\tAttempt authentication without first making an unauthenticated attempt and\n+\treceiving a 401 response. This can be used to ensure that all requests are\n+\tauthenticated. If `http.emptyAuth` is set to true, this value has no effect.\n++\n+If the credential helper used specifies an authentication scheme (i.e., via the\n+`authtype` field), that value will be used; if a username and password is\n+provided without a scheme, then Basic authentication is used.  The value of the\n+option determines the scheme requested from the helper. Possible values are:\n++\n+--\n+* `basic` - Request Basic authentication from the helper.\n+* `auto` - Don't request any scheme from the helper.\n+--\n+\n http.delegation::\n \tControl GSSAPI credential delegation. The delegation is disabled\n \tby default in libcurl since version 7.21.7. Set parameter to tell\ndiff --git a/http.c b/http.c\nindex 2dea2d03da..2e54eddb45 100644\n--- a/http.c\n+++ b/http.c\n@@ -106,12 +106,19 @@ static struct {\n };\n #endif\n \n+enum proactive_auth {\n+\tPROACTIVE_AUTH_NONE,\n+\tPROACTIVE_AUTH_IF_CREDENTIALS,\n+\tPROACTIVE_AUTH_AUTO,\n+\tPROACTIVE_AUTH_BASIC,\n+};\n+\n static struct credential proxy_auth = CREDENTIAL_INIT;\n static const char *curl_proxyuserpwd;\n static char *curl_cookie_file;\n static int curl_save_cookies;\n struct credential http_auth = CREDENTIAL_INIT;\n-static int http_proactive_auth;\n+static enum proactive_auth http_proactive_auth;\n static char *user_agent;\n static int curl_empty_auth = -1;\n \n@@ -146,6 +153,11 @@ static int http_schannel_check_revoke = 1;\n  */\n static int http_schannel_use_ssl_cainfo;\n \n+static int always_auth_proactively(void)\n+{\n+\treturn http_proactive_auth != PROACTIVE_AUTH_NONE && http_proactive_auth != PROACTIVE_AUTH_IF_CREDENTIALS;\n+}\n+\n size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)\n {\n \tsize_t size = eltsize * nmemb;\n@@ -537,6 +549,18 @@ static int http_options(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(\"http.proactiveauth\", var)) {\n+\t\tif (value && !strcmp(value, \"auto\"))\n+\t\t\thttp_proactive_auth = PROACTIVE_AUTH_AUTO;\n+\t\telse if (value && !strcmp(value, \"basic\"))\n+\t\t\thttp_proactive_auth = PROACTIVE_AUTH_BASIC;\n+\t\telse if (!value)\n+\t\t\thttp_proactive_auth = PROACTIVE_AUTH_NONE;\n+\t\telse\n+\t\t\twarning(_(\"Unknown value %s for http.proactiveauth\"), value);\n+\t\treturn 0;\n+\t}\n+\n \t/* Fall back on the default ones */\n \treturn git_default_config(var, value, ctx, data);\n }\n@@ -578,14 +602,29 @@ static void init_curl_http_auth(CURL *result)\n {\n \tif ((!http_auth.username || !*http_auth.username) &&\n \t    (!http_auth.credential || !*http_auth.credential)) {\n-\t\tif (curl_empty_auth_enabled())\n+\t\tint empty_auth = curl_empty_auth_enabled();\n+\t\tif ((empty_auth != -1 && !always_auth_proactively()) || empty_auth == 1) {\n \t\t\tcurl_easy_setopt(result, CURLOPT_USERPWD, \":\");\n-\t\treturn;\n+\t\t\treturn;\n+\t\t} else if (!always_auth_proactively()) {\n+\t\t\treturn;\n+\t\t} else if (http_proactive_auth == PROACTIVE_AUTH_BASIC) {\n+\t\t\tstrvec_push(&http_auth.wwwauth_headers, \"Basic\");\n+\t\t}\n \t}\n \n \tcredential_fill(&http_auth, 1);\n \n \tif (http_auth.password) {\n+\t\tif (always_auth_proactively()) {\n+\t\t\t/*\n+\t\t\t * We got a credential without an authtype and we don't\n+\t\t\t * know what's available.  Since our only two options at\n+\t\t\t * the moment are auto (which defaults to basic) and\n+\t\t\t * basic, use basic for now.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);\n+\t\t}\n \t\tcurl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);\n \t\tcurl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);\n \t}\n@@ -1048,7 +1087,7 @@ static CURL *get_curl_handle(void)\n #endif\n \t}\n \n-\tif (http_proactive_auth)\n+\tif (http_proactive_auth != PROACTIVE_AUTH_NONE)\n \t\tinit_curl_http_auth(result);\n \n \tif (getenv(\"GIT_SSL_VERSION\"))\n@@ -1292,7 +1331,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)\n \tif (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)\n \t\tdie(\"curl_global_init failed\");\n \n-\thttp_proactive_auth = proactive_auth;\n+\tif (proactive_auth && http_proactive_auth == PROACTIVE_AUTH_NONE)\n+\t\thttp_proactive_auth = PROACTIVE_AUTH_IF_CREDENTIALS;\n \n \tif (remote && remote->http_proxy)\n \t\tcurl_http_proxy = xstrdup(remote->http_proxy);\n@@ -1788,6 +1828,8 @@ static int handle_curl_result(struct slot_results *results)\n \t\t\t\treturn HTTP_REAUTH;\n \t\t\t}\n \t\t\tcredential_reject(&http_auth);\n+\t\t\tif (always_auth_proactively())\n+\t\t\t\thttp_proactive_auth = PROACTIVE_AUTH_NONE;\n \t\t\treturn HTTP_NOAUTH;\n \t\t} else {\n \t\t\thttp_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;\n@@ -2184,7 +2226,12 @@ static int http_request_reauth(const char *url,\n \t\t\t       struct http_get_options *options)\n {\n \tint i = 3;\n-\tint ret = http_request(url, result, target, options);\n+\tint ret;\n+\n+\tif (always_auth_proactively())\n+\t\tcredential_fill(&http_auth, 1);\n+\n+\tret = http_request(url, result, target, options);\n \n \tif (ret != HTTP_OK && ret != HTTP_REAUTH)\n \t\treturn ret;\ndiff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh\nindex 4af796de67..ba03f6a09f 100755\n--- a/t/t5563-simple-http-auth.sh\n+++ b/t/t5563-simple-http-auth.sh\n@@ -178,6 +178,122 @@ test_expect_success 'access using basic auth invalid credentials' '\n \tEOF\n '\n \n+test_expect_success 'access using basic proactive auth' '\n+\ttest_when_finished \"per_test_cleanup\" &&\n+\n+\tset_credential_reply get <<-EOF &&\n+\tusername=alice\n+\tpassword=secret-passwd\n+\tEOF\n+\n+\t# Basic base64(alice:secret-passwd)\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.valid\" <<-EOF &&\n+\tid=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==\n+\tEOF\n+\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.challenge\" <<-EOF &&\n+\tid=1 status=200\n+\tid=default status=403\n+\tEOF\n+\n+\ttest_config_global credential.helper test-helper &&\n+\ttest_config_global http.proactiveAuth basic &&\n+\tgit ls-remote \"$HTTPD_URL/custom_auth/repo.git\" &&\n+\n+\texpect_credential_query get <<-EOF &&\n+\tcapability[]=authtype\n+\tcapability[]=state\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\twwwauth[]=Basic\n+\tEOF\n+\n+\texpect_credential_query store <<-EOF\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tusername=alice\n+\tpassword=secret-passwd\n+\tEOF\n+'\n+\n+test_expect_success 'access using auto proactive auth with basic default' '\n+\ttest_when_finished \"per_test_cleanup\" &&\n+\n+\tset_credential_reply get <<-EOF &&\n+\tusername=alice\n+\tpassword=secret-passwd\n+\tEOF\n+\n+\t# Basic base64(alice:secret-passwd)\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.valid\" <<-EOF &&\n+\tid=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==\n+\tEOF\n+\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.challenge\" <<-EOF &&\n+\tid=1 status=200\n+\tid=default status=403\n+\tEOF\n+\n+\ttest_config_global credential.helper test-helper &&\n+\ttest_config_global http.proactiveAuth auto &&\n+\tgit ls-remote \"$HTTPD_URL/custom_auth/repo.git\" &&\n+\n+\texpect_credential_query get <<-EOF &&\n+\tcapability[]=authtype\n+\tcapability[]=state\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tEOF\n+\n+\texpect_credential_query store <<-EOF\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tusername=alice\n+\tpassword=secret-passwd\n+\tEOF\n+'\n+\n+test_expect_success 'access using auto proactive auth with authtype from credential helper' '\n+\ttest_when_finished \"per_test_cleanup\" &&\n+\n+\tset_credential_reply get <<-EOF &&\n+\tcapability[]=authtype\n+\tauthtype=Bearer\n+\tcredential=YS1naXQtdG9rZW4=\n+\tEOF\n+\n+\t# Basic base64(a-git-token)\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.valid\" <<-EOF &&\n+\tid=1 creds=Bearer YS1naXQtdG9rZW4=\n+\tEOF\n+\n+\tCHALLENGE=\"$HTTPD_ROOT_PATH/custom-auth.challenge\" &&\n+\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.challenge\" <<-EOF &&\n+\tid=1 status=200\n+\tid=default status=403\n+\tEOF\n+\n+\ttest_config_global credential.helper test-helper &&\n+\ttest_config_global http.proactiveAuth auto &&\n+\tgit ls-remote \"$HTTPD_URL/custom_auth/repo.git\" &&\n+\n+\texpect_credential_query get <<-EOF &&\n+\tcapability[]=authtype\n+\tcapability[]=state\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tEOF\n+\n+\texpect_credential_query store <<-EOF\n+\tcapability[]=authtype\n+\tauthtype=Bearer\n+\tcredential=YS1naXQtdG9rZW4=\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tEOF\n+'\n+\n test_expect_success 'access using basic auth with extra challenges' '\n \ttest_when_finished \"per_test_cleanup\" &&\n \n"},{"id":"497817","messageId":"xmqqa5j53pbo.fsf@gitster.g","threadId":"61688","inReplyTo":"20240628002742.3421311-2-sandals@crustytoothpaste.net","subject":"Re: [PATCH 1/1] http: allow authenticating proactively","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-28T18:16:43Z","receivedAt":"2024-06-28T18:16:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> diff --git a/Documentation/config/http.txt b/Documentation/config/http.txt\n> index 2d4e0c9b86..2bacb2b862 100644\n> --- a/Documentation/config/http.txt\n> +++ b/Documentation/config/http.txt\n> @@ -56,6 +56,21 @@ http.emptyAuth::\n>  \ta username in the URL, as libcurl normally requires a username for\n>  \tauthentication.\n>  \n> +http.proactiveAuth::\n> +\tAttempt authentication without first making an unauthenticated attempt and\n> +\treceiving a 401 response. This can be used to ensure that all requests are\n> +\tauthenticated. If `http.emptyAuth` is set to true, this value has no effect.\n\nWell written.\n\n> ++\n> +If the credential helper used specifies an authentication scheme (i.e., via the\n> +`authtype` field), that value will be used; if a username and password is\n> +provided without a scheme, then Basic authentication is used.  The value of the\n> +option determines the scheme requested from the helper. Possible values are:\n> ++\n> +--\n> +* `basic` - Request Basic authentication from the helper.\n> +* `auto` - Don't request any scheme from the helper.\n> +--\n\nWhat does \"don't request\" exactly mean?  It is not like we are\ntelling the helper \"Don't give us anything\", right?  Are we telling\nthe helper \"Give us any username/password for the URL in any\nauthentication scheme you know about?\"\n\nAs we are not getting an initial \"401 Unauthorized\", which is a good\nchannel to convey WWW-Authenticate, Digest is not available to us in\nthis context; we may end up using Basic---if the other side then\nsays \"No, I do not like basic, please use Diest in this realm with\nthis nonce\" with a \"401 Unauthorized\" with WWW-Authenticate, then\nall we gained was a chance to expose the username/password in\nplaintext (ok, that's still TLS protected in practice so it may not\nbe a huge deal).  Hopefully that wouldn't be a problem, but perhaps\nwe would want to suggest that this mechanism should primarily be\nused when the user _knows_ that the other side is happy accepting\nyou with Basic, or something, in the documentation?\n\n> diff --git a/http.c b/http.c\n> index 2dea2d03da..2e54eddb45 100644\n> --- a/http.c\n> +++ b/http.c\n> @@ -106,12 +106,19 @@ static struct {\n>  };\n>  #endif\n>  \n> +enum proactive_auth {\n> +\tPROACTIVE_AUTH_NONE,\n> +\tPROACTIVE_AUTH_IF_CREDENTIALS,\n> +\tPROACTIVE_AUTH_AUTO,\n> +\tPROACTIVE_AUTH_BASIC,\n> +};\n\nPROACTIVE_AUTH_NONE being at the first position to be assigned 0\nin this enum has significance, because ...\n\n>  static struct credential proxy_auth = CREDENTIAL_INIT;\n>  static const char *curl_proxyuserpwd;\n>  static char *curl_cookie_file;\n>  static int curl_save_cookies;\n>  struct credential http_auth = CREDENTIAL_INIT;\n> -static int http_proactive_auth;\n> +static enum proactive_auth http_proactive_auth;\n\n... this implicitly initializes the variable to *_NONE and we do\nrely on that value.  It may clarify what we are doing, if we are a\nbit more explicit, i.e.,\n\n        enum proactive_auth {\n                PROACTIVE_AUTH_NONE = 0,\n\t\t...\n\nIt would give a hint to future developers that they shouldn't\nreorder the enum def without thinking.\n\n> +static int always_auth_proactively(void)\n> +{\n> +\treturn http_proactive_auth != PROACTIVE_AUTH_NONE && http_proactive_auth != PROACTIVE_AUTH_IF_CREDENTIALS;\n> +}\n\nAn overly long line.\n\n>  size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)\n>  {\n>  \tsize_t size = eltsize * nmemb;\n> @@ -537,6 +549,18 @@ static int http_options(const char *var, const char *value,\n>  \t\treturn 0;\n>  \t}\n>  \n> +\tif (!strcmp(\"http.proactiveauth\", var)) {\n> +\t\tif (value && !strcmp(value, \"auto\"))\n> +\t\t\thttp_proactive_auth = PROACTIVE_AUTH_AUTO;\n> +\t\telse if (value && !strcmp(value, \"basic\"))\n> +\t\t\thttp_proactive_auth = PROACTIVE_AUTH_BASIC;\n> +\t\telse if (!value)\n> +\t\t\thttp_proactive_auth = PROACTIVE_AUTH_NONE;\n\nThis is how you \"reset\" the variable that is set in lower-precedence\nconfiguration source back to the default, but use of \"I exist\ntherefore I represent true without giving any value\" for that\npurpose is rather unusual.  Even if this were about resetting a\nmulti-valued configuration variable to empty, don't we usually allow\nan empty string to serve that purpose as well?\n\nAnd more importantly we apparently do not use this variable as\nmulti-valued one---the above callback is a bog-standard \"last one\nwins\" single value variable.\n\nSo instead of using the \"I exist\" true, I think it is a lot easier\nto see if you used an explicit string \"none\" for this purpose.\n\nIf you insist to use \"I exist\" true, checking it upfront would allow\nyou (and future developers) to lose many \"value&&\", i.e.,\n\n\t\tif (!value || !*value)\n\t\t\t... reset ...;\n\t\telse if (!strcmp(value, \"auto\"))\n\t\t\t...\n\nbut I do not see a strong reason to use the !value to begin with.  I\nsuspect that the usual\n\n\t\tif (!value)\n\t\t\treturn config_error_nonbool(var);\n\t\telseif (!strcmp(value, \"none\"))\n\t\t\thttp_proactive_auth = PROACTIVE_AUTH_NONE;\n\t\telse if ...\n\nwould be easier to read and maintain.\n\nIn any case, the documentation update above failed to mention the\nmechanism to reset to the state that is equivalent to an\nunconfigured state, which needs an update.  If we document what you\ndo in your patch, it would read something like this, perhaps.\n\n        +\n        An earlier setting made in configuration files with lower-precedence\n        can be overridden by setting it to a valueless truth, e.g.,\n        --\n        [http]\n\t\tproactiveAuth ; no \"= value\" needed here.\n        --\n\nAnd that is awkward, which is one of the reasons why I would prefer\nan explicit \"none\" (or \"default\" perhaps, if there were some reason\nyou wanted to avoid it).\n\n> +\t\telse\n> +\t\t\twarning(_(\"Unknown value %s for http.proactiveauth\"), value);\n> +\t\treturn 0;\n> +\t}\n> +\n\n> @@ -578,14 +602,29 @@ static void init_curl_http_auth(CURL *result)\n>  {\n>  \tif ((!http_auth.username || !*http_auth.username) &&\n>  \t    (!http_auth.credential || !*http_auth.credential)) {\n> -\t\tif (curl_empty_auth_enabled())\n> +\t\tint empty_auth = curl_empty_auth_enabled();\n> +\t\tif ((empty_auth != -1 && !always_auth_proactively()) || empty_auth == 1) {\n>  \t\t\tcurl_easy_setopt(result, CURLOPT_USERPWD, \":\");\n> -\t\treturn;\n> +\t\t\treturn;\n> +\t\t} else if (!always_auth_proactively()) {\n> +\t\t\treturn;\n> +\t\t} else if (http_proactive_auth == PROACTIVE_AUTH_BASIC) {\n> +\t\t\tstrvec_push(&http_auth.wwwauth_headers, \"Basic\");\n> +\t\t}\n>  \t}\n\nWhen http.proactiveauth explicitly says \"basic\", we push \"Basic\" to\nthe strvec here.\n\n>  \tcredential_fill(&http_auth, 1);\n>  \n>  \tif (http_auth.password) {\n> +\t\tif (always_auth_proactively()) {\n\nBut when http.proactiveauth is set to either \"auto\" or \"basic\",\nalways_auth_proactively() returns true (because it is not set to\n*_NONE and it is not *_IF_CREDENTIALS) and we come here, to do\ncurl_easy_setop() to use basic anyway.\n\n> +\t\t\t/*\n> +\t\t\t * We got a credential without an authtype and we don't\n> +\t\t\t * know what's available.  Since our only two options at\n> +\t\t\t * the moment are auto (which defaults to basic) and\n> +\t\t\t * basic, use basic for now.\n> +\t\t\t */\n> +\t\t\tcurl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);\n\nSo users with \"auto\" will not see \"Basic\" in .wwwauth_headers\nstrvec, while those with \"basic\" will.  Is this intended?  What is\nthe expected difference in behaviour coming from this difference?\n\nOr am I simply reading the code incorrectly and not understanding\nwhat happens before the control reaches fwrite_wwwauth()?\n\n> +\t\t}\n>  \t\tcurl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);\n>  \t\tcurl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);\n>  \t}\n> @@ -1048,7 +1087,7 @@ static CURL *get_curl_handle(void)\n>  #endif\n>  \t}\n>  \n> -\tif (http_proactive_auth)\n> +\tif (http_proactive_auth != PROACTIVE_AUTH_NONE)\n>  \t\tinit_curl_http_auth(result);\n\nMakes sense.\n\n> @@ -1292,7 +1331,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)\n>  \tif (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)\n>  \t\tdie(\"curl_global_init failed\");\n>  \n> -\thttp_proactive_auth = proactive_auth;\n> +\tif (proactive_auth && http_proactive_auth == PROACTIVE_AUTH_NONE)\n> +\t\thttp_proactive_auth = PROACTIVE_AUTH_IF_CREDENTIALS;\n\nThe webdav http-push is the only caller of http_init() with the\nproactive_auth parameter set to true.  In such a case, if we do not\nsee the configuration variable left/set to the default, we force the\n\"if-credentials\" behaviour.  IOW, if http.proactive_auth is set to\nsome value, we leave it as-is even for http-push (which wants to use\nthe \"if-credentials\" behaviour).\n\nI wonder if there are cases where you would want to do the\nproactive-auth for fetches (which you couldn't do before), but you\nwant to drive http-push with if-credentials, and if so, if this\nchanges the behaviour in a way that is visible to the end-user.\n\nOr perhaps http-push over webdav outlived its usefulness and we\nshould send a patch to Documentation/BreakingChanges.txt to declare\nits deprecation and removal, in which case there is nothing to\nwonder or be worried here ;-)\n\n> @@ -1788,6 +1828,8 @@ static int handle_curl_result(struct slot_results *results)\n>  \t\t\t\treturn HTTP_REAUTH;\n>  \t\t\t}\n>  \t\t\tcredential_reject(&http_auth);\n> +\t\t\tif (always_auth_proactively())\n> +\t\t\t\thttp_proactive_auth = PROACTIVE_AUTH_NONE;\n\nOnce we see a failure, there is not much point in doing the\nproactive-auth to the same destination in requests we are going to\nmake from now on.  Makes sense.\n\n> @@ -2184,7 +2226,12 @@ static int http_request_reauth(const char *url,\n>  \t\t\t       struct http_get_options *options)\n>  {\n>  \tint i = 3;\n> -\tint ret = http_request(url, result, target, options);\n> +\tint ret;\n> +\n> +\tif (always_auth_proactively())\n> +\t\tcredential_fill(&http_auth, 1);\n> +\n> +\tret = http_request(url, result, target, options);\n>  \n>  \tif (ret != HTTP_OK && ret != HTTP_REAUTH)\n>  \t\treturn ret;\n\nOK, if we need to auth proactively, of course we need to fill the\ncredential before making a request.  Makes sense.\n\nThanks.\n\n\n\n"},{"id":"497829","messageId":"Zn8yhdN6henrIqgD@tapette.crustytoothpaste.net","threadId":"61688","inReplyTo":"xmqqa5j53pbo.fsf@gitster.g","subject":"Re: [PATCH 1/1] http: allow authenticating proactively","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2024-06-28T22:00:37Z","receivedAt":"2024-06-28T22:00:40Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2024-06-28 at 18:16:43, Junio C Hamano wrote:\n> \"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n> \n> > diff --git a/Documentation/config/http.txt b/Documentation/config/http.txt\n> > index 2d4e0c9b86..2bacb2b862 100644\n> > --- a/Documentation/config/http.txt\n> > +++ b/Documentation/config/http.txt\n> > @@ -56,6 +56,21 @@ http.emptyAuth::\n> >  \ta username in the URL, as libcurl normally requires a username for\n> >  \tauthentication.\n> >  \n> > +http.proactiveAuth::\n> > +\tAttempt authentication without first making an unauthenticated attempt and\n> > +\treceiving a 401 response. This can be used to ensure that all requests are\n> > +\tauthenticated. If `http.emptyAuth` is set to true, this value has no effect.\n> \n> Well written.\n> \n> > ++\n> > +If the credential helper used specifies an authentication scheme (i.e., via the\n> > +`authtype` field), that value will be used; if a username and password is\n> > +provided without a scheme, then Basic authentication is used.  The value of the\n> > +option determines the scheme requested from the helper. Possible values are:\n> > ++\n> > +--\n> > +* `basic` - Request Basic authentication from the helper.\n> > +* `auto` - Don't request any scheme from the helper.\n> > +--\n> \n> What does \"don't request\" exactly mean?  It is not like we are\n> telling the helper \"Don't give us anything\", right?  Are we telling\n> the helper \"Give us any username/password for the URL in any\n> authentication scheme you know about?\"\n\nIt means we don't send a `wwwauth[]` entry in the request.  We are\ngiving the helper carte blanche to decide what scheme is best (maybe it\nknows we want Bearer, for example).\n\n> As we are not getting an initial \"401 Unauthorized\", which is a good\n> channel to convey WWW-Authenticate, Digest is not available to us in\n> this context; we may end up using Basic---if the other side then\n> says \"No, I do not like basic, please use Diest in this realm with\n> this nonce\" with a \"401 Unauthorized\" with WWW-Authenticate, then\n> all we gained was a chance to expose the username/password in\n> plaintext (ok, that's still TLS protected in practice so it may not\n> be a huge deal).  Hopefully that wouldn't be a problem, but perhaps\n> we would want to suggest that this mechanism should primarily be\n> used when the user _knows_ that the other side is happy accepting\n> you with Basic, or something, in the documentation?\n\nI think it might suffice to say that TLS should always be used with this\nconfiguration.  The documentation is very clear that it will use Basic\nif no scheme is provided, so presumably the user is either very\nconfident in their helper configuration (e.g., they know for certain\nthat the helper will always emit an `authtype` field because they've so\nconfigured it), or they're willing to accept the risk that the password\nis sent with basic auth.\n\n> PROACTIVE_AUTH_NONE being at the first position to be assigned 0\n> in this enum has significance, because ...\n> \n> >  static struct credential proxy_auth = CREDENTIAL_INIT;\n> >  static const char *curl_proxyuserpwd;\n> >  static char *curl_cookie_file;\n> >  static int curl_save_cookies;\n> >  struct credential http_auth = CREDENTIAL_INIT;\n> > -static int http_proactive_auth;\n> > +static enum proactive_auth http_proactive_auth;\n> \n> ... this implicitly initializes the variable to *_NONE and we do\n> rely on that value.  It may clarify what we are doing, if we are a\n> bit more explicit, i.e.,\n> \n>         enum proactive_auth {\n>                 PROACTIVE_AUTH_NONE = 0,\n> \t\t...\n> \n> It would give a hint to future developers that they shouldn't\n> reorder the enum def without thinking.\n\nSure, that seems like a good idea.\n\n> > +static int always_auth_proactively(void)\n> > +{\n> > +\treturn http_proactive_auth != PROACTIVE_AUTH_NONE && http_proactive_auth != PROACTIVE_AUTH_IF_CREDENTIALS;\n> > +}\n> \n> An overly long line.\n\nWill fix.\n\n> >  size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)\n> >  {\n> >  \tsize_t size = eltsize * nmemb;\n> > @@ -537,6 +549,18 @@ static int http_options(const char *var, const char *value,\n> >  \t\treturn 0;\n> >  \t}\n> >  \n> > +\tif (!strcmp(\"http.proactiveauth\", var)) {\n> > +\t\tif (value && !strcmp(value, \"auto\"))\n> > +\t\t\thttp_proactive_auth = PROACTIVE_AUTH_AUTO;\n> > +\t\telse if (value && !strcmp(value, \"basic\"))\n> > +\t\t\thttp_proactive_auth = PROACTIVE_AUTH_BASIC;\n> > +\t\telse if (!value)\n> > +\t\t\thttp_proactive_auth = PROACTIVE_AUTH_NONE;\n> \n> This is how you \"reset\" the variable that is set in lower-precedence\n> configuration source back to the default, but use of \"I exist\n> therefore I represent true without giving any value\" for that\n> purpose is rather unusual.  Even if this were about resetting a\n> multi-valued configuration variable to empty, don't we usually allow\n> an empty string to serve that purpose as well?\n> \n> And more importantly we apparently do not use this variable as\n> multi-valued one---the above callback is a bog-standard \"last one\n> wins\" single value variable.\n> \n> So instead of using the \"I exist\" true, I think it is a lot easier\n> to see if you used an explicit string \"none\" for this purpose.\n\nOkay, I think that seems reasonable.\n\n> >  \tcredential_fill(&http_auth, 1);\n> >  \n> >  \tif (http_auth.password) {\n> > +\t\tif (always_auth_proactively()) {\n> \n> But when http.proactiveauth is set to either \"auto\" or \"basic\",\n> always_auth_proactively() returns true (because it is not set to\n> *_NONE and it is not *_IF_CREDENTIALS) and we come here, to do\n> curl_easy_setop() to use basic anyway.\n\nYes, iff there's a password.  The reason is that it isn't sufficient for\nlibcurl to have a username and password to send authentication.  Either\nwe have to explicitly fill in the Authorization header (which we do with\n`authtype` and `credential`) or we have to specify exactly one\nauthentication scheme; otherwise, libcurl doesn't auth.  Note that\nentire branch is not taken if we have `authtype` and `credential`; that\nis handled elsewhere.\n\nThis is subtle, so I'll mention it in the commit message on a reroll.\n\n> > +\t\t\t/*\n> > +\t\t\t * We got a credential without an authtype and we don't\n> > +\t\t\t * know what's available.  Since our only two options at\n> > +\t\t\t * the moment are auto (which defaults to basic) and\n> > +\t\t\t * basic, use basic for now.\n> > +\t\t\t */\n> > +\t\t\tcurl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);\n> \n> So users with \"auto\" will not see \"Basic\" in .wwwauth_headers\n> strvec, while those with \"basic\" will.  Is this intended?  What is\n> the expected difference in behaviour coming from this difference?\n\nYes.  The difference is that a credential helper supporting multiple\nschemes will (or at least _should_) read the `wwwauth[]` entry and say,\n\"Oh, the server only supports Basic auth, so I must send Basic auth\",\nwhereas not sending anything tells it that it may choose freely.  For\nexample, a helper might legitimately return Bearer for the case where we\ndon't send a `wwwauth[]` entry (which would be handled differently),\nwhereas it probably would not want to do that when the option is set to\n`basic`.\n\n> > @@ -1292,7 +1331,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)\n> >  \tif (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)\n> >  \t\tdie(\"curl_global_init failed\");\n> >  \n> > -\thttp_proactive_auth = proactive_auth;\n> > +\tif (proactive_auth && http_proactive_auth == PROACTIVE_AUTH_NONE)\n> > +\t\thttp_proactive_auth = PROACTIVE_AUTH_IF_CREDENTIALS;\n> \n> The webdav http-push is the only caller of http_init() with the\n> proactive_auth parameter set to true.  In such a case, if we do not\n> see the configuration variable left/set to the default, we force the\n> \"if-credentials\" behaviour.  IOW, if http.proactive_auth is set to\n> some value, we leave it as-is even for http-push (which wants to use\n> the \"if-credentials\" behaviour).\n\nCorrect.  When this option was a boolean, the if-credentials behaviour\nwas what happened when the option was true.\n\n> I wonder if there are cases where you would want to do the\n> proactive-auth for fetches (which you couldn't do before), but you\n> want to drive http-push with if-credentials, and if so, if this\n> changes the behaviour in a way that is visible to the end-user.\n\nI think that's remarkably unlikely.  Pushing effectively always requires\nsome sort of authentication because you don't want random people\nmodifying your codebase/writing project/other collection of bits.\nThat's like security 101.\n\nTherefore, you're _going_ to authenticate sooner or later on push.  The\nonly case where if-credentials is helpful is what I mentioned about\nt5540, where we actually have an open push.  That isn't a real world use\ncase for security reasons, but using TLS certificates to authenticate\nand not otherwise sending credentials, which _looks_ the same to this\ncode, is legitimate.\n\nHowever, you wouldn't configure TLS certificates for push only and use\nregular auth for fetch, since you'd already have the TLS certificate on\nthe client and you'd have already requested the certificate when\nestablishing the TLS connection.  Moreover, there's no reason that you\n_would_ want to auth for fetches or clones and not for pushes; if your\ngoal is to make it easier to throttle heavy users, say, pushes are more\nexpensive because you have to run pre-receive hooks and fsck the data.\n\nPerhaps someone has come up with such an unusual use case and is still\nusing the WebDAV approach, and in such a case, we can simply let them\nsend a nice patch to implement http.pushProactiveAuth for that case.  In\nthe mean time, I'm happy to let one option control both.\n\n> Or perhaps http-push over webdav outlived its usefulness and we\n> should send a patch to Documentation/BreakingChanges.txt to declare\n> its deprecation and removal, in which case there is nothing to\n> wonder or be worried here ;-)\n\nI think cgit still supports it because it's easier to implement in a\nCGI-style piece of code, and maybe that's useful.  While I don't use it,\nit can be easier to set up in certain environments, and I don't really\nwant to be the one to announce its demise.\n\nAnyway, thanks for the review, and I'll try to get a reroll out\nrelatively soon.  I have a couple days of vacation with the Canada Day\nholiday, so hopefully I can find some time to get one out then.\n-- \nbrian m. carlson (they/them or he/him)\nToronto, Ontario, CA\n"},{"id":"497830","messageId":"xmqqh6dc1zk2.fsf@gitster.g","threadId":"61688","inReplyTo":"Zn8yhdN6henrIqgD@tapette.crustytoothpaste.net","subject":"Re: [PATCH 1/1] http: allow authenticating proactively","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-28T22:18:37Z","receivedAt":"2024-06-28T22:18:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n>> > +* `basic` - Request Basic authentication from the helper.\n>> > +* `auto` - Don't request any scheme from the helper.\n>> > +--\n>> \n>> What does \"don't request\" exactly mean?  It is not like we are\n>> telling the helper \"Don't give us anything\", right?  Are we telling\n>> the helper \"Give us any username/password for the URL in any\n>> authentication scheme you know about?\"\n>\n> It means we don't send a `wwwauth[]` entry in the request.  We are\n> giving the helper carte blanche to decide what scheme is best (maybe it\n> knows we want Bearer, for example).\n\nWell, at least I couldn't read the proposed document update and read\nthat much out of it, and I suspect there may be other readers who\nwill share my confusion.  I think the source of the confusion is\nthat \"don't request\" does not automatically imply \"allow the helper\nto pick any scheme as it sees fit\" if you do not know how helper is\ndesigned to behave when it is not requested \"any scheme\".\n\n    `basic` - Request Basic authentication from the helper.\n    `auto`  - Ask the helper to pick an appropriate scheme.\n    `none`  - Disable proactive authentication.\n\nor something, perhaps?\n\nThanks.\n\n"},{"id":"497831","messageId":"Zn9UGwMQ4Ur-peI6@tapette.crustytoothpaste.net","threadId":"61688","inReplyTo":"xmqqh6dc1zk2.fsf@gitster.g","subject":"Re: [PATCH 1/1] http: allow authenticating proactively","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2024-06-29T00:23:55Z","receivedAt":"2024-06-29T00:23:58Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2024-06-28 at 22:18:37, Junio C Hamano wrote:\n> Well, at least I couldn't read the proposed document update and read\n> that much out of it, and I suspect there may be other readers who\n> will share my confusion.  I think the source of the confusion is\n> that \"don't request\" does not automatically imply \"allow the helper\n> to pick any scheme as it sees fit\" if you do not know how helper is\n> designed to behave when it is not requested \"any scheme\".\n> \n>     `basic` - Request Basic authentication from the helper.\n>     `auto`  - Ask the helper to pick an appropriate scheme.\n>     `none`  - Disable proactive authentication.\n> \n> or something, perhaps?\n\nThat sounds good.  I appreciate you providing some language that would\nbe less confusing to you, because it will probably be less confusing to\nothers as well.\n-- \nbrian m. carlson (they/them or he/him)\nToronto, Ontario, CA\n"},{"id":"497900","messageId":"xmqqr0cdyvzi.fsf@gitster.g","threadId":"61688","inReplyTo":"Zn9UGwMQ4Ur-peI6@tapette.crustytoothpaste.net","subject":"Re: [PATCH 1/1] http: allow authenticating proactively","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-01T15:26:09Z","receivedAt":"2024-07-01T15:26:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> On 2024-06-28 at 22:18:37, Junio C Hamano wrote:\n>> Well, at least I couldn't read the proposed document update and read\n>> that much out of it, and I suspect there may be other readers who\n>> will share my confusion.  I think the source of the confusion is\n>> that \"don't request\" does not automatically imply \"allow the helper\n>> to pick any scheme as it sees fit\" if you do not know how helper is\n>> designed to behave when it is not requested \"any scheme\".\n>> \n>>     `basic` - Request Basic authentication from the helper.\n>>     `auto`  - Ask the helper to pick an appropriate scheme.\n>>     `none`  - Disable proactive authentication.\n>> \n>> or something, perhaps?\n>\n> That sounds good.  I appreciate you providing some language that would\n> be less confusing to you, because it will probably be less confusing to\n> others as well.\n\nThanks.\n\nGiving a possible alternative, when you are certain you understood\nwhat you originally found confusing, is probably one of the things\nwe should add as a tip to ReviewingPatches document (do we have one\nalready--- #leftoverbits ?).\n\n"},{"id":"498065","messageId":"20240704001748.746429-1-sandals@crustytoothpaste.net","threadId":"61688","inReplyTo":"20240628002742.3421311-1-sandals@crustytoothpaste.net","subject":"[PATCH v2 0/1] Proactive authentication over HTTP","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2024-07-04T00:17:47Z","receivedAt":"2024-07-04T00:17:57Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Currently Git only sends authentication over HTTP once it's received a\n401 response from the server.  This series allows users to indicate that\nthey (or the credential helper) know what kind of authentication is to\nbe performed and avoid the extra round trip.\n\nChanges since v1:\n* Add a `none` option to explicitly reset the value.\n* Improve explanation of `auto`.\n* Improve commit message to explain tricky edge case.\n* Note that TLS should always be used.\n\nbrian m. carlson (1):\n  http: allow authenticating proactively\n\n Documentation/config/http.txt |  20 ++++++\n http.c                        |  60 ++++++++++++++++--\n t/t5563-simple-http-auth.sh   | 116 ++++++++++++++++++++++++++++++++++\n 3 files changed, 190 insertions(+), 6 deletions(-)\n\n"},{"id":"498066","messageId":"20240704001748.746429-2-sandals@crustytoothpaste.net","threadId":"61688","inReplyTo":"20240704001748.746429-1-sandals@crustytoothpaste.net","subject":"[PATCH v2 1/1] http: allow authenticating proactively","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2024-07-04T00:17:48Z","receivedAt":"2024-07-04T00:17:57Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"When making a request over HTTP(S), Git only sends authentication if it\nreceives a 401 response.  Thus, if a repository is open to the public\nfor reading, Git will typically never ask for authentication for fetches\nand clones.\n\nHowever, there may be times when a user would like to authenticate\nnevertheless.  For example, a forge may give higher rate limits to users\nwho authenticate because they are easier to contact in case of excessive\nuse.  Or it may be useful for a known heavy user, such as an internal\nservice, to proactively authenticate so its use can be monitored and, if\nnecessary, throttled.\n\nLet's make this possible with a new option, \"http.proactiveAuth\".  This\noption specifies a type of authentication which can be used to\nauthenticate against the host in question.  This is necessary because we\nlack the WWW-Authenticate header to provide us details; similarly, we\ncannot accept certain types of authentication because we require\ninformation from the server, such as a nonce or challenge, to\nsuccessfully authenticate.\n\nIf we're in auto mode and we got a username and password, set the\nauthentication scheme to Basic.  libcurl will not send authentication\nproactively unless there's a single choice of allowed authentication,\nand we know in this case we didn't get an authtype entry telling us what\nscheme to use, or we would have taken a different codepath and written\nthe header ourselves.  In any event, of the other schemes that libcurl\nsupports, Digest and NTLM require a nonce or challenge, which means that\nthey cannot work with proactive auth, and GSSAPI does not use a username\nand password at all, so Basic is the only logical choice among the\nbuilt-in options.\n\nNote that the existing http_proactive_auth variable signifies proactive\nauth if there are already credentials, which is different from the\nfunctionality we're adding, which always seeks credentials even if none\nare provided.  Nonetheless, t5540 tests the existing behavior for\nWebDAV-based pushes to an open repository without credentials, so we\npreserve it.  While at first this may seem an insecure and bizarre\ndecision, it may be that authentication is done with TLS certificates,\nin which case it might actually provide a quite high level of security.\nExpand the variable to use an enum to handle the additional cases and a\nhelper function to distinguish our new cases from the old ones.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n Documentation/config/http.txt |  20 ++++++\n http.c                        |  60 ++++++++++++++++--\n t/t5563-simple-http-auth.sh   | 116 ++++++++++++++++++++++++++++++++++\n 3 files changed, 190 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/http.txt b/Documentation/config/http.txt\nindex 2d4e0c9b86..9fd6c38252 100644\n--- a/Documentation/config/http.txt\n+++ b/Documentation/config/http.txt\n@@ -56,6 +56,26 @@ http.emptyAuth::\n \ta username in the URL, as libcurl normally requires a username for\n \tauthentication.\n \n+http.proactiveAuth::\n+\tAttempt authentication without first making an unauthenticated attempt and\n+\treceiving a 401 response. This can be used to ensure that all requests are\n+\tauthenticated. If `http.emptyAuth` is set to true, this value has no effect.\n++\n+If the credential helper used specifies an authentication scheme (i.e., via the\n+`authtype` field), that value will be used; if a username and password is\n+provided without a scheme, then Basic authentication is used.  The value of the\n+option determines the scheme requested from the helper. Possible values are:\n++\n+--\n+* `basic` - Request Basic authentication from the helper.\n+* `auto` - Allow the helper to pick an appropriate scheme.\n+* `none` - Disable proactive authentication.\n+--\n++\n+Note that TLS should always be used with this configuration, since otherwise it\n+is easy to accidentally expose plaintext credentials if Basic authentication\n+is selected.\n+\n http.delegation::\n \tControl GSSAPI credential delegation. The delegation is disabled\n \tby default in libcurl since version 7.21.7. Set parameter to tell\ndiff --git a/http.c b/http.c\nindex 13fa94bef3..3e90b32bb0 100644\n--- a/http.c\n+++ b/http.c\n@@ -108,12 +108,19 @@ static struct {\n };\n #endif\n \n+enum proactive_auth {\n+\tPROACTIVE_AUTH_NONE = 0,\n+\tPROACTIVE_AUTH_IF_CREDENTIALS,\n+\tPROACTIVE_AUTH_AUTO,\n+\tPROACTIVE_AUTH_BASIC,\n+};\n+\n static struct credential proxy_auth = CREDENTIAL_INIT;\n static const char *curl_proxyuserpwd;\n static char *curl_cookie_file;\n static int curl_save_cookies;\n struct credential http_auth = CREDENTIAL_INIT;\n-static int http_proactive_auth;\n+static enum proactive_auth http_proactive_auth;\n static char *user_agent;\n static int curl_empty_auth = -1;\n \n@@ -148,6 +155,12 @@ static int http_schannel_check_revoke = 1;\n  */\n static int http_schannel_use_ssl_cainfo;\n \n+static int always_auth_proactively(void)\n+{\n+\treturn http_proactive_auth != PROACTIVE_AUTH_NONE &&\n+\t       http_proactive_auth != PROACTIVE_AUTH_IF_CREDENTIALS;\n+}\n+\n size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)\n {\n \tsize_t size = eltsize * nmemb;\n@@ -539,6 +552,18 @@ static int http_options(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(\"http.proactiveauth\", var)) {\n+\t\tif (value && !strcmp(value, \"auto\"))\n+\t\t\thttp_proactive_auth = PROACTIVE_AUTH_AUTO;\n+\t\telse if (value && !strcmp(value, \"basic\"))\n+\t\t\thttp_proactive_auth = PROACTIVE_AUTH_BASIC;\n+\t\telse if (value && !strcmp(value, \"none\"))\n+\t\t\thttp_proactive_auth = PROACTIVE_AUTH_NONE;\n+\t\telse\n+\t\t\twarning(_(\"Unknown value for http.proactiveauth\"));\n+\t\treturn 0;\n+\t}\n+\n \t/* Fall back on the default ones */\n \treturn git_default_config(var, value, ctx, data);\n }\n@@ -580,14 +605,29 @@ static void init_curl_http_auth(CURL *result)\n {\n \tif ((!http_auth.username || !*http_auth.username) &&\n \t    (!http_auth.credential || !*http_auth.credential)) {\n-\t\tif (curl_empty_auth_enabled())\n+\t\tint empty_auth = curl_empty_auth_enabled();\n+\t\tif ((empty_auth != -1 && !always_auth_proactively()) || empty_auth == 1) {\n \t\t\tcurl_easy_setopt(result, CURLOPT_USERPWD, \":\");\n-\t\treturn;\n+\t\t\treturn;\n+\t\t} else if (!always_auth_proactively()) {\n+\t\t\treturn;\n+\t\t} else if (http_proactive_auth == PROACTIVE_AUTH_BASIC) {\n+\t\t\tstrvec_push(&http_auth.wwwauth_headers, \"Basic\");\n+\t\t}\n \t}\n \n \tcredential_fill(&http_auth, 1);\n \n \tif (http_auth.password) {\n+\t\tif (always_auth_proactively()) {\n+\t\t\t/*\n+\t\t\t * We got a credential without an authtype and we don't\n+\t\t\t * know what's available.  Since our only two options at\n+\t\t\t * the moment are auto (which defaults to basic) and\n+\t\t\t * basic, use basic for now.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);\n+\t\t}\n \t\tcurl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);\n \t\tcurl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);\n \t}\n@@ -1050,7 +1090,7 @@ static CURL *get_curl_handle(void)\n #endif\n \t}\n \n-\tif (http_proactive_auth)\n+\tif (http_proactive_auth != PROACTIVE_AUTH_NONE)\n \t\tinit_curl_http_auth(result);\n \n \tif (getenv(\"GIT_SSL_VERSION\"))\n@@ -1294,7 +1334,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)\n \tif (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)\n \t\tdie(\"curl_global_init failed\");\n \n-\thttp_proactive_auth = proactive_auth;\n+\tif (proactive_auth && http_proactive_auth == PROACTIVE_AUTH_NONE)\n+\t\thttp_proactive_auth = PROACTIVE_AUTH_IF_CREDENTIALS;\n \n \tif (remote && remote->http_proxy)\n \t\tcurl_http_proxy = xstrdup(remote->http_proxy);\n@@ -1790,6 +1831,8 @@ static int handle_curl_result(struct slot_results *results)\n \t\t\t\treturn HTTP_REAUTH;\n \t\t\t}\n \t\t\tcredential_reject(&http_auth);\n+\t\t\tif (always_auth_proactively())\n+\t\t\t\thttp_proactive_auth = PROACTIVE_AUTH_NONE;\n \t\t\treturn HTTP_NOAUTH;\n \t\t} else {\n \t\t\thttp_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;\n@@ -2186,7 +2229,12 @@ static int http_request_reauth(const char *url,\n \t\t\t       struct http_get_options *options)\n {\n \tint i = 3;\n-\tint ret = http_request(url, result, target, options);\n+\tint ret;\n+\n+\tif (always_auth_proactively())\n+\t\tcredential_fill(&http_auth, 1);\n+\n+\tret = http_request(url, result, target, options);\n \n \tif (ret != HTTP_OK && ret != HTTP_REAUTH)\n \t\treturn ret;\ndiff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh\nindex 4af796de67..ba03f6a09f 100755\n--- a/t/t5563-simple-http-auth.sh\n+++ b/t/t5563-simple-http-auth.sh\n@@ -178,6 +178,122 @@ test_expect_success 'access using basic auth invalid credentials' '\n \tEOF\n '\n \n+test_expect_success 'access using basic proactive auth' '\n+\ttest_when_finished \"per_test_cleanup\" &&\n+\n+\tset_credential_reply get <<-EOF &&\n+\tusername=alice\n+\tpassword=secret-passwd\n+\tEOF\n+\n+\t# Basic base64(alice:secret-passwd)\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.valid\" <<-EOF &&\n+\tid=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==\n+\tEOF\n+\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.challenge\" <<-EOF &&\n+\tid=1 status=200\n+\tid=default status=403\n+\tEOF\n+\n+\ttest_config_global credential.helper test-helper &&\n+\ttest_config_global http.proactiveAuth basic &&\n+\tgit ls-remote \"$HTTPD_URL/custom_auth/repo.git\" &&\n+\n+\texpect_credential_query get <<-EOF &&\n+\tcapability[]=authtype\n+\tcapability[]=state\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\twwwauth[]=Basic\n+\tEOF\n+\n+\texpect_credential_query store <<-EOF\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tusername=alice\n+\tpassword=secret-passwd\n+\tEOF\n+'\n+\n+test_expect_success 'access using auto proactive auth with basic default' '\n+\ttest_when_finished \"per_test_cleanup\" &&\n+\n+\tset_credential_reply get <<-EOF &&\n+\tusername=alice\n+\tpassword=secret-passwd\n+\tEOF\n+\n+\t# Basic base64(alice:secret-passwd)\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.valid\" <<-EOF &&\n+\tid=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==\n+\tEOF\n+\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.challenge\" <<-EOF &&\n+\tid=1 status=200\n+\tid=default status=403\n+\tEOF\n+\n+\ttest_config_global credential.helper test-helper &&\n+\ttest_config_global http.proactiveAuth auto &&\n+\tgit ls-remote \"$HTTPD_URL/custom_auth/repo.git\" &&\n+\n+\texpect_credential_query get <<-EOF &&\n+\tcapability[]=authtype\n+\tcapability[]=state\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tEOF\n+\n+\texpect_credential_query store <<-EOF\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tusername=alice\n+\tpassword=secret-passwd\n+\tEOF\n+'\n+\n+test_expect_success 'access using auto proactive auth with authtype from credential helper' '\n+\ttest_when_finished \"per_test_cleanup\" &&\n+\n+\tset_credential_reply get <<-EOF &&\n+\tcapability[]=authtype\n+\tauthtype=Bearer\n+\tcredential=YS1naXQtdG9rZW4=\n+\tEOF\n+\n+\t# Basic base64(a-git-token)\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.valid\" <<-EOF &&\n+\tid=1 creds=Bearer YS1naXQtdG9rZW4=\n+\tEOF\n+\n+\tCHALLENGE=\"$HTTPD_ROOT_PATH/custom-auth.challenge\" &&\n+\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.challenge\" <<-EOF &&\n+\tid=1 status=200\n+\tid=default status=403\n+\tEOF\n+\n+\ttest_config_global credential.helper test-helper &&\n+\ttest_config_global http.proactiveAuth auto &&\n+\tgit ls-remote \"$HTTPD_URL/custom_auth/repo.git\" &&\n+\n+\texpect_credential_query get <<-EOF &&\n+\tcapability[]=authtype\n+\tcapability[]=state\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tEOF\n+\n+\texpect_credential_query store <<-EOF\n+\tcapability[]=authtype\n+\tauthtype=Bearer\n+\tcredential=YS1naXQtdG9rZW4=\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tEOF\n+'\n+\n test_expect_success 'access using basic auth with extra challenges' '\n \ttest_when_finished \"per_test_cleanup\" &&\n \n"},{"id":"498076","messageId":"xmqqo77d7lkr.fsf@gitster.g","threadId":"61688","inReplyTo":"20240704001748.746429-2-sandals@crustytoothpaste.net","subject":"Re: [PATCH v2 1/1] http: allow authenticating proactively","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-04T05:49:56Z","receivedAt":"2024-07-04T05:49:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> If we're in auto mode and we got a username and password, set the\n> authentication scheme to Basic.  libcurl will not send authentication\n> proactively unless there's a single choice of allowed authentication,\n> and we know in this case we didn't get an authtype entry telling us what\n> scheme to use, or we would have taken a different codepath and written\n> the header ourselves.  In any event, of the other schemes that libcurl\n> supports, Digest and NTLM require a nonce or challenge, which means that\n> they cannot work with proactive auth, and GSSAPI does not use a username\n> and password at all, so Basic is the only logical choice among the\n> built-in options.\n\nNice explanation.\n\n> +http.proactiveAuth::\n> +\tAttempt authentication without first making an unauthenticated attempt and\n> +\treceiving a 401 response. This can be used to ensure that all requests are\n> +\tauthenticated. If `http.emptyAuth` is set to true, this value has no effect.\n> ++\n> +If the credential helper used specifies an authentication scheme (i.e., via the\n> +`authtype` field), that value will be used; if a username and password is\n> +provided without a scheme, then Basic authentication is used.  The value of the\n> +option determines the scheme requested from the helper. Possible values are:\n> ++\n> +--\n> +* `basic` - Request Basic authentication from the helper.\n> +* `auto` - Allow the helper to pick an appropriate scheme.\n> +* `none` - Disable proactive authentication.\n> +--\n> ++\n> +Note that TLS should always be used with this configuration, since otherwise it\n> +is easy to accidentally expose plaintext credentials if Basic authentication\n> +is selected.\n\nOK.\n\n> @@ -539,6 +552,18 @@ static int http_options(const char *var, const char *value,\n>  \t\treturn 0;\n>  \t}\n>  \n> +\tif (!strcmp(\"http.proactiveauth\", var)) {\n\nIf we choose to make\n\n\t[http] proactiveauth ; nothing else on the line\n\nan error, we could do\n\n\t\tif (!value)\n                \treturn config_error_nonbool(var);\n\nand lose all the \"we have to make sure value is not NULL before\nfeeding it to strcmp()\" checks below.\n\nOr \n\n\t\tif (!value) {\n\t\t\twarning(_(\"http.proactiveauth set to true???\"));\n                        return 0;\n\t\t}\n\nif we wanted to be more lenient (which is more in line with how we\ntreat unknown string value below).\n\n> +\t\tif (value && !strcmp(value, \"auto\"))\n> +\t\t\thttp_proactive_auth = PROACTIVE_AUTH_AUTO;\n> +\t\telse if (value && !strcmp(value, \"basic\"))\n> +\t\t\thttp_proactive_auth = PROACTIVE_AUTH_BASIC;\n> +\t\telse if (value && !strcmp(value, \"none\"))\n> +\t\t\thttp_proactive_auth = PROACTIVE_AUTH_NONE;\n> +\t\telse\n> +\t\t\twarning(_(\"Unknown value for http.proactiveauth\"));\n> +\t\treturn 0;\n> +\t}\n> +\n\nOther than that I saw nothing puzzling or curious.  Looking very\ngood.\n\nThanks.  Will queue.\n"},{"id":"498399","messageId":"20240710000155.947377-1-sandals@crustytoothpaste.net","threadId":"61688","inReplyTo":"20240704001748.746429-1-sandals@crustytoothpaste.net","subject":"[PATCH v3 0/1] Proactive authentication over HTTP","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2024-07-10T00:01:54Z","receivedAt":"2024-07-10T00:01:59Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"Currently Git only sends authentication over HTTP once it's received a\n401 response from the server.  This series allows users to indicate that\nthey (or the credential helper) know what kind of authentication is to\nbe performed and avoid the extra round trip.\n\nChanges since v2:\n* Improve parsing of configuration options as suggested by Junio.\n\nChanges since v1:\n* Add a `none` option to explicitly reset the value.\n* Improve explanation of `auto`.\n* Improve commit message to explain tricky edge case.\n* Note that TLS should always be used.\n\nbrian m. carlson (1):\n  http: allow authenticating proactively\n\n Documentation/config/http.txt |  20 ++++++\n http.c                        |  62 ++++++++++++++++--\n t/t5563-simple-http-auth.sh   | 116 ++++++++++++++++++++++++++++++++++\n 3 files changed, 192 insertions(+), 6 deletions(-)\n\n"},{"id":"498400","messageId":"20240710000155.947377-2-sandals@crustytoothpaste.net","threadId":"61688","inReplyTo":"20240710000155.947377-1-sandals@crustytoothpaste.net","subject":"[PATCH v3 1/1] http: allow authenticating proactively","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2024-07-10T00:01:55Z","receivedAt":"2024-07-10T00:01:59Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"When making a request over HTTP(S), Git only sends authentication if it\nreceives a 401 response.  Thus, if a repository is open to the public\nfor reading, Git will typically never ask for authentication for fetches\nand clones.\n\nHowever, there may be times when a user would like to authenticate\nnevertheless.  For example, a forge may give higher rate limits to users\nwho authenticate because they are easier to contact in case of excessive\nuse.  Or it may be useful for a known heavy user, such as an internal\nservice, to proactively authenticate so its use can be monitored and, if\nnecessary, throttled.\n\nLet's make this possible with a new option, \"http.proactiveAuth\".  This\noption specifies a type of authentication which can be used to\nauthenticate against the host in question.  This is necessary because we\nlack the WWW-Authenticate header to provide us details; similarly, we\ncannot accept certain types of authentication because we require\ninformation from the server, such as a nonce or challenge, to\nsuccessfully authenticate.\n\nIf we're in auto mode and we got a username and password, set the\nauthentication scheme to Basic.  libcurl will not send authentication\nproactively unless there's a single choice of allowed authentication,\nand we know in this case we didn't get an authtype entry telling us what\nscheme to use, or we would have taken a different codepath and written\nthe header ourselves.  In any event, of the other schemes that libcurl\nsupports, Digest and NTLM require a nonce or challenge, which means that\nthey cannot work with proactive auth, and GSSAPI does not use a username\nand password at all, so Basic is the only logical choice among the\nbuilt-in options.\n\nNote that the existing http_proactive_auth variable signifies proactive\nauth if there are already credentials, which is different from the\nfunctionality we're adding, which always seeks credentials even if none\nare provided.  Nonetheless, t5540 tests the existing behavior for\nWebDAV-based pushes to an open repository without credentials, so we\npreserve it.  While at first this may seem an insecure and bizarre\ndecision, it may be that authentication is done with TLS certificates,\nin which case it might actually provide a quite high level of security.\nExpand the variable to use an enum to handle the additional cases and a\nhelper function to distinguish our new cases from the old ones.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n Documentation/config/http.txt |  20 ++++++\n http.c                        |  62 ++++++++++++++++--\n t/t5563-simple-http-auth.sh   | 116 ++++++++++++++++++++++++++++++++++\n 3 files changed, 192 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/http.txt b/Documentation/config/http.txt\nindex 2d4e0c9b86..9fd6c38252 100644\n--- a/Documentation/config/http.txt\n+++ b/Documentation/config/http.txt\n@@ -56,6 +56,26 @@ http.emptyAuth::\n \ta username in the URL, as libcurl normally requires a username for\n \tauthentication.\n \n+http.proactiveAuth::\n+\tAttempt authentication without first making an unauthenticated attempt and\n+\treceiving a 401 response. This can be used to ensure that all requests are\n+\tauthenticated. If `http.emptyAuth` is set to true, this value has no effect.\n++\n+If the credential helper used specifies an authentication scheme (i.e., via the\n+`authtype` field), that value will be used; if a username and password is\n+provided without a scheme, then Basic authentication is used.  The value of the\n+option determines the scheme requested from the helper. Possible values are:\n++\n+--\n+* `basic` - Request Basic authentication from the helper.\n+* `auto` - Allow the helper to pick an appropriate scheme.\n+* `none` - Disable proactive authentication.\n+--\n++\n+Note that TLS should always be used with this configuration, since otherwise it\n+is easy to accidentally expose plaintext credentials if Basic authentication\n+is selected.\n+\n http.delegation::\n \tControl GSSAPI credential delegation. The delegation is disabled\n \tby default in libcurl since version 7.21.7. Set parameter to tell\ndiff --git a/http.c b/http.c\nindex 13fa94bef3..81b5cf8039 100644\n--- a/http.c\n+++ b/http.c\n@@ -108,12 +108,19 @@ static struct {\n };\n #endif\n \n+enum proactive_auth {\n+\tPROACTIVE_AUTH_NONE = 0,\n+\tPROACTIVE_AUTH_IF_CREDENTIALS,\n+\tPROACTIVE_AUTH_AUTO,\n+\tPROACTIVE_AUTH_BASIC,\n+};\n+\n static struct credential proxy_auth = CREDENTIAL_INIT;\n static const char *curl_proxyuserpwd;\n static char *curl_cookie_file;\n static int curl_save_cookies;\n struct credential http_auth = CREDENTIAL_INIT;\n-static int http_proactive_auth;\n+static enum proactive_auth http_proactive_auth;\n static char *user_agent;\n static int curl_empty_auth = -1;\n \n@@ -148,6 +155,12 @@ static int http_schannel_check_revoke = 1;\n  */\n static int http_schannel_use_ssl_cainfo;\n \n+static int always_auth_proactively(void)\n+{\n+\treturn http_proactive_auth != PROACTIVE_AUTH_NONE &&\n+\t       http_proactive_auth != PROACTIVE_AUTH_IF_CREDENTIALS;\n+}\n+\n size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)\n {\n \tsize_t size = eltsize * nmemb;\n@@ -539,6 +552,20 @@ static int http_options(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(\"http.proactiveauth\", var)) {\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tif (!strcmp(value, \"auto\"))\n+\t\t\thttp_proactive_auth = PROACTIVE_AUTH_AUTO;\n+\t\telse if (!strcmp(value, \"basic\"))\n+\t\t\thttp_proactive_auth = PROACTIVE_AUTH_BASIC;\n+\t\telse if (!strcmp(value, \"none\"))\n+\t\t\thttp_proactive_auth = PROACTIVE_AUTH_NONE;\n+\t\telse\n+\t\t\twarning(_(\"Unknown value for http.proactiveauth\"));\n+\t\treturn 0;\n+\t}\n+\n \t/* Fall back on the default ones */\n \treturn git_default_config(var, value, ctx, data);\n }\n@@ -580,14 +607,29 @@ static void init_curl_http_auth(CURL *result)\n {\n \tif ((!http_auth.username || !*http_auth.username) &&\n \t    (!http_auth.credential || !*http_auth.credential)) {\n-\t\tif (curl_empty_auth_enabled())\n+\t\tint empty_auth = curl_empty_auth_enabled();\n+\t\tif ((empty_auth != -1 && !always_auth_proactively()) || empty_auth == 1) {\n \t\t\tcurl_easy_setopt(result, CURLOPT_USERPWD, \":\");\n-\t\treturn;\n+\t\t\treturn;\n+\t\t} else if (!always_auth_proactively()) {\n+\t\t\treturn;\n+\t\t} else if (http_proactive_auth == PROACTIVE_AUTH_BASIC) {\n+\t\t\tstrvec_push(&http_auth.wwwauth_headers, \"Basic\");\n+\t\t}\n \t}\n \n \tcredential_fill(&http_auth, 1);\n \n \tif (http_auth.password) {\n+\t\tif (always_auth_proactively()) {\n+\t\t\t/*\n+\t\t\t * We got a credential without an authtype and we don't\n+\t\t\t * know what's available.  Since our only two options at\n+\t\t\t * the moment are auto (which defaults to basic) and\n+\t\t\t * basic, use basic for now.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);\n+\t\t}\n \t\tcurl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);\n \t\tcurl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);\n \t}\n@@ -1050,7 +1092,7 @@ static CURL *get_curl_handle(void)\n #endif\n \t}\n \n-\tif (http_proactive_auth)\n+\tif (http_proactive_auth != PROACTIVE_AUTH_NONE)\n \t\tinit_curl_http_auth(result);\n \n \tif (getenv(\"GIT_SSL_VERSION\"))\n@@ -1294,7 +1336,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)\n \tif (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)\n \t\tdie(\"curl_global_init failed\");\n \n-\thttp_proactive_auth = proactive_auth;\n+\tif (proactive_auth && http_proactive_auth == PROACTIVE_AUTH_NONE)\n+\t\thttp_proactive_auth = PROACTIVE_AUTH_IF_CREDENTIALS;\n \n \tif (remote && remote->http_proxy)\n \t\tcurl_http_proxy = xstrdup(remote->http_proxy);\n@@ -1790,6 +1833,8 @@ static int handle_curl_result(struct slot_results *results)\n \t\t\t\treturn HTTP_REAUTH;\n \t\t\t}\n \t\t\tcredential_reject(&http_auth);\n+\t\t\tif (always_auth_proactively())\n+\t\t\t\thttp_proactive_auth = PROACTIVE_AUTH_NONE;\n \t\t\treturn HTTP_NOAUTH;\n \t\t} else {\n \t\t\thttp_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;\n@@ -2186,7 +2231,12 @@ static int http_request_reauth(const char *url,\n \t\t\t       struct http_get_options *options)\n {\n \tint i = 3;\n-\tint ret = http_request(url, result, target, options);\n+\tint ret;\n+\n+\tif (always_auth_proactively())\n+\t\tcredential_fill(&http_auth, 1);\n+\n+\tret = http_request(url, result, target, options);\n \n \tif (ret != HTTP_OK && ret != HTTP_REAUTH)\n \t\treturn ret;\ndiff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh\nindex 4af796de67..ba03f6a09f 100755\n--- a/t/t5563-simple-http-auth.sh\n+++ b/t/t5563-simple-http-auth.sh\n@@ -178,6 +178,122 @@ test_expect_success 'access using basic auth invalid credentials' '\n \tEOF\n '\n \n+test_expect_success 'access using basic proactive auth' '\n+\ttest_when_finished \"per_test_cleanup\" &&\n+\n+\tset_credential_reply get <<-EOF &&\n+\tusername=alice\n+\tpassword=secret-passwd\n+\tEOF\n+\n+\t# Basic base64(alice:secret-passwd)\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.valid\" <<-EOF &&\n+\tid=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==\n+\tEOF\n+\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.challenge\" <<-EOF &&\n+\tid=1 status=200\n+\tid=default status=403\n+\tEOF\n+\n+\ttest_config_global credential.helper test-helper &&\n+\ttest_config_global http.proactiveAuth basic &&\n+\tgit ls-remote \"$HTTPD_URL/custom_auth/repo.git\" &&\n+\n+\texpect_credential_query get <<-EOF &&\n+\tcapability[]=authtype\n+\tcapability[]=state\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\twwwauth[]=Basic\n+\tEOF\n+\n+\texpect_credential_query store <<-EOF\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tusername=alice\n+\tpassword=secret-passwd\n+\tEOF\n+'\n+\n+test_expect_success 'access using auto proactive auth with basic default' '\n+\ttest_when_finished \"per_test_cleanup\" &&\n+\n+\tset_credential_reply get <<-EOF &&\n+\tusername=alice\n+\tpassword=secret-passwd\n+\tEOF\n+\n+\t# Basic base64(alice:secret-passwd)\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.valid\" <<-EOF &&\n+\tid=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==\n+\tEOF\n+\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.challenge\" <<-EOF &&\n+\tid=1 status=200\n+\tid=default status=403\n+\tEOF\n+\n+\ttest_config_global credential.helper test-helper &&\n+\ttest_config_global http.proactiveAuth auto &&\n+\tgit ls-remote \"$HTTPD_URL/custom_auth/repo.git\" &&\n+\n+\texpect_credential_query get <<-EOF &&\n+\tcapability[]=authtype\n+\tcapability[]=state\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tEOF\n+\n+\texpect_credential_query store <<-EOF\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tusername=alice\n+\tpassword=secret-passwd\n+\tEOF\n+'\n+\n+test_expect_success 'access using auto proactive auth with authtype from credential helper' '\n+\ttest_when_finished \"per_test_cleanup\" &&\n+\n+\tset_credential_reply get <<-EOF &&\n+\tcapability[]=authtype\n+\tauthtype=Bearer\n+\tcredential=YS1naXQtdG9rZW4=\n+\tEOF\n+\n+\t# Basic base64(a-git-token)\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.valid\" <<-EOF &&\n+\tid=1 creds=Bearer YS1naXQtdG9rZW4=\n+\tEOF\n+\n+\tCHALLENGE=\"$HTTPD_ROOT_PATH/custom-auth.challenge\" &&\n+\n+\tcat >\"$HTTPD_ROOT_PATH/custom-auth.challenge\" <<-EOF &&\n+\tid=1 status=200\n+\tid=default status=403\n+\tEOF\n+\n+\ttest_config_global credential.helper test-helper &&\n+\ttest_config_global http.proactiveAuth auto &&\n+\tgit ls-remote \"$HTTPD_URL/custom_auth/repo.git\" &&\n+\n+\texpect_credential_query get <<-EOF &&\n+\tcapability[]=authtype\n+\tcapability[]=state\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tEOF\n+\n+\texpect_credential_query store <<-EOF\n+\tcapability[]=authtype\n+\tauthtype=Bearer\n+\tcredential=YS1naXQtdG9rZW4=\n+\tprotocol=http\n+\thost=$HTTPD_DEST\n+\tEOF\n+'\n+\n test_expect_success 'access using basic auth with extra challenges' '\n \ttest_when_finished \"per_test_cleanup\" &&\n \n"}]}