3 test_description
='basic credential helper tests'
5 TEST_PASSES_SANITIZE_LEAK
=true
7 .
"$TEST_DIRECTORY"/lib-credential.sh
9 test_expect_success
'setup helper scripts' '
11 whoami=$(echo $0 | sed s/.*git-credential-//)
12 echo >&2 "$whoami: $*"
15 while read key value; do
16 echo >&2 "$whoami: $key=$value"
17 if test -z "${key%%*\[\]}"
20 eval "$key=\"\$$key $value\""
28 write_script git-credential-useless <<-\EOF &&
33 write_script git-credential-quit <<-\EOF &&
38 write_script git-credential-verbatim <<-\EOF &&
42 test -z "$user" || echo username=$user
43 test -z "$pass" || echo password=$pass
46 write_script git-credential-verbatim-cred <<-\EOF &&
50 echo capability[]=authtype
51 echo capability[]=state
52 test -z "${capability##*authtype*}" || exit 0
53 test -z "$authtype" || echo authtype=$authtype
54 test -z "$credential" || echo credential=$credential
55 test -z "${capability##*state*}" || exit 0
56 echo state[]=verbatim-cred:foo
59 write_script git-credential-verbatim-ephemeral <<-\EOF &&
63 echo capability[]=authtype
64 test -z "${capability##*authtype*}" || exit 0
65 test -z "$authtype" || echo authtype=$authtype
66 test -z "$credential" || echo credential=$credential
70 write_script git-credential-verbatim-with-expiry <<-\EOF &&
75 test -z "$user" || echo username=$user
76 test -z "$pass" || echo password=$pass
77 test -z "$pexpiry" || echo password_expiry_utc=$pexpiry
83 test_expect_success
'credential_fill invokes helper' '
84 check fill "verbatim foo bar" <<-\EOF
94 verbatim: protocol=http
95 verbatim: host=example.com
99 test_expect_success
'credential_fill invokes helper with credential' '
100 check fill "verbatim-cred Bearer token" <<-\EOF
101 capability[]=authtype
105 capability[]=authtype
112 verbatim-cred: capability[]=authtype
113 verbatim-cred: protocol=http
114 verbatim-cred: host=example.com
118 test_expect_success
'credential_fill invokes helper with ephemeral credential' '
119 check fill "verbatim-ephemeral Bearer token" <<-\EOF
120 capability[]=authtype
124 capability[]=authtype
131 verbatim-ephemeral: get
132 verbatim-ephemeral: capability[]=authtype
133 verbatim-ephemeral: protocol=http
134 verbatim-ephemeral: host=example.com
137 test_expect_success
'credential_fill invokes helper with credential and state' '
138 check fill "verbatim-cred Bearer token" <<-\EOF
139 capability[]=authtype
144 capability[]=authtype
150 state[]=verbatim-cred:foo
153 verbatim-cred: capability[]=authtype
154 verbatim-cred: capability[]=state
155 verbatim-cred: protocol=http
156 verbatim-cred: host=example.com
160 test_expect_success
'credential_fill invokes multiple helpers' '
161 check fill useless "verbatim foo bar" <<-\EOF
171 useless: protocol=http
172 useless: host=example.com
174 verbatim: protocol=http
175 verbatim: host=example.com
179 test_expect_success
'credential_fill response does not get capabilities when helpers are incapable' '
180 check fill useless "verbatim foo bar" <<-\EOF
181 capability[]=authtype
192 useless: capability[]=authtype
193 useless: capability[]=state
194 useless: protocol=http
195 useless: host=example.com
197 verbatim: capability[]=authtype
198 verbatim: capability[]=state
199 verbatim: protocol=http
200 verbatim: host=example.com
204 test_expect_success
'credential_fill response does not get capabilities when caller is incapable' '
205 check fill "verbatim-cred Bearer token" <<-\EOF
213 verbatim-cred: protocol=http
214 verbatim-cred: host=example.com
218 test_expect_success
'credential_fill stops when we get a full response' '
219 check fill "verbatim one two" "verbatim three four" <<-\EOF
229 verbatim: protocol=http
230 verbatim: host=example.com
234 test_expect_success
'credential_fill thinks a credential is a full response' '
235 check fill "verbatim-cred Bearer token" "verbatim three four" <<-\EOF
236 capability[]=authtype
240 capability[]=authtype
247 verbatim-cred: capability[]=authtype
248 verbatim-cred: protocol=http
249 verbatim-cred: host=example.com
253 test_expect_success
'credential_fill continues through partial response' '
254 check fill "verbatim one \"\"" "verbatim two three" <<-\EOF
264 verbatim: protocol=http
265 verbatim: host=example.com
267 verbatim: protocol=http
268 verbatim: host=example.com
269 verbatim: username=one
273 test_expect_success
'credential_fill populates password_expiry_utc' '
274 check fill "verbatim-with-expiry one two 9999999999" <<-\EOF
282 password_expiry_utc=9999999999
284 verbatim-with-expiry: get
285 verbatim-with-expiry: protocol=http
286 verbatim-with-expiry: host=example.com
290 test_expect_success
'credential_fill ignores expired password' '
291 check fill "verbatim-with-expiry one two 5" "verbatim three four" <<-\EOF
300 verbatim-with-expiry: get
301 verbatim-with-expiry: protocol=http
302 verbatim-with-expiry: host=example.com
304 verbatim: protocol=http
305 verbatim: host=example.com
306 verbatim: username=one
310 test_expect_success
'credential_fill passes along metadata' '
311 check fill "verbatim one two" <<-\EOF
323 verbatim: protocol=ftp
324 verbatim: host=example.com
325 verbatim: path=foo.git
329 test_expect_success
'credential_fill produces no credential without capability' '
330 check fill "verbatim-cred Bearer token" <<-\EOF
338 verbatim-cred: protocol=http
339 verbatim-cred: host=example.com
343 test_expect_success
'credential_approve calls all helpers' '
344 check approve useless "verbatim one two" <<-\EOF
352 useless: protocol=http
353 useless: host=example.com
354 useless: username=foo
355 useless: password=bar
357 verbatim: protocol=http
358 verbatim: host=example.com
359 verbatim: username=foo
360 verbatim: password=bar
364 test_expect_success
'credential_approve stores password expiry' '
365 check approve useless <<-\EOF
370 password_expiry_utc=9999999999
374 useless: protocol=http
375 useless: host=example.com
376 useless: username=foo
377 useless: password=bar
378 useless: password_expiry_utc=9999999999
382 test_expect_success
'credential_approve stores oauth refresh token' '
383 check approve useless <<-\EOF
388 oauth_refresh_token=xyzzy
392 useless: protocol=http
393 useless: host=example.com
394 useless: username=foo
395 useless: password=bar
396 useless: oauth_refresh_token=xyzzy
400 test_expect_success
'do not bother storing password-less credential' '
401 check approve useless <<-\EOF
410 test_expect_success
'credential_approve does not store expired password' '
411 check approve useless <<-\EOF
416 password_expiry_utc=5
422 test_expect_success
'credential_reject calls all helpers' '
423 check reject useless "verbatim one two" <<-\EOF
431 useless: protocol=http
432 useless: host=example.com
433 useless: username=foo
434 useless: password=bar
436 verbatim: protocol=http
437 verbatim: host=example.com
438 verbatim: username=foo
439 verbatim: password=bar
443 test_expect_success
'credential_reject erases credential regardless of expiry' '
444 check reject useless <<-\EOF
449 password_expiry_utc=5
453 useless: protocol=http
454 useless: host=example.com
455 useless: username=foo
456 useless: password=bar
457 useless: password_expiry_utc=5
461 test_expect_success
'usernames can be preserved' '
462 check fill "verbatim \"\" three" <<-\EOF
473 verbatim: protocol=http
474 verbatim: host=example.com
475 verbatim: username=one
479 test_expect_success
'usernames can be overridden' '
480 check fill "verbatim two three" <<-\EOF
491 verbatim: protocol=http
492 verbatim: host=example.com
493 verbatim: username=one
497 test_expect_success
'do not bother completing already-full credential' '
498 check fill "verbatim three four" <<-\EOF
512 # We can't test the basic terminal password prompt here because
513 # getpass() tries too hard to find the real terminal. But if our
514 # askpass helper is run, we know the internal getpass is working.
515 test_expect_success
'empty helper list falls back to internal getpass' '
522 username=askpass-username
523 password=askpass-password
525 askpass: Username for '\''http://example.com'\'':
526 askpass: Password for '\''http://askpass-username@example.com'\'':
530 test_expect_success
'internal getpass does not ask for known username' '
539 password=askpass-password
541 askpass: Password for '\''http://foo@example.com'\'':
545 test_expect_success
'git-credential respects core.askPass' '
546 write_script alternate-askpass <<-\EOF &&
547 echo >&2 "alternate askpass invoked"
550 test_config core.askpass "$PWD/alternate-askpass" &&
552 # unset GIT_ASKPASS set by lib-credential.sh which would
553 # override our config, but do so in a subshell so that we do
554 # not interfere with other tests
555 sane_unset GIT_ASKPASS &&
562 username=alternate-value
563 password=alternate-value
565 alternate askpass invoked
566 alternate askpass invoked
576 test_expect_success
'respect configured credentials' '
577 test_config credential.helper "$HELPER" &&
590 test_expect_success
'match configured credential' '
591 test_config credential.https://example.com.helper "$HELPER" &&
605 test_expect_success
'do not match configured credential' '
606 test_config credential.https://foo.helper "$HELPER" &&
613 username=askpass-username
614 password=askpass-password
616 askpass: Username for '\''https://bar'\'':
617 askpass: Password for '\''https://askpass-username@bar'\'':
621 test_expect_success
'match multiple configured helpers' '
622 test_config credential.helper "verbatim \"\" \"\"" &&
623 test_config credential.https://example.com.helper "$HELPER" &&
635 verbatim: protocol=https
636 verbatim: host=example.com
640 test_expect_success
'match multiple configured helpers with URLs' '
641 test_config credential.https://example.com/repo.git.helper "verbatim \"\" \"\"" &&
642 test_config credential.https://example.com.helper "$HELPER" &&
654 verbatim: protocol=https
655 verbatim: host=example.com
659 test_expect_success
'match percent-encoded values' '
660 test_config credential.https://example.com/%2566.git.helper "$HELPER" &&
662 url=https://example.com/%2566.git
672 test_expect_success
'match percent-encoded UTF-8 values in path' '
673 test_config credential.https://example.com.useHttpPath true &&
674 test_config credential.https://example.com/perú.git.helper "$HELPER" &&
676 url=https://example.com/per%C3%BA.git
687 test_expect_success
'match percent-encoded values in username' '
688 test_config credential.https://user%2fname@example.com/foo/bar.git.helper "$HELPER" &&
690 url=https://user%2fname@example.com/foo/bar.git
700 test_expect_success
'fetch with multiple path components' '
701 test_unconfig credential.helper &&
702 test_config credential.https://example.com/foo/repo.git.helper "verbatim foo bar" &&
704 url=https://example.com/foo/repo.git
712 verbatim: protocol=https
713 verbatim: host=example.com
717 test_expect_success
'pull username from config' '
718 test_config credential.https://example.com.username foo &&
726 password=askpass-password
728 askpass: Password for '\''https://foo@example.com'\'':
732 test_expect_success
'honors username from URL over helper (URL)' '
733 test_config credential.https://example.com.username bob &&
734 test_config credential.https://example.com.helper "verbatim \"\" bar" &&
736 url=https://alice@example.com
744 verbatim: protocol=https
745 verbatim: host=example.com
746 verbatim: username=alice
750 test_expect_success
'honors username from URL over helper (components)' '
751 test_config credential.https://example.com.username bob &&
752 test_config credential.https://example.com.helper "verbatim \"\" bar" &&
764 verbatim: protocol=https
765 verbatim: host=example.com
766 verbatim: username=alice
770 test_expect_success
'last matching username wins' '
771 test_config credential.https://example.com/path.git.username bob &&
772 test_config credential.https://example.com.username alice &&
773 test_config credential.https://example.com.helper "verbatim \"\" bar" &&
775 url=https://example.com/path.git
783 verbatim: protocol=https
784 verbatim: host=example.com
785 verbatim: username=alice
789 test_expect_success
'http paths can be part of context' '
790 check fill "verbatim foo bar" <<-\EOF &&
801 verbatim: protocol=https
802 verbatim: host=example.com
804 test_config credential.https://example.com.useHttpPath true &&
805 check fill "verbatim foo bar" <<-\EOF
817 verbatim: protocol=https
818 verbatim: host=example.com
819 verbatim: path=foo.git
823 test_expect_success
'context uses urlmatch' '
824 test_config "credential.https://*.org.useHttpPath" true &&
825 check fill "verbatim foo bar" <<-\EOF
837 verbatim: protocol=https
838 verbatim: host=example.org
839 verbatim: path=foo.git
843 test_expect_success
'helpers can abort the process' '
845 -c credential.helper=quit \
846 -c credential.helper="verbatim foo bar" \
847 credential fill >stdout 2>stderr <<-\EOF &&
851 test_must_be_empty stdout &&
852 cat >expect <<-\EOF &&
855 quit: host=example.com
856 fatal: credential helper '\''quit'\'' told us to quit
858 test_cmp expect stderr
861 test_expect_success
'empty helper spec resets helper list' '
862 test_config credential.helper "verbatim file file" &&
863 check fill "" "verbatim cmdline cmdline" <<-\EOF
873 verbatim: protocol=http
874 verbatim: host=example.com
878 test_expect_success
'url parser rejects embedded newlines' '
879 test_must_fail git credential fill 2>stderr <<-\EOF &&
880 url=https://one.example.com?%0ahost=two.example.com/
882 cat >expect <<-\EOF &&
883 warning: url contains a newline in its path component: https://one.example.com?%0ahost=two.example.com/
884 fatal: credential url cannot be parsed: https://one.example.com?%0ahost=two.example.com/
886 test_cmp expect stderr
889 test_expect_success
'host-less URLs are parsed as empty host' '
890 check fill "verbatim foo bar" <<-\EOF
891 url=cert:///path/to/cert.pem
895 path=path/to/cert.pem
900 verbatim: protocol=cert
902 verbatim: path=path/to/cert.pem
906 test_expect_success
'credential system refuses to work with missing host' '
907 test_must_fail git credential fill 2>stderr <<-\EOF &&
910 cat >expect <<-\EOF &&
911 fatal: refusing to work with credential missing host field
913 test_cmp expect stderr
916 test_expect_success
'credential system refuses to work with missing protocol' '
917 test_must_fail git credential fill 2>stderr <<-\EOF &&
920 cat >expect <<-\EOF &&
921 fatal: refusing to work with credential missing protocol field
923 test_cmp expect stderr
926 # usage: check_host_and_path <url> <expected-host> <expected-path>
927 check_host_and_path
() {
928 # we always parse the path component, but we need this to make sure it
929 # is passed to the helper
930 test_config credential.useHTTPPath true
&&
931 check fill
"verbatim user pass" <<-EOF
941 verbatim: protocol=https
947 test_expect_success
'url parser handles bare query marker' '
948 check_host_and_path https://example.com?foo.git example.com ?foo.git
951 test_expect_success
'url parser handles bare fragment marker' '
952 check_host_and_path https://example.com#foo.git example.com "#foo.git"
955 test_expect_success
'url parser not confused by encoded markers' '
956 check_host_and_path https://example.com%23%3f%2f/foo.git \
957 "example.com#?/" foo.git
960 test_expect_success
'credential config with partial URLs' '
961 echo "echo password=yep" | write_script git-credential-yep &&
962 test_write_lines url=https://user@example.com/repo.git >stdin &&
967 https://example.com \
968 https://example.com/ \
969 https://user@example.com \
970 https://user@example.com/ \
971 https://example.com/repo.git \
972 https://user@example.com/repo.git \
975 git -c credential.$partial.helper=yep \
976 credential fill <stdin >stdout &&
986 git -c credential.$partial.helper=yep \
987 credential fill <stdin >stdout &&
992 git -c credential.$partial.helper=yep \
993 -c credential.with%0anewline.username=uh-oh \
994 credential fill <stdin 2>stderr &&
995 test_grep "skipping credential lookup for key" stderr