@matheusbsbs / matheus-bagliano / commits / 46fd7b3900
Fork 0

credential: allow wildcard patterns when matching config

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>

brian m. carlson committed Feb 20, 2020 at 02:24 UTC 46fd7b390034eb5dde6a3f955e677c5260e4d10c
7 files changed +103 -21
Documentation/gitcredentials.txt
+3 -1
@@ -131,7 +131,9 @@ context would not match:
131 because the hostnames differ. Nor would it match `foo.example.com`; Git
132 compares hostnames exactly, without considering whether two hosts are part of
133 the same domain. Likewise, a config entry for `http://example.com` would not
134 -match: Git compares the protocols exactly.
134 +match: Git compares the protocols exactly. However, you may use wildcards in
135 +the domain name and other pattern matching techniques as with the `http.<url>.*`
136 +options.
137
138 If the "pattern" URL does include a path component, then this too must match
139 exactly: the context `https://example.com/bar/baz.git` will match a config
credential.c
+47 -19
@@ -6,6 +6,7 @@
6 #include "url.h"
7 #include "prompt.h"
8 #include "sigchain.h"
9 +#include "urlmatch.h"
10
11 void credential_init(struct credential *c)
12 {
@@ -40,7 +41,7 @@ static int credential_config_callback(const char *var, const char *value,
41 void *data)
42 {
43 struct credential *c = data;
43 - const char *key, *dot;
44 + const char *key;
45
46 if (!skip_prefix(var, "credential.", &key))
47 return 0;
@@ -48,23 +49,6 @@ static int credential_config_callback(const char *var, const char *value,
49 if (!value)
50 return config_error_nonbool(var);
51
51 - dot = strrchr(key, '.');
52 - if (dot) {
53 - struct credential want = CREDENTIAL_INIT;
54 - char *url = xmemdupz(key, dot - key);
55 - int matched;
56 -
57 - credential_from_url(&want, url);
58 - matched = credential_match(&want, c);
59 -
60 - credential_clear(&want);
61 - free(url);
62 -
63 - if (!matched)
64 - return 0;
65 - key = dot + 1;
66 - }
67 -
52 if (!strcmp(key, "helper")) {
53 if (*value)
54 string_list_append(&c->helpers, value);
@@ -89,11 +73,38 @@ static int proto_is_http(const char *s)
73 return !strcmp(s, "https") || !strcmp(s, "http");
74 }
75
76 +static void credential_describe(struct credential *c, struct strbuf *out);
77 +static void credential_format(struct credential *c, struct strbuf *out);
78 +
79 +static int select_all(const struct urlmatch_item *a,
80 + const struct urlmatch_item *b)
81 +{
82 + return 0;
83 +}
84 +
85 static void credential_apply_config(struct credential *c)
86 {
87 + char *normalized_url;
88 + struct urlmatch_config config = { STRING_LIST_INIT_DUP };
89 + struct strbuf url = STRBUF_INIT;
90 +
91 if (c->configured)
92 return;
96 - git_config(credential_config_callback, c);
93 +
94 + config.section = "credential";
95 + config.key = NULL;
96 + config.collect_fn = credential_config_callback;
97 + config.cascade_fn = NULL;
98 + config.select_fn = select_all;
99 + config.cb = c;
100 +
101 + credential_format(c, &url);
102 + normalized_url = url_normalize(url.buf, &config.url);
103 +
104 + git_config(urlmatch_config_entry, &config);
105 + free(normalized_url);
106 + strbuf_release(&url);
107 +
108 c->configured = 1;
109
110 if (!c->use_http_path && proto_is_http(c->protocol)) {
@@ -114,6 +125,23 @@ static void credential_describe(struct credential *c, struct strbuf *out)
125 strbuf_addf(out, "/%s", c->path);
126 }
127
128 +static void credential_format(struct credential *c, struct strbuf *out)
129 +{
130 + if (!c->protocol)
131 + return;
132 + strbuf_addf(out, "%s://", c->protocol);
133 + if (c->username && *c->username) {
134 + strbuf_add_percentencode(out, c->username);
135 + strbuf_addch(out, '@');
136 + }
137 + if (c->host)
138 + strbuf_addstr(out, c->host);
139 + if (c->path) {
140 + strbuf_addch(out, '/');
141 + strbuf_add_percentencode(out, c->path);
142 + }
143 +}
144 +
145 static char *credential_ask_one(const char *what, struct credential *c,
146 int flags)
147 {
strbuf.c
+15
@@ -479,6 +479,21 @@ void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src)
479 }
480 }
481
482 +#define URL_UNSAFE_CHARS " <>\"%{}|\\^`:/?#[]@!$&'()*+,;="
483 +
484 +void strbuf_add_percentencode(struct strbuf *dst, const char *src)
485 +{
486 + size_t i, len = strlen(src);
487 +
488 + for (i = 0; i < len; i++) {
489 + unsigned char ch = src[i];
490 + if (ch <= 0x1F || ch >= 0x7F || strchr(URL_UNSAFE_CHARS, ch))
491 + strbuf_addf(dst, "%%%02X", (unsigned char)ch);
492 + else
493 + strbuf_addch(dst, ch);
494 + }
495 +}
496 +
497 size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
498 {
499 size_t res;
strbuf.h
+6
@@ -366,6 +366,12 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb,
366 */
367 void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
368
369 +/**
370 + * Append the contents of a string to a strbuf, percent-encoding any characters
371 + * that are needed to be encoded for a URL.
372 + */
373 +void strbuf_add_percentencode(struct strbuf *dst, const char *src);
374 +
375 /**
376 * Append the given byte size as a human-readable string (i.e. 12.23 KiB,
377 * 3.50 MiB).
t/t0300-credentials.sh
+20
@@ -397,6 +397,26 @@ test_expect_success 'http paths can be part of context' '
397 EOF
398 '
399
400 +test_expect_success 'context uses urlmatch' '
401 + test_config "credential.https://*.org.useHttpPath" true &&
402 + check fill "verbatim foo bar" <<-\EOF
403 + protocol=https
404 + host=example.org
405 + path=foo.git
406 + --
407 + protocol=https
408 + host=example.org
409 + path=foo.git
410 + username=foo
411 + password=bar
412 + --
413 + verbatim: get
414 + verbatim: protocol=https
415 + verbatim: host=example.org
416 + verbatim: path=foo.git
417 + EOF
418 +'
419 +
420 test_expect_success 'helpers can abort the process' '
421 test_must_fail git \
422 -c credential.helper="!f() { echo quit=1; }; f" \
urlmatch.c
+3 -1
@@ -557,6 +557,8 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
557 const char *key, *dot;
558 struct strbuf synthkey = STRBUF_INIT;
559 int retval;
560 + int (*select_fn)(const struct urlmatch_item *a, const struct urlmatch_item *b) =
561 + collect->select_fn ? collect->select_fn : cmp_matches;
562
563 if (!skip_prefix(var, collect->section, &key) || *(key++) != '.') {
564 if (collect->cascade_fn)
@@ -587,7 +589,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
589 if (!item->util) {
590 item->util = xcalloc(1, sizeof(matched));
591 } else {
590 - if (cmp_matches(&matched, item->util) < 0)
592 + if (select_fn(&matched, item->util) < 0)
593 /*
594 * Our match is worse than the old one,
595 * we cannot use it.
urlmatch.h
+9
@@ -50,6 +50,15 @@ struct urlmatch_config {
50 void *cb;
51 int (*collect_fn)(const char *var, const char *value, void *cb);
52 int (*cascade_fn)(const char *var, const char *value, void *cb);
53 + /*
54 + * Compare the two matches, the one just discovered and the existing
55 + * best match and return a negative value if the found item is to be
56 + * rejected or a non-negative value if it is to be accepted. If this
57 + * field is set to NULL, use the default comparison technique, which
58 + * checks to ses if found is better (according to the urlmatch
59 + * specificity rules) than existing.
60 + */
61 + int (*select_fn)(const struct urlmatch_item *found, const struct urlmatch_item *existing);
62 };
63
64 int urlmatch_config_entry(const char *var, const char *value, void *cb);