git/t/t5563-simple-http-auth.sh
brian m. carlson 610cbc1dfb 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 <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2024-07-09 21:27:51 -07:00

679 lines
18 KiB
Bash
Executable file

#!/bin/sh
test_description='test http auth header and credential helper interop'
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-httpd.sh
enable_cgipassauth
if ! test_have_prereq CGIPASSAUTH
then
skip_all="no CGIPassAuth support"
test_done
fi
start_httpd
test_expect_success 'setup_credential_helper' '
mkdir "$TRASH_DIRECTORY/bin" &&
PATH=$PATH:"$TRASH_DIRECTORY/bin" &&
export PATH &&
CREDENTIAL_HELPER="$TRASH_DIRECTORY/bin/git-credential-test-helper" &&
write_script "$CREDENTIAL_HELPER" <<-\EOF
cmd=$1
teefile=$cmd-query-temp.cred
catfile=$cmd-reply.cred
sed -n -e "/^$/q" -e "p" >>$teefile
state=$(sed -ne "s/^state\[\]=helper://p" "$teefile")
if test -z "$state"
then
mv "$teefile" "$cmd-query.cred"
else
mv "$teefile" "$cmd-query-$state.cred"
catfile="$cmd-reply-$state.cred"
fi
if test "$cmd" = "get"
then
cat $catfile
fi
EOF
'
set_credential_reply () {
local suffix="$(test -n "$2" && echo "-$2")"
cat >"$TRASH_DIRECTORY/$1-reply$suffix.cred"
}
expect_credential_query () {
local suffix="$(test -n "$2" && echo "-$2")"
cat >"$TRASH_DIRECTORY/$1-expect$suffix.cred" &&
test_cmp "$TRASH_DIRECTORY/$1-expect$suffix.cred" \
"$TRASH_DIRECTORY/$1-query$suffix.cred"
}
per_test_cleanup () {
rm -f *.cred &&
rm -f "$HTTPD_ROOT_PATH"/custom-auth.valid \
"$HTTPD_ROOT_PATH"/custom-auth.challenge
}
test_expect_success 'setup repository' '
test_commit foo &&
git init --bare "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
git push --mirror "$HTTPD_DOCUMENT_ROOT_PATH/repo.git"
'
test_expect_success 'access using basic auth' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default response=WWW-Authenticate: Basic realm="example.com"
EOF
test_config_global credential.helper test-helper &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=Basic realm="example.com"
EOF
expect_credential_query store <<-EOF
protocol=http
host=$HTTPD_DEST
username=alice
password=secret-passwd
EOF
'
test_expect_success 'access using basic auth via authtype' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
capability[]=authtype
authtype=Basic
credential=YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default response=WWW-Authenticate: Basic realm="example.com"
EOF
test_config_global credential.helper test-helper &&
GIT_CURL_VERBOSE=1 git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=Basic realm="example.com"
EOF
expect_credential_query store <<-EOF
capability[]=authtype
authtype=Basic
credential=YWxpY2U6c2VjcmV0LXBhc3N3ZA==
protocol=http
host=$HTTPD_DEST
EOF
'
test_expect_success 'access using basic auth invalid credentials' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=baduser
password=wrong-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default response=WWW-Authenticate: Basic realm="example.com"
EOF
test_config_global credential.helper test-helper &&
test_must_fail git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=Basic realm="example.com"
EOF
expect_credential_query erase <<-EOF
protocol=http
host=$HTTPD_DEST
username=baduser
password=wrong-passwd
wwwauth[]=Basic realm="example.com"
EOF
'
test_expect_success 'access using basic proactive auth' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default status=403
EOF
test_config_global credential.helper test-helper &&
test_config_global http.proactiveAuth basic &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=Basic
EOF
expect_credential_query store <<-EOF
protocol=http
host=$HTTPD_DEST
username=alice
password=secret-passwd
EOF
'
test_expect_success 'access using auto proactive auth with basic default' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default status=403
EOF
test_config_global credential.helper test-helper &&
test_config_global http.proactiveAuth auto &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
EOF
expect_credential_query store <<-EOF
protocol=http
host=$HTTPD_DEST
username=alice
password=secret-passwd
EOF
'
test_expect_success 'access using auto proactive auth with authtype from credential helper' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
capability[]=authtype
authtype=Bearer
credential=YS1naXQtdG9rZW4=
EOF
# Basic base64(a-git-token)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Bearer YS1naXQtdG9rZW4=
EOF
CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default status=403
EOF
test_config_global credential.helper test-helper &&
test_config_global http.proactiveAuth auto &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
EOF
expect_credential_query store <<-EOF
capability[]=authtype
authtype=Bearer
credential=YS1naXQtdG9rZW4=
protocol=http
host=$HTTPD_DEST
EOF
'
test_expect_success 'access using basic auth with extra challenges' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default response=WWW-Authenticate: FooBar param1="value1" param2="value2"
id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0
id=default response=WWW-Authenticate: Basic realm="example.com"
EOF
test_config_global credential.helper test-helper &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=FooBar param1="value1" param2="value2"
wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
wwwauth[]=Basic realm="example.com"
EOF
expect_credential_query store <<-EOF
protocol=http
host=$HTTPD_DEST
username=alice
password=secret-passwd
EOF
'
test_expect_success 'access using basic auth mixed-case wwwauth header name' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default response=www-authenticate: foobar param1="value1" param2="value2"
id=default response=WWW-AUTHENTICATE: BEARER authorize_uri="id.example.com" p=1 q=0
id=default response=WwW-aUtHeNtIcAtE: baSiC realm="example.com"
EOF
test_config_global credential.helper test-helper &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=foobar param1="value1" param2="value2"
wwwauth[]=BEARER authorize_uri="id.example.com" p=1 q=0
wwwauth[]=baSiC realm="example.com"
EOF
expect_credential_query store <<-EOF
protocol=http
host=$HTTPD_DEST
username=alice
password=secret-passwd
EOF
'
test_expect_success 'access using basic auth with wwwauth header continuations' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
# Note that leading and trailing whitespace is important to correctly
# simulate a continuation/folded header.
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default response=WWW-Authenticate: FooBar param1="value1"
id=default response= param2="value2"
id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com"
id=default response= p=1
id=default response= q=0
id=default response=WWW-Authenticate: Basic realm="example.com"
EOF
test_config_global credential.helper test-helper &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=FooBar param1="value1" param2="value2"
wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
wwwauth[]=Basic realm="example.com"
EOF
expect_credential_query store <<-EOF
protocol=http
host=$HTTPD_DEST
username=alice
password=secret-passwd
EOF
'
test_expect_success 'access using basic auth with wwwauth header empty continuations' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
# Note that leading and trailing whitespace is important to correctly
# simulate a continuation/folded header.
printf "id=1 status=200\n" >"$CHALLENGE" &&
printf "id=default response=WWW-Authenticate: FooBar param1=\"value1\"\r\n" >>"$CHALLENGE" &&
printf "id=default response= \r\n" >>"$CHALLENGE" &&
printf "id=default response= param2=\"value2\"\r\n" >>"$CHALLENGE" &&
printf "id=default response=WWW-Authenticate: Bearer authorize_uri=\"id.example.com\"\r\n" >>"$CHALLENGE" &&
printf "id=default response= p=1\r\n" >>"$CHALLENGE" &&
printf "id=default response= \r\n" >>"$CHALLENGE" &&
printf "id=default response= q=0\r\n" >>"$CHALLENGE" &&
printf "id=default response=WWW-Authenticate: Basic realm=\"example.com\"\r\n" >>"$CHALLENGE" &&
test_config_global credential.helper test-helper &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=FooBar param1="value1" param2="value2"
wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
wwwauth[]=Basic realm="example.com"
EOF
expect_credential_query store <<-EOF
protocol=http
host=$HTTPD_DEST
username=alice
password=secret-passwd
EOF
'
test_expect_success 'access using basic auth with wwwauth header mixed line-endings' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
# Note that leading and trailing whitespace is important to correctly
# simulate a continuation/folded header.
printf "id=1 status=200\n" >"$CHALLENGE" &&
printf "id=default response=WWW-Authenticate: FooBar param1=\"value1\"\r\n" >>"$CHALLENGE" &&
printf "id=default response= \r\n" >>"$CHALLENGE" &&
printf "id=default response=\tparam2=\"value2\"\r\n" >>"$CHALLENGE" &&
printf "id=default response=WWW-Authenticate: Basic realm=\"example.com\"" >>"$CHALLENGE" &&
test_config_global credential.helper test-helper &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=FooBar param1="value1" param2="value2"
wwwauth[]=Basic realm="example.com"
EOF
expect_credential_query store <<-EOF
protocol=http
host=$HTTPD_DEST
username=alice
password=secret-passwd
EOF
'
test_expect_success 'access using bearer auth' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
capability[]=authtype
authtype=Bearer
credential=YS1naXQtdG9rZW4=
EOF
# Basic base64(a-git-token)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Bearer YS1naXQtdG9rZW4=
EOF
CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default response=WWW-Authenticate: FooBar param1="value1" param2="value2"
id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0
id=default response=WWW-Authenticate: Basic realm="example.com"
EOF
test_config_global credential.helper test-helper &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=FooBar param1="value1" param2="value2"
wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
wwwauth[]=Basic realm="example.com"
EOF
expect_credential_query store <<-EOF
capability[]=authtype
authtype=Bearer
credential=YS1naXQtdG9rZW4=
protocol=http
host=$HTTPD_DEST
EOF
'
test_expect_success 'access using bearer auth with invalid credentials' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
capability[]=authtype
authtype=Bearer
credential=incorrect-token
EOF
# Basic base64(a-git-token)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Bearer YS1naXQtdG9rZW4=
EOF
CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default response=WWW-Authenticate: FooBar param1="value1" param2="value2"
id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0
id=default response=WWW-Authenticate: Basic realm="example.com"
EOF
test_config_global credential.helper test-helper &&
test_must_fail git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=FooBar param1="value1" param2="value2"
wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
wwwauth[]=Basic realm="example.com"
EOF
expect_credential_query erase <<-EOF
capability[]=authtype
authtype=Bearer
credential=incorrect-token
protocol=http
host=$HTTPD_DEST
wwwauth[]=FooBar param1="value1" param2="value2"
wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
wwwauth[]=Basic realm="example.com"
EOF
'
test_expect_success 'access using three-legged auth' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
capability[]=authtype
capability[]=state
authtype=Multistage
credential=YS1naXQtdG9rZW4=
state[]=helper:foobar
continue=1
EOF
set_credential_reply get foobar <<-EOF &&
capability[]=authtype
capability[]=state
authtype=Multistage
credential=YW5vdGhlci10b2tlbg==
state[]=helper:bazquux
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Multistage YS1naXQtdG9rZW4=
id=2 creds=Multistage YW5vdGhlci10b2tlbg==
EOF
CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=401 response=WWW-Authenticate: Multistage challenge="456"
id=1 status=401 response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0
id=2 status=200
id=default response=WWW-Authenticate: Multistage challenge="123"
id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0
EOF
test_config_global credential.helper test-helper &&
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
expect_credential_query get <<-EOF &&
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=Multistage challenge="123"
wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
EOF
expect_credential_query get foobar <<-EOF &&
capability[]=authtype
capability[]=state
authtype=Multistage
protocol=http
host=$HTTPD_DEST
wwwauth[]=Multistage challenge="456"
wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
state[]=helper:foobar
EOF
expect_credential_query store bazquux <<-EOF
capability[]=authtype
capability[]=state
authtype=Multistage
credential=YW5vdGhlci10b2tlbg==
protocol=http
host=$HTTPD_DEST
state[]=helper:bazquux
EOF
'
test_done