git/t/lib-credential.sh
brian m. carlson 30c0a3036f t: add credential tests for authtype
It's helpful to have some basic tests for credential helpers supporting
the authtype and credential fields.  Let's add some tests for this case
so that we can make sure newly supported helpers work correctly.

Note that we explicitly check that credential helpers can produce
different sets of authtype and credential values based on the username.
While the username is not used in the HTTP protocol with authtype and
credential, it can still be specified in the URL and thus may be part of
the protocol.  Additionally, because it is common for users to have
multiple accounts on one service (say, both personal and professional
accounts), it's very helpful to be able to store different credentials
for different accounts in the same helper, and that doesn't become less
useful if one is using, say, Bearer authentication instead of Basic.
Thus, credential helpers should be expected to support this
functionality as basic functionality, so verify here that they do so.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2024-04-16 22:39:08 -07:00

670 lines
14 KiB
Bash

# Shell library for testing credential handling including helpers. See t0302
# for an example of testing a specific helper.
# Try a set of credential helpers; the expected stdin,
# stdout and stderr should be provided on stdin,
# separated by "--".
check() {
credential_opts=
credential_cmd=$1
shift
for arg in "$@"; do
credential_opts="$credential_opts -c credential.helper='$arg'"
done
read_chunk >stdin &&
read_chunk >expect-stdout &&
read_chunk >expect-stderr &&
if ! eval "git $credential_opts credential $credential_cmd <stdin >stdout 2>stderr"; then
echo "git credential failed with code $?" &&
cat stderr &&
false
fi &&
test_cmp expect-stdout stdout &&
test_cmp expect-stderr stderr
}
read_chunk() {
while read line; do
case "$line" in
--) break ;;
*) echo "$line" ;;
esac
done
}
# Clear any residual data from previous tests. We only
# need this when testing third-party helpers which read and
# write outside of our trash-directory sandbox.
#
# Don't bother checking for success here, as it is
# outside the scope of tests and represents a best effort to
# clean up after ourselves.
helper_test_clean() {
reject $1 https example.com store-user
reject $1 https example.com user1
reject $1 https example.com user2
reject $1 https example.com user-expiry
reject $1 https example.com user-expiry-overwrite
reject $1 https example.com user4
reject $1 https example.com user-distinct-pass
reject $1 https example.com user-overwrite
reject $1 https example.com user-erase1
reject $1 https example.com user-erase2
reject $1 https victim.example.com user
reject $1 http path.tld user
reject $1 https timeout.tld user
reject $1 https sso.tld
}
reject() {
(
echo protocol=$2
echo host=$3
echo username=$4
) | git -c credential.helper=$1 credential reject
}
helper_test() {
HELPER=$1
test_expect_success "helper ($HELPER) has no existing data" '
check fill $HELPER <<-\EOF
protocol=https
host=example.com
--
protocol=https
host=example.com
username=askpass-username
password=askpass-password
--
askpass: Username for '\''https://example.com'\'':
askpass: Password for '\''https://askpass-username@example.com'\'':
EOF
'
test_expect_success "helper ($HELPER) stores password" '
check approve $HELPER <<-\EOF
protocol=https
host=example.com
username=store-user
password=store-pass
EOF
'
test_expect_success "helper ($HELPER) can retrieve password" '
check fill $HELPER <<-\EOF
protocol=https
host=example.com
--
protocol=https
host=example.com
username=store-user
password=store-pass
--
EOF
'
test_expect_success "helper ($HELPER) requires matching protocol" '
check fill $HELPER <<-\EOF
protocol=http
host=example.com
--
protocol=http
host=example.com
username=askpass-username
password=askpass-password
--
askpass: Username for '\''http://example.com'\'':
askpass: Password for '\''http://askpass-username@example.com'\'':
EOF
'
test_expect_success "helper ($HELPER) requires matching host" '
check fill $HELPER <<-\EOF
protocol=https
host=other.tld
--
protocol=https
host=other.tld
username=askpass-username
password=askpass-password
--
askpass: Username for '\''https://other.tld'\'':
askpass: Password for '\''https://askpass-username@other.tld'\'':
EOF
'
test_expect_success "helper ($HELPER) requires matching username" '
check fill $HELPER <<-\EOF
protocol=https
host=example.com
username=other
--
protocol=https
host=example.com
username=other
password=askpass-password
--
askpass: Password for '\''https://other@example.com'\'':
EOF
'
test_expect_success "helper ($HELPER) requires matching path" '
test_config credential.usehttppath true &&
check approve $HELPER <<-\EOF &&
protocol=http
host=path.tld
path=foo.git
username=user
password=pass
EOF
check fill $HELPER <<-\EOF
protocol=http
host=path.tld
path=bar.git
--
protocol=http
host=path.tld
path=bar.git
username=askpass-username
password=askpass-password
--
askpass: Username for '\''http://path.tld/bar.git'\'':
askpass: Password for '\''http://askpass-username@path.tld/bar.git'\'':
EOF
'
test_expect_success "helper ($HELPER) overwrites on store" '
check approve $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user-overwrite
password=pass1
EOF
check approve $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user-overwrite
password=pass2
EOF
check fill $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user-overwrite
--
protocol=https
host=example.com
username=user-overwrite
password=pass2
EOF
check reject $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user-overwrite
password=pass2
EOF
check fill $HELPER <<-\EOF
protocol=https
host=example.com
username=user-overwrite
--
protocol=https
host=example.com
username=user-overwrite
password=askpass-password
--
askpass: Password for '\''https://user-overwrite@example.com'\'':
EOF
'
test_expect_success "helper ($HELPER) can forget host" '
check reject $HELPER <<-\EOF &&
protocol=https
host=example.com
EOF
check fill $HELPER <<-\EOF
protocol=https
host=example.com
--
protocol=https
host=example.com
username=askpass-username
password=askpass-password
--
askpass: Username for '\''https://example.com'\'':
askpass: Password for '\''https://askpass-username@example.com'\'':
EOF
'
test_expect_success "helper ($HELPER) can store multiple users" '
check approve $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user1
password=pass1
EOF
check approve $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user2
password=pass2
EOF
check fill $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user1
--
protocol=https
host=example.com
username=user1
password=pass1
EOF
check fill $HELPER <<-\EOF
protocol=https
host=example.com
username=user2
--
protocol=https
host=example.com
username=user2
password=pass2
EOF
'
test_expect_success "helper ($HELPER) does not erase a password distinct from input" '
check approve $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user-distinct-pass
password=pass1
EOF
check reject $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user-distinct-pass
password=pass2
EOF
check fill $HELPER <<-\EOF
protocol=https
host=example.com
username=user-distinct-pass
--
protocol=https
host=example.com
username=user-distinct-pass
password=pass1
EOF
'
test_expect_success "helper ($HELPER) can forget user" '
check reject $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user1
EOF
check fill $HELPER <<-\EOF
protocol=https
host=example.com
username=user1
--
protocol=https
host=example.com
username=user1
password=askpass-password
--
askpass: Password for '\''https://user1@example.com'\'':
EOF
'
test_expect_success "helper ($HELPER) remembers other user" '
check fill $HELPER <<-\EOF
protocol=https
host=example.com
username=user2
--
protocol=https
host=example.com
username=user2
password=pass2
EOF
'
test_expect_success "helper ($HELPER) can store empty username" '
check approve $HELPER <<-\EOF &&
protocol=https
host=sso.tld
username=
password=
EOF
check fill $HELPER <<-\EOF
protocol=https
host=sso.tld
--
protocol=https
host=sso.tld
username=
password=
EOF
'
test_expect_success "helper ($HELPER) erases all matching credentials" '
check approve $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user-erase1
password=pass1
EOF
check approve $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user-erase2
password=pass1
EOF
check reject $HELPER <<-\EOF &&
protocol=https
host=example.com
EOF
check fill $HELPER <<-\EOF
protocol=https
host=example.com
--
protocol=https
host=example.com
username=askpass-username
password=askpass-password
--
askpass: Username for '\''https://example.com'\'':
askpass: Password for '\''https://askpass-username@example.com'\'':
EOF
'
: ${GIT_TEST_LONG_CRED_BUFFER:=1024}
# 23 bytes accounts for "wwwauth[]=basic realm=" plus NUL
LONG_VALUE_LEN=$((GIT_TEST_LONG_CRED_BUFFER - 23))
LONG_VALUE=$(perl -e 'print "a" x shift' $LONG_VALUE_LEN)
test_expect_success "helper ($HELPER) not confused by long header" '
check approve $HELPER <<-\EOF &&
protocol=https
host=victim.example.com
username=user
password=to-be-stolen
EOF
check fill $HELPER <<-EOF
protocol=https
host=badguy.example.com
wwwauth[]=basic realm=${LONG_VALUE}host=victim.example.com
--
protocol=https
host=badguy.example.com
username=askpass-username
password=askpass-password
wwwauth[]=basic realm=${LONG_VALUE}host=victim.example.com
--
askpass: Username for '\''https://badguy.example.com'\'':
askpass: Password for '\''https://askpass-username@badguy.example.com'\'':
EOF
'
}
helper_test_timeout() {
HELPER="$*"
test_expect_success "helper ($HELPER) times out" '
check approve "$HELPER" <<-\EOF &&
protocol=https
host=timeout.tld
username=user
password=pass
EOF
sleep 2 &&
check fill "$HELPER" <<-\EOF
protocol=https
host=timeout.tld
--
protocol=https
host=timeout.tld
username=askpass-username
password=askpass-password
--
askpass: Username for '\''https://timeout.tld'\'':
askpass: Password for '\''https://askpass-username@timeout.tld'\'':
EOF
'
}
helper_test_password_expiry_utc() {
HELPER=$1
test_expect_success "helper ($HELPER) stores password_expiry_utc" '
check approve $HELPER <<-\EOF
protocol=https
host=example.com
username=user-expiry
password=pass
password_expiry_utc=9999999999
EOF
'
test_expect_success "helper ($HELPER) gets password_expiry_utc" '
check fill $HELPER <<-\EOF
protocol=https
host=example.com
username=user-expiry
--
protocol=https
host=example.com
username=user-expiry
password=pass
password_expiry_utc=9999999999
--
EOF
'
test_expect_success "helper ($HELPER) overwrites when password_expiry_utc changes" '
check approve $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user-expiry-overwrite
password=pass1
password_expiry_utc=9999999998
EOF
check approve $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user-expiry-overwrite
password=pass2
password_expiry_utc=9999999999
EOF
check fill $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user-expiry-overwrite
--
protocol=https
host=example.com
username=user-expiry-overwrite
password=pass2
password_expiry_utc=9999999999
EOF
check reject $HELPER <<-\EOF &&
protocol=https
host=example.com
username=user-expiry-overwrite
password=pass2
EOF
check fill $HELPER <<-\EOF
protocol=https
host=example.com
username=user-expiry-overwrite
--
protocol=https
host=example.com
username=user-expiry-overwrite
password=askpass-password
--
askpass: Password for '\''https://user-expiry-overwrite@example.com'\'':
EOF
'
}
helper_test_oauth_refresh_token() {
HELPER=$1
test_expect_success "helper ($HELPER) stores oauth_refresh_token" '
check approve $HELPER <<-\EOF
protocol=https
host=example.com
username=user4
password=pass
oauth_refresh_token=xyzzy
EOF
'
test_expect_success "helper ($HELPER) gets oauth_refresh_token" '
check fill $HELPER <<-\EOF
protocol=https
host=example.com
username=user4
--
protocol=https
host=example.com
username=user4
password=pass
oauth_refresh_token=xyzzy
--
EOF
'
}
helper_test_authtype() {
HELPER=$1
test_expect_success "helper ($HELPER) stores authtype and credential" '
check approve $HELPER <<-\EOF
capability[]=authtype
authtype=Bearer
credential=random-token
protocol=https
host=git.example.com
EOF
'
test_expect_success "helper ($HELPER) gets authtype and credential" '
check fill $HELPER <<-\EOF
capability[]=authtype
protocol=https
host=git.example.com
--
capability[]=authtype
authtype=Bearer
credential=random-token
protocol=https
host=git.example.com
--
EOF
'
test_expect_success "helper ($HELPER) stores authtype and credential with username" '
check approve $HELPER <<-\EOF
capability[]=authtype
authtype=Bearer
credential=other-token
protocol=https
host=git.example.com
username=foobar
EOF
'
test_expect_success "helper ($HELPER) gets authtype and credential with username" '
check fill $HELPER <<-\EOF
capability[]=authtype
protocol=https
host=git.example.com
username=foobar
--
capability[]=authtype
authtype=Bearer
credential=other-token
protocol=https
host=git.example.com
username=foobar
--
EOF
'
test_expect_success "helper ($HELPER) does not get authtype and credential with different username" '
check fill $HELPER <<-\EOF
capability[]=authtype
protocol=https
host=git.example.com
username=barbaz
--
protocol=https
host=git.example.com
username=barbaz
password=askpass-password
--
askpass: Password for '\''https://barbaz@git.example.com'\'':
EOF
'
test_expect_success "helper ($HELPER) does not store ephemeral authtype and credential" '
check approve $HELPER <<-\EOF &&
capability[]=authtype
authtype=Bearer
credential=git2-token
protocol=https
host=git2.example.com
ephemeral=1
EOF
check fill $HELPER <<-\EOF
capability[]=authtype
protocol=https
host=git2.example.com
--
protocol=https
host=git2.example.com
username=askpass-username
password=askpass-password
--
askpass: Username for '\''https://git2.example.com'\'':
askpass: Password for '\''https://askpass-username@git2.example.com'\'':
EOF
'
test_expect_success "helper ($HELPER) does not store ephemeral username and password" '
check approve $HELPER <<-\EOF &&
capability[]=authtype
protocol=https
host=git2.example.com
user=barbaz
password=secret
ephemeral=1
EOF
check fill $HELPER <<-\EOF
capability[]=authtype
protocol=https
host=git2.example.com
--
protocol=https
host=git2.example.com
username=askpass-username
password=askpass-password
--
askpass: Username for '\''https://git2.example.com'\'':
askpass: Password for '\''https://askpass-username@git2.example.com'\'':
EOF
'
}
write_script askpass <<\EOF
echo >&2 askpass: $*
what=$(echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z)
echo "askpass-$what"
EOF
GIT_ASKPASS="$PWD/askpass"
export GIT_ASKPASS