diff --git a/credential.c b/credential.c index c349b9aac3..96be1c22dd 100644 --- a/credential.c +++ b/credential.c @@ -22,6 +22,61 @@ void credential_clear(struct credential *c) credential_init(c); } +int credential_match(const struct credential *want, + const struct credential *have) +{ +#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x))) + return CHECK(protocol) && + CHECK(host) && + CHECK(path) && + CHECK(username); +#undef CHECK +} + +static int credential_config_callback(const char *var, const char *value, + void *data) +{ + struct credential *c = data; + const char *key, *dot; + + key = skip_prefix(var, "credential."); + if (!key) + return 0; + + if (!value) + return config_error_nonbool(var); + + dot = strrchr(key, '.'); + if (dot) { + struct credential want = CREDENTIAL_INIT; + char *url = xmemdupz(key, dot - key); + int matched; + + credential_from_url(&want, url); + matched = credential_match(&want, c); + + credential_clear(&want); + free(url); + + if (!matched) + return 0; + key = dot + 1; + } + + if (!strcmp(key, "helper")) + string_list_append(&c->helpers, value); + + return 0; +} + +static void credential_apply_config(struct credential *c) +{ + if (c->configured) + return; + git_config(credential_config_callback, c); + c->configured = 1; +} + static void credential_describe(struct credential *c, struct strbuf *out) { if (!c->protocol) @@ -195,6 +250,8 @@ void credential_fill(struct credential *c) if (c->username && c->password) return; + credential_apply_config(c); + for (i = 0; i < c->helpers.nr; i++) { credential_do(c, c->helpers.items[i].string, "get"); if (c->username && c->password) @@ -215,6 +272,8 @@ void credential_approve(struct credential *c) if (!c->username || !c->password) return; + credential_apply_config(c); + for (i = 0; i < c->helpers.nr; i++) credential_do(c, c->helpers.items[i].string, "store"); c->approved = 1; @@ -224,6 +283,8 @@ void credential_reject(struct credential *c) { int i; + credential_apply_config(c); + for (i = 0; i < c->helpers.nr; i++) credential_do(c, c->helpers.items[i].string, "erase"); diff --git a/credential.h b/credential.h index 8a6d162e7b..e5042723a8 100644 --- a/credential.h +++ b/credential.h @@ -5,7 +5,8 @@ struct credential { struct string_list helpers; - unsigned approved:1; + unsigned approved:1, + configured:1; char *username; char *password; @@ -25,5 +26,7 @@ void credential_reject(struct credential *); int credential_read(struct credential *, FILE *); void credential_from_url(struct credential *, const char *url); +int credential_match(const struct credential *have, + const struct credential *want); #endif /* CREDENTIAL_H */ diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index 81a455f4c3..42d0f5b707 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -192,4 +192,46 @@ test_expect_success 'internal getpass does not ask for known username' ' EOF ' +HELPER="!f() { + cat >/dev/null + echo username=foo + echo password=bar + }; f" +test_expect_success 'respect configured credentials' ' + test_config credential.helper "$HELPER" && + check fill <<-\EOF + -- + username=foo + password=bar + -- + EOF +' + +test_expect_success 'match configured credential' ' + test_config credential.https://example.com.helper "$HELPER" && + check fill <<-\EOF + protocol=https + host=example.com + path=repo.git + -- + username=foo + password=bar + -- + EOF +' + +test_expect_success 'do not match configured credential' ' + test_config credential.https://foo.helper "$HELPER" && + check fill <<-\EOF + protocol=https + host=bar + -- + username=askpass-username + password=askpass-password + -- + askpass: Username for '\''https://bar'\'': + askpass: Password for '\''https://askpass-username@bar'\'': + EOF +' + test_done diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index 398a2d29a4..c59908fe77 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -101,6 +101,18 @@ test_expect_success 'http auth can request both user and pass' ' expect_askpass both user@host ' +test_expect_success 'http auth respects credential helper config' ' + test_config_global credential.helper "!f() { + cat >/dev/null + echo username=user@host + echo password=user@host + }; f" && + >askpass-query && + echo wrong >askpass-response && + git clone "$HTTPD_URL/auth/repo.git" clone-auth-helper && + expect_askpass none +' + test_expect_success 'fetch changes via http' ' echo content >>file && git commit -a -m two &&