]> git.ipfire.org Git - thirdparty/git.git/commitdiff
credential: allow wildcard patterns when matching config
authorbrian m. carlson <bk2204@github.com>
Thu, 20 Feb 2020 02:24:13 +0000 (02:24 +0000)
committerJunio C Hamano <gitster@pobox.com>
Thu, 20 Feb 2020 21:05:43 +0000 (13:05 -0800)
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com.  We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.

Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now.  However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL.  While different, this
is probably the behavior people were wanting anyway.

Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it.  We now also no longer need to the
custom code to match URLs, so let's remove it.

Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior.  Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.

Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/gitcredentials.txt
credential.c
strbuf.c
strbuf.h
t/t0300-credentials.sh
urlmatch.c
urlmatch.h

index ea759fdee594f74c8bd06839367d6596ce153352..12cb032352c457fac30bec7acf403b3b65436cea 100644 (file)
@@ -131,7 +131,9 @@ context would not match:
 because the hostnames differ. Nor would it match `foo.example.com`; Git
 compares hostnames exactly, without considering whether two hosts are part of
 the same domain. Likewise, a config entry for `http://example.com` would not
-match: Git compares the protocols exactly.
+match: Git compares the protocols exactly.  However, you may use wildcards in
+the domain name and other pattern matching techniques as with the `http.<url>.*`
+options.
 
 If the "pattern" URL does include a path component, then this too must match
 exactly: the context `https://example.com/bar/baz.git` will match a config
index 5701e3279202ad4d7ebec0e6db5da04ff23b7623..77dfde44e3d136a29f68091878c9f9392af746c0 100644 (file)
@@ -6,6 +6,7 @@
 #include "url.h"
 #include "prompt.h"
 #include "sigchain.h"
+#include "urlmatch.h"
 
 void credential_init(struct credential *c)
 {
@@ -40,7 +41,7 @@ static int credential_config_callback(const char *var, const char *value,
                                      void *data)
 {
        struct credential *c = data;
-       const char *key, *dot;
+       const char *key;
 
        if (!skip_prefix(var, "credential.", &key))
                return 0;
@@ -48,23 +49,6 @@ static int credential_config_callback(const char *var, const char *value,
        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")) {
                if (*value)
                        string_list_append(&c->helpers, value);
@@ -89,11 +73,38 @@ static int proto_is_http(const char *s)
        return !strcmp(s, "https") || !strcmp(s, "http");
 }
 
+static void credential_describe(struct credential *c, struct strbuf *out);
+static void credential_format(struct credential *c, struct strbuf *out);
+
+static int select_all(const struct urlmatch_item *a,
+                     const struct urlmatch_item *b)
+{
+       return 0;
+}
+
 static void credential_apply_config(struct credential *c)
 {
+       char *normalized_url;
+       struct urlmatch_config config = { STRING_LIST_INIT_DUP };
+       struct strbuf url = STRBUF_INIT;
+
        if (c->configured)
                return;
-       git_config(credential_config_callback, c);
+
+       config.section = "credential";
+       config.key = NULL;
+       config.collect_fn = credential_config_callback;
+       config.cascade_fn = NULL;
+       config.select_fn = select_all;
+       config.cb = c;
+
+       credential_format(c, &url);
+       normalized_url = url_normalize(url.buf, &config.url);
+
+       git_config(urlmatch_config_entry, &config);
+       free(normalized_url);
+       strbuf_release(&url);
+
        c->configured = 1;
 
        if (!c->use_http_path && proto_is_http(c->protocol)) {
@@ -114,6 +125,23 @@ static void credential_describe(struct credential *c, struct strbuf *out)
                strbuf_addf(out, "/%s", c->path);
 }
 
+static void credential_format(struct credential *c, struct strbuf *out)
+{
+       if (!c->protocol)
+               return;
+       strbuf_addf(out, "%s://", c->protocol);
+       if (c->username && *c->username) {
+               strbuf_add_percentencode(out, c->username);
+               strbuf_addch(out, '@');
+       }
+       if (c->host)
+               strbuf_addstr(out, c->host);
+       if (c->path) {
+               strbuf_addch(out, '/');
+               strbuf_add_percentencode(out, c->path);
+       }
+}
+
 static char *credential_ask_one(const char *what, struct credential *c,
                                int flags)
 {
index f19da55b0783dc2d1bf4cab0e0ce76be5711cc55..bb0065ccaf5b764323cd727dbabe3061bbe328e4 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -479,6 +479,21 @@ void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src)
        }
 }
 
+#define URL_UNSAFE_CHARS " <>\"%{}|\\^`:/?#[]@!$&'()*+,;="
+
+void strbuf_add_percentencode(struct strbuf *dst, const char *src)
+{
+       size_t i, len = strlen(src);
+
+       for (i = 0; i < len; i++) {
+               unsigned char ch = src[i];
+               if (ch <= 0x1F || ch >= 0x7F || strchr(URL_UNSAFE_CHARS, ch))
+                       strbuf_addf(dst, "%%%02X", (unsigned char)ch);
+               else
+                       strbuf_addch(dst, ch);
+       }
+}
+
 size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
 {
        size_t res;
index bfa66569a4bffd063578477e68010a5ccc14f5a3..2504ae4ecb6e4575f2ef3e80338556caefaaaa4c 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -366,6 +366,12 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb,
  */
 void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
 
+/**
+ * Append the contents of a string to a strbuf, percent-encoding any characters
+ * that are needed to be encoded for a URL.
+ */
+void strbuf_add_percentencode(struct strbuf *dst, const char *src);
+
 /**
  * Append the given byte size as a human-readable string (i.e. 12.23 KiB,
  * 3.50 MiB).
index 8f8759905663369908c3ee0721831c266806cfeb..39f097ea9e5094530b981c49193df8df2cbda406 100755 (executable)
@@ -397,6 +397,26 @@ test_expect_success 'http paths can be part of context' '
        EOF
 '
 
+test_expect_success 'context uses urlmatch' '
+       test_config "credential.https://*.org.useHttpPath" true &&
+       check fill "verbatim foo bar" <<-\EOF
+       protocol=https
+       host=example.org
+       path=foo.git
+       --
+       protocol=https
+       host=example.org
+       path=foo.git
+       username=foo
+       password=bar
+       --
+       verbatim: get
+       verbatim: protocol=https
+       verbatim: host=example.org
+       verbatim: path=foo.git
+       EOF
+'
+
 test_expect_success 'helpers can abort the process' '
        test_must_fail git \
                -c credential.helper="!f() { echo quit=1; }; f" \
index 3e42bd750485d67bb6151ab5f15ac09a0d887502..29272a5c4f4d4a1785b66cb3c3a213c911d0883b 100644 (file)
@@ -557,6 +557,8 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
        const char *key, *dot;
        struct strbuf synthkey = STRBUF_INIT;
        int retval;
+       int (*select_fn)(const struct urlmatch_item *a, const struct urlmatch_item *b) =
+               collect->select_fn ? collect->select_fn : cmp_matches;
 
        if (!skip_prefix(var, collect->section, &key) || *(key++) != '.') {
                if (collect->cascade_fn)
@@ -587,7 +589,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
        if (!item->util) {
                item->util = xcalloc(1, sizeof(matched));
        } else {
-               if (cmp_matches(&matched, item->util) < 0)
+               if (select_fn(&matched, item->util) < 0)
                         /*
                          * Our match is worse than the old one,
                          * we cannot use it.
index eed5f292354cfef466f15f29ef944450e10d4d1a..2407520731f9f0c5250d7bdf5833c56320c10160 100644 (file)
@@ -50,6 +50,15 @@ struct urlmatch_config {
        void *cb;
        int (*collect_fn)(const char *var, const char *value, void *cb);
        int (*cascade_fn)(const char *var, const char *value, void *cb);
+       /*
+        * Compare the two matches, the one just discovered and the existing
+        * best match and return a negative value if the found item is to be
+        * rejected or a non-negative value if it is to be accepted.  If this
+        * field is set to NULL, use the default comparison technique, which
+        * checks to ses if found is better (according to the urlmatch
+        * specificity rules) than existing.
+        */
+       int (*select_fn)(const struct urlmatch_item *found, const struct urlmatch_item *existing);
 };
 
 int urlmatch_config_entry(const char *var, const char *value, void *cb);