Code

credential: apply helper config
authorJeff King <peff@peff.net>
Sat, 10 Dec 2011 10:31:24 +0000 (05:31 -0500)
committerJunio C Hamano <gitster@pobox.com>
Mon, 12 Dec 2011 07:16:24 +0000 (23:16 -0800)
The functionality for credential storage helpers is already
there; we just need to give the users a way to turn it on.
This patch provides a "credential.helper" configuration
variable which allows the user to provide one or more helper
strings.

Rather than simply matching credential.helper, we will also
compare URLs in subsection headings to the current context.
This means you can apply configuration to a subset of
credentials. For example:

  [credential "https://example.com"]
helper = foo

would match a request for "https://example.com/foo.git", but
not one for "https://kernel.org/foo.git".

This is overkill for the "helper" variable, since users are
unlikely to want different helpers for different sites (and
since helpers run arbitrary code, they could do the matching
themselves anyway).

However, future patches will add new config variables where
this extra feature will be more useful.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
credential.c
credential.h
t/t0300-credentials.sh
t/t5550-http-fetch.sh

index c349b9aac3999ba4dbce16fd82b582cd60d61761..96be1c22dd4b6188aef9c89de51ffe6ac08a0331 100644 (file)
@@ -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");
 
index 8a6d162e7b477d329b23effae8f0007163f39f3d..e5042723a87543714846961667c3bb49b2c25827 100644 (file)
@@ -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 */
index 81a455f4c32e7d6541747b98d930fdffc9623f6f..42d0f5b7078ab405f70d71a9eeb7acb45b400ac6 100755 (executable)
@@ -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
index 398a2d29a431c4218c4b56ba6728d930dc3a71c2..c59908fe77fdf1af3357a801e8ad86e3b0355508 100755 (executable)
@@ -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 &&