apply: don't leak fd on fdopen() error
[git.git] / t / t0300-credentials.sh
blob400f6bdbca13c2a93cb8521e21e08fbd4e1eb3bd
1 #!/bin/sh
3 test_description='basic credential helper tests'
4 . ./test-lib.sh
5 . "$TEST_DIRECTORY"/lib-credential.sh
7 test_expect_success 'setup helper scripts' '
8 cat >dump <<-\EOF &&
9 whoami=$(echo $0 | sed s/.*git-credential-//)
10 echo >&2 "$whoami: $*"
11 OIFS=$IFS
12 IFS==
13 while read key value; do
14 echo >&2 "$whoami: $key=$value"
15 eval "$key=$value"
16 done
17 IFS=$OIFS
18 EOF
20 write_script git-credential-useless <<-\EOF &&
21 . ./dump
22 exit 0
23 EOF
25 write_script git-credential-quit <<-\EOF &&
26 . ./dump
27 echo quit=1
28 EOF
30 write_script git-credential-verbatim <<-\EOF &&
31 user=$1; shift
32 pass=$1; shift
33 . ./dump
34 test -z "$user" || echo username=$user
35 test -z "$pass" || echo password=$pass
36 EOF
38 write_script git-credential-verbatim-with-expiry <<-\EOF &&
39 user=$1; shift
40 pass=$1; shift
41 pexpiry=$1; shift
42 . ./dump
43 test -z "$user" || echo username=$user
44 test -z "$pass" || echo password=$pass
45 test -z "$pexpiry" || echo password_expiry_utc=$pexpiry
46 EOF
48 PATH="$PWD:$PATH"
51 test_expect_success 'credential_fill invokes helper' '
52 check fill "verbatim foo bar" <<-\EOF
53 protocol=http
54 host=example.com
56 protocol=http
57 host=example.com
58 username=foo
59 password=bar
61 verbatim: get
62 verbatim: protocol=http
63 verbatim: host=example.com
64 EOF
67 test_expect_success 'credential_fill invokes multiple helpers' '
68 check fill useless "verbatim foo bar" <<-\EOF
69 protocol=http
70 host=example.com
72 protocol=http
73 host=example.com
74 username=foo
75 password=bar
77 useless: get
78 useless: protocol=http
79 useless: host=example.com
80 verbatim: get
81 verbatim: protocol=http
82 verbatim: host=example.com
83 EOF
86 test_expect_success 'credential_fill stops when we get a full response' '
87 check fill "verbatim one two" "verbatim three four" <<-\EOF
88 protocol=http
89 host=example.com
91 protocol=http
92 host=example.com
93 username=one
94 password=two
96 verbatim: get
97 verbatim: protocol=http
98 verbatim: host=example.com
99 EOF
102 test_expect_success 'credential_fill continues through partial response' '
103 check fill "verbatim one \"\"" "verbatim two three" <<-\EOF
104 protocol=http
105 host=example.com
107 protocol=http
108 host=example.com
109 username=two
110 password=three
112 verbatim: get
113 verbatim: protocol=http
114 verbatim: host=example.com
115 verbatim: get
116 verbatim: protocol=http
117 verbatim: host=example.com
118 verbatim: username=one
122 test_expect_success 'credential_fill populates password_expiry_utc' '
123 check fill "verbatim-with-expiry one two 9999999999" <<-\EOF
124 protocol=http
125 host=example.com
127 protocol=http
128 host=example.com
129 username=one
130 password=two
131 password_expiry_utc=9999999999
133 verbatim-with-expiry: get
134 verbatim-with-expiry: protocol=http
135 verbatim-with-expiry: host=example.com
139 test_expect_success 'credential_fill ignores expired password' '
140 check fill "verbatim-with-expiry one two 5" "verbatim three four" <<-\EOF
141 protocol=http
142 host=example.com
144 protocol=http
145 host=example.com
146 username=three
147 password=four
149 verbatim-with-expiry: get
150 verbatim-with-expiry: protocol=http
151 verbatim-with-expiry: host=example.com
152 verbatim: get
153 verbatim: protocol=http
154 verbatim: host=example.com
155 verbatim: username=one
159 test_expect_success 'credential_fill passes along metadata' '
160 check fill "verbatim one two" <<-\EOF
161 protocol=ftp
162 host=example.com
163 path=foo.git
165 protocol=ftp
166 host=example.com
167 path=foo.git
168 username=one
169 password=two
171 verbatim: get
172 verbatim: protocol=ftp
173 verbatim: host=example.com
174 verbatim: path=foo.git
178 test_expect_success 'credential_approve calls all helpers' '
179 check approve useless "verbatim one two" <<-\EOF
180 protocol=http
181 host=example.com
182 username=foo
183 password=bar
186 useless: store
187 useless: protocol=http
188 useless: host=example.com
189 useless: username=foo
190 useless: password=bar
191 verbatim: store
192 verbatim: protocol=http
193 verbatim: host=example.com
194 verbatim: username=foo
195 verbatim: password=bar
199 test_expect_success 'credential_approve stores password expiry' '
200 check approve useless <<-\EOF
201 protocol=http
202 host=example.com
203 username=foo
204 password=bar
205 password_expiry_utc=9999999999
208 useless: store
209 useless: protocol=http
210 useless: host=example.com
211 useless: username=foo
212 useless: password=bar
213 useless: password_expiry_utc=9999999999
217 test_expect_success 'credential_approve stores oauth refresh token' '
218 check approve useless <<-\EOF
219 protocol=http
220 host=example.com
221 username=foo
222 password=bar
223 oauth_refresh_token=xyzzy
226 useless: store
227 useless: protocol=http
228 useless: host=example.com
229 useless: username=foo
230 useless: password=bar
231 useless: oauth_refresh_token=xyzzy
235 test_expect_success 'do not bother storing password-less credential' '
236 check approve useless <<-\EOF
237 protocol=http
238 host=example.com
239 username=foo
245 test_expect_success 'credential_approve does not store expired password' '
246 check approve useless <<-\EOF
247 protocol=http
248 host=example.com
249 username=foo
250 password=bar
251 password_expiry_utc=5
257 test_expect_success 'credential_reject calls all helpers' '
258 check reject useless "verbatim one two" <<-\EOF
259 protocol=http
260 host=example.com
261 username=foo
262 password=bar
265 useless: erase
266 useless: protocol=http
267 useless: host=example.com
268 useless: username=foo
269 useless: password=bar
270 verbatim: erase
271 verbatim: protocol=http
272 verbatim: host=example.com
273 verbatim: username=foo
274 verbatim: password=bar
278 test_expect_success 'credential_reject erases credential regardless of expiry' '
279 check reject useless <<-\EOF
280 protocol=http
281 host=example.com
282 username=foo
283 password=bar
284 password_expiry_utc=5
287 useless: erase
288 useless: protocol=http
289 useless: host=example.com
290 useless: username=foo
291 useless: password=bar
292 useless: password_expiry_utc=5
296 test_expect_success 'usernames can be preserved' '
297 check fill "verbatim \"\" three" <<-\EOF
298 protocol=http
299 host=example.com
300 username=one
302 protocol=http
303 host=example.com
304 username=one
305 password=three
307 verbatim: get
308 verbatim: protocol=http
309 verbatim: host=example.com
310 verbatim: username=one
314 test_expect_success 'usernames can be overridden' '
315 check fill "verbatim two three" <<-\EOF
316 protocol=http
317 host=example.com
318 username=one
320 protocol=http
321 host=example.com
322 username=two
323 password=three
325 verbatim: get
326 verbatim: protocol=http
327 verbatim: host=example.com
328 verbatim: username=one
332 test_expect_success 'do not bother completing already-full credential' '
333 check fill "verbatim three four" <<-\EOF
334 protocol=http
335 host=example.com
336 username=one
337 password=two
339 protocol=http
340 host=example.com
341 username=one
342 password=two
347 # We can't test the basic terminal password prompt here because
348 # getpass() tries too hard to find the real terminal. But if our
349 # askpass helper is run, we know the internal getpass is working.
350 test_expect_success 'empty helper list falls back to internal getpass' '
351 check fill <<-\EOF
352 protocol=http
353 host=example.com
355 protocol=http
356 host=example.com
357 username=askpass-username
358 password=askpass-password
360 askpass: Username for '\''http://example.com'\'':
361 askpass: Password for '\''http://askpass-username@example.com'\'':
365 test_expect_success 'internal getpass does not ask for known username' '
366 check fill <<-\EOF
367 protocol=http
368 host=example.com
369 username=foo
371 protocol=http
372 host=example.com
373 username=foo
374 password=askpass-password
376 askpass: Password for '\''http://foo@example.com'\'':
380 test_expect_success 'git-credential respects core.askPass' '
381 write_script alternate-askpass <<-\EOF &&
382 echo >&2 "alternate askpass invoked"
383 echo alternate-value
385 test_config core.askpass "$PWD/alternate-askpass" &&
387 # unset GIT_ASKPASS set by lib-credential.sh which would
388 # override our config, but do so in a subshell so that we do
389 # not interfere with other tests
390 sane_unset GIT_ASKPASS &&
391 check fill <<-\EOF
392 protocol=http
393 host=example.com
395 protocol=http
396 host=example.com
397 username=alternate-value
398 password=alternate-value
400 alternate askpass invoked
401 alternate askpass invoked
406 HELPER="!f() {
407 cat >/dev/null
408 echo username=foo
409 echo password=bar
410 }; f"
411 test_expect_success 'respect configured credentials' '
412 test_config credential.helper "$HELPER" &&
413 check fill <<-\EOF
414 protocol=http
415 host=example.com
417 protocol=http
418 host=example.com
419 username=foo
420 password=bar
425 test_expect_success 'match configured credential' '
426 test_config credential.https://example.com.helper "$HELPER" &&
427 check fill <<-\EOF
428 protocol=https
429 host=example.com
430 path=repo.git
432 protocol=https
433 host=example.com
434 username=foo
435 password=bar
440 test_expect_success 'do not match configured credential' '
441 test_config credential.https://foo.helper "$HELPER" &&
442 check fill <<-\EOF
443 protocol=https
444 host=bar
446 protocol=https
447 host=bar
448 username=askpass-username
449 password=askpass-password
451 askpass: Username for '\''https://bar'\'':
452 askpass: Password for '\''https://askpass-username@bar'\'':
456 test_expect_success 'match multiple configured helpers' '
457 test_config credential.helper "verbatim \"\" \"\"" &&
458 test_config credential.https://example.com.helper "$HELPER" &&
459 check fill <<-\EOF
460 protocol=https
461 host=example.com
462 path=repo.git
464 protocol=https
465 host=example.com
466 username=foo
467 password=bar
469 verbatim: get
470 verbatim: protocol=https
471 verbatim: host=example.com
475 test_expect_success 'match multiple configured helpers with URLs' '
476 test_config credential.https://example.com/repo.git.helper "verbatim \"\" \"\"" &&
477 test_config credential.https://example.com.helper "$HELPER" &&
478 check fill <<-\EOF
479 protocol=https
480 host=example.com
481 path=repo.git
483 protocol=https
484 host=example.com
485 username=foo
486 password=bar
488 verbatim: get
489 verbatim: protocol=https
490 verbatim: host=example.com
494 test_expect_success 'match percent-encoded values' '
495 test_config credential.https://example.com/%2566.git.helper "$HELPER" &&
496 check fill <<-\EOF
497 url=https://example.com/%2566.git
499 protocol=https
500 host=example.com
501 username=foo
502 password=bar
507 test_expect_success 'match percent-encoded UTF-8 values in path' '
508 test_config credential.https://example.com.useHttpPath true &&
509 test_config credential.https://example.com/perú.git.helper "$HELPER" &&
510 check fill <<-\EOF
511 url=https://example.com/per%C3%BA.git
513 protocol=https
514 host=example.com
515 path=perú.git
516 username=foo
517 password=bar
522 test_expect_success 'match percent-encoded values in username' '
523 test_config credential.https://user%2fname@example.com/foo/bar.git.helper "$HELPER" &&
524 check fill <<-\EOF
525 url=https://user%2fname@example.com/foo/bar.git
527 protocol=https
528 host=example.com
529 username=foo
530 password=bar
535 test_expect_success 'fetch with multiple path components' '
536 test_unconfig credential.helper &&
537 test_config credential.https://example.com/foo/repo.git.helper "verbatim foo bar" &&
538 check fill <<-\EOF
539 url=https://example.com/foo/repo.git
541 protocol=https
542 host=example.com
543 username=foo
544 password=bar
546 verbatim: get
547 verbatim: protocol=https
548 verbatim: host=example.com
552 test_expect_success 'pull username from config' '
553 test_config credential.https://example.com.username foo &&
554 check fill <<-\EOF
555 protocol=https
556 host=example.com
558 protocol=https
559 host=example.com
560 username=foo
561 password=askpass-password
563 askpass: Password for '\''https://foo@example.com'\'':
567 test_expect_success 'honors username from URL over helper (URL)' '
568 test_config credential.https://example.com.username bob &&
569 test_config credential.https://example.com.helper "verbatim \"\" bar" &&
570 check fill <<-\EOF
571 url=https://alice@example.com
573 protocol=https
574 host=example.com
575 username=alice
576 password=bar
578 verbatim: get
579 verbatim: protocol=https
580 verbatim: host=example.com
581 verbatim: username=alice
585 test_expect_success 'honors username from URL over helper (components)' '
586 test_config credential.https://example.com.username bob &&
587 test_config credential.https://example.com.helper "verbatim \"\" bar" &&
588 check fill <<-\EOF
589 protocol=https
590 host=example.com
591 username=alice
593 protocol=https
594 host=example.com
595 username=alice
596 password=bar
598 verbatim: get
599 verbatim: protocol=https
600 verbatim: host=example.com
601 verbatim: username=alice
605 test_expect_success 'last matching username wins' '
606 test_config credential.https://example.com/path.git.username bob &&
607 test_config credential.https://example.com.username alice &&
608 test_config credential.https://example.com.helper "verbatim \"\" bar" &&
609 check fill <<-\EOF
610 url=https://example.com/path.git
612 protocol=https
613 host=example.com
614 username=alice
615 password=bar
617 verbatim: get
618 verbatim: protocol=https
619 verbatim: host=example.com
620 verbatim: username=alice
624 test_expect_success 'http paths can be part of context' '
625 check fill "verbatim foo bar" <<-\EOF &&
626 protocol=https
627 host=example.com
628 path=foo.git
630 protocol=https
631 host=example.com
632 username=foo
633 password=bar
635 verbatim: get
636 verbatim: protocol=https
637 verbatim: host=example.com
639 test_config credential.https://example.com.useHttpPath true &&
640 check fill "verbatim foo bar" <<-\EOF
641 protocol=https
642 host=example.com
643 path=foo.git
645 protocol=https
646 host=example.com
647 path=foo.git
648 username=foo
649 password=bar
651 verbatim: get
652 verbatim: protocol=https
653 verbatim: host=example.com
654 verbatim: path=foo.git
658 test_expect_success 'context uses urlmatch' '
659 test_config "credential.https://*.org.useHttpPath" true &&
660 check fill "verbatim foo bar" <<-\EOF
661 protocol=https
662 host=example.org
663 path=foo.git
665 protocol=https
666 host=example.org
667 path=foo.git
668 username=foo
669 password=bar
671 verbatim: get
672 verbatim: protocol=https
673 verbatim: host=example.org
674 verbatim: path=foo.git
678 test_expect_success 'helpers can abort the process' '
679 test_must_fail git \
680 -c credential.helper=quit \
681 -c credential.helper="verbatim foo bar" \
682 credential fill >stdout 2>stderr <<-\EOF &&
683 protocol=http
684 host=example.com
686 test_must_be_empty stdout &&
687 cat >expect <<-\EOF &&
688 quit: get
689 quit: protocol=http
690 quit: host=example.com
691 fatal: credential helper '\''quit'\'' told us to quit
693 test_cmp expect stderr
696 test_expect_success 'empty helper spec resets helper list' '
697 test_config credential.helper "verbatim file file" &&
698 check fill "" "verbatim cmdline cmdline" <<-\EOF
699 protocol=http
700 host=example.com
702 protocol=http
703 host=example.com
704 username=cmdline
705 password=cmdline
707 verbatim: get
708 verbatim: protocol=http
709 verbatim: host=example.com
713 test_expect_success 'url parser rejects embedded newlines' '
714 test_must_fail git credential fill 2>stderr <<-\EOF &&
715 url=https://one.example.com?%0ahost=two.example.com/
717 cat >expect <<-\EOF &&
718 warning: url contains a newline in its path component: https://one.example.com?%0ahost=two.example.com/
719 fatal: credential url cannot be parsed: https://one.example.com?%0ahost=two.example.com/
721 test_cmp expect stderr
724 test_expect_success 'host-less URLs are parsed as empty host' '
725 check fill "verbatim foo bar" <<-\EOF
726 url=cert:///path/to/cert.pem
728 protocol=cert
729 host=
730 path=path/to/cert.pem
731 username=foo
732 password=bar
734 verbatim: get
735 verbatim: protocol=cert
736 verbatim: host=
737 verbatim: path=path/to/cert.pem
741 test_expect_success 'credential system refuses to work with missing host' '
742 test_must_fail git credential fill 2>stderr <<-\EOF &&
743 protocol=http
745 cat >expect <<-\EOF &&
746 fatal: refusing to work with credential missing host field
748 test_cmp expect stderr
751 test_expect_success 'credential system refuses to work with missing protocol' '
752 test_must_fail git credential fill 2>stderr <<-\EOF &&
753 host=example.com
755 cat >expect <<-\EOF &&
756 fatal: refusing to work with credential missing protocol field
758 test_cmp expect stderr
761 # usage: check_host_and_path <url> <expected-host> <expected-path>
762 check_host_and_path () {
763 # we always parse the path component, but we need this to make sure it
764 # is passed to the helper
765 test_config credential.useHTTPPath true &&
766 check fill "verbatim user pass" <<-EOF
767 url=$1
769 protocol=https
770 host=$2
771 path=$3
772 username=user
773 password=pass
775 verbatim: get
776 verbatim: protocol=https
777 verbatim: host=$2
778 verbatim: path=$3
782 test_expect_success 'url parser handles bare query marker' '
783 check_host_and_path https://example.com?foo.git example.com ?foo.git
786 test_expect_success 'url parser handles bare fragment marker' '
787 check_host_and_path https://example.com#foo.git example.com "#foo.git"
790 test_expect_success 'url parser not confused by encoded markers' '
791 check_host_and_path https://example.com%23%3f%2f/foo.git \
792 "example.com#?/" foo.git
795 test_expect_success 'credential config with partial URLs' '
796 echo "echo password=yep" | write_script git-credential-yep &&
797 test_write_lines url=https://user@example.com/repo.git >stdin &&
798 for partial in \
799 example.com \
800 user@example.com \
801 https:// \
802 https://example.com \
803 https://example.com/ \
804 https://user@example.com \
805 https://user@example.com/ \
806 https://example.com/repo.git \
807 https://user@example.com/repo.git \
808 /repo.git
810 git -c credential.$partial.helper=yep \
811 credential fill <stdin >stdout &&
812 grep yep stdout ||
813 return 1
814 done &&
816 for partial in \
817 dont.use.this \
818 http:// \
819 /repo
821 git -c credential.$partial.helper=yep \
822 credential fill <stdin >stdout &&
823 ! grep yep stdout ||
824 return 1
825 done &&
827 git -c credential.$partial.helper=yep \
828 -c credential.with%0anewline.username=uh-oh \
829 credential fill <stdin 2>stderr &&
830 test_grep "skipping credential lookup for key" stderr
833 test_done