Merge branch 'bc/wildcard-credential'
A configuration element used for credential subsystem can now use wildcard pattern to specify for which set of URLs the entry applies. * bc/wildcard-credential: credential: allow wildcard patterns when matching config credential: use the last matching username in the config t0300: add tests for some additional cases t1300: add test for urlmatch with multiple wildcards mailmap: add an additional email address for brian m. carlson
This commit is contained in:
1
.mailmap
1
.mailmap
@ -31,6 +31,7 @@ Brandon Casey <drafnel@gmail.com> <casey@nrlssc.navy.mil>
|
||||
Brandon Williams <bwilliams.eng@gmail.com> <bmwill@google.com>
|
||||
brian m. carlson <sandals@crustytoothpaste.net>
|
||||
brian m. carlson <sandals@crustytoothpaste.net> <sandals@crustytoothpaste.ath.cx>
|
||||
brian m. carlson <sandals@crustytoothpaste.net> <bk2204@github.com>
|
||||
Bryan Larsen <bryan@larsen.st> <bryan.larsen@gmail.com>
|
||||
Bryan Larsen <bryan@larsen.st> <bryanlarsen@yahoo.com>
|
||||
Cheng Renquan <crquan@gmail.com>
|
||||
|
@ -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
|
||||
|
75
credential.c
75
credential.c
@ -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,31 +49,16 @@ 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);
|
||||
else
|
||||
string_list_clear(&c->helpers, 0);
|
||||
} else if (!strcmp(key, "username")) {
|
||||
if (!c->username)
|
||||
if (!c->username_from_proto) {
|
||||
free(c->username);
|
||||
c->username = xstrdup(value);
|
||||
}
|
||||
}
|
||||
else if (!strcmp(key, "usehttppath"))
|
||||
c->use_http_path = git_config_bool(var, value);
|
||||
@ -87,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)) {
|
||||
@ -112,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)
|
||||
{
|
||||
@ -163,6 +193,7 @@ int credential_read(struct credential *c, FILE *fp)
|
||||
if (!strcmp(key, "username")) {
|
||||
free(c->username);
|
||||
c->username = xstrdup(value);
|
||||
c->username_from_proto = 1;
|
||||
} else if (!strcmp(key, "password")) {
|
||||
free(c->password);
|
||||
c->password = xstrdup(value);
|
||||
@ -349,10 +380,14 @@ void credential_from_url(struct credential *c, const char *url)
|
||||
else if (!colon || at <= colon) {
|
||||
/* Case (2) */
|
||||
c->username = url_decode_mem(cp, at - cp);
|
||||
if (c->username && *c->username)
|
||||
c->username_from_proto = 1;
|
||||
host = at + 1;
|
||||
} else {
|
||||
/* Case (3) */
|
||||
c->username = url_decode_mem(cp, colon - cp);
|
||||
if (c->username && *c->username)
|
||||
c->username_from_proto = 1;
|
||||
c->password = url_decode_mem(colon + 1, at - (colon + 1));
|
||||
host = at + 1;
|
||||
}
|
||||
|
@ -118,7 +118,8 @@ struct credential {
|
||||
unsigned approved:1,
|
||||
configured:1,
|
||||
quit:1,
|
||||
use_http_path:1;
|
||||
use_http_path:1,
|
||||
username_from_proto:1;
|
||||
|
||||
char *username;
|
||||
char *password;
|
||||
|
15
strbuf.c
15
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;
|
||||
|
6
strbuf.h
6
strbuf.h
@ -378,6 +378,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).
|
||||
|
@ -240,6 +240,57 @@ test_expect_success 'do not match configured credential' '
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'match multiple configured helpers' '
|
||||
test_config credential.helper "verbatim \"\" \"\"" &&
|
||||
test_config credential.https://example.com.helper "$HELPER" &&
|
||||
check fill <<-\EOF
|
||||
protocol=https
|
||||
host=example.com
|
||||
path=repo.git
|
||||
--
|
||||
protocol=https
|
||||
host=example.com
|
||||
username=foo
|
||||
password=bar
|
||||
--
|
||||
verbatim: get
|
||||
verbatim: protocol=https
|
||||
verbatim: host=example.com
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'match multiple configured helpers with URLs' '
|
||||
test_config credential.https://example.com/repo.git.helper "verbatim \"\" \"\"" &&
|
||||
test_config credential.https://example.com.helper "$HELPER" &&
|
||||
check fill <<-\EOF
|
||||
protocol=https
|
||||
host=example.com
|
||||
path=repo.git
|
||||
--
|
||||
protocol=https
|
||||
host=example.com
|
||||
username=foo
|
||||
password=bar
|
||||
--
|
||||
verbatim: get
|
||||
verbatim: protocol=https
|
||||
verbatim: host=example.com
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'match percent-encoded values' '
|
||||
test_config credential.https://example.com/%2566.git.helper "$HELPER" &&
|
||||
check fill <<-\EOF
|
||||
url=https://example.com/%2566.git
|
||||
--
|
||||
protocol=https
|
||||
host=example.com
|
||||
username=foo
|
||||
password=bar
|
||||
--
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'pull username from config' '
|
||||
test_config credential.https://example.com.username foo &&
|
||||
check fill <<-\EOF
|
||||
@ -255,6 +306,63 @@ test_expect_success 'pull username from config' '
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'honors username from URL over helper (URL)' '
|
||||
test_config credential.https://example.com.username bob &&
|
||||
test_config credential.https://example.com.helper "verbatim \"\" bar" &&
|
||||
check fill <<-\EOF
|
||||
url=https://alice@example.com
|
||||
--
|
||||
protocol=https
|
||||
host=example.com
|
||||
username=alice
|
||||
password=bar
|
||||
--
|
||||
verbatim: get
|
||||
verbatim: protocol=https
|
||||
verbatim: host=example.com
|
||||
verbatim: username=alice
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'honors username from URL over helper (components)' '
|
||||
test_config credential.https://example.com.username bob &&
|
||||
test_config credential.https://example.com.helper "verbatim \"\" bar" &&
|
||||
check fill <<-\EOF
|
||||
protocol=https
|
||||
host=example.com
|
||||
username=alice
|
||||
--
|
||||
protocol=https
|
||||
host=example.com
|
||||
username=alice
|
||||
password=bar
|
||||
--
|
||||
verbatim: get
|
||||
verbatim: protocol=https
|
||||
verbatim: host=example.com
|
||||
verbatim: username=alice
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'last matching username wins' '
|
||||
test_config credential.https://example.com/path.git.username bob &&
|
||||
test_config credential.https://example.com.username alice &&
|
||||
test_config credential.https://example.com.helper "verbatim \"\" bar" &&
|
||||
check fill <<-\EOF
|
||||
url=https://example.com/path.git
|
||||
--
|
||||
protocol=https
|
||||
host=example.com
|
||||
username=alice
|
||||
password=bar
|
||||
--
|
||||
verbatim: get
|
||||
verbatim: protocol=https
|
||||
verbatim: host=example.com
|
||||
verbatim: username=alice
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'http paths can be part of context' '
|
||||
check fill "verbatim foo bar" <<-\EOF &&
|
||||
protocol=https
|
||||
@ -289,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" \
|
||||
|
@ -1408,6 +1408,8 @@ test_expect_success 'urlmatch favors more specific URLs' '
|
||||
cookieFile = /tmp/wildcard.txt
|
||||
[http "https://*.example.com/wildcardwithsubdomain"]
|
||||
cookieFile = /tmp/wildcardwithsubdomain.txt
|
||||
[http "https://*.example.*"]
|
||||
cookieFile = /tmp/multiwildcard.txt
|
||||
[http "https://trailing.example.com"]
|
||||
cookieFile = /tmp/trailing.txt
|
||||
[http "https://user@*.example.com/"]
|
||||
@ -1454,6 +1456,10 @@ test_expect_success 'urlmatch favors more specific URLs' '
|
||||
|
||||
echo http.cookiefile /tmp/sub.txt >expect &&
|
||||
git config --get-urlmatch HTTP https://user@sub.example.com >actual &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
echo http.cookiefile /tmp/multiwildcard.txt >expect &&
|
||||
git config --get-urlmatch HTTP https://wildcard.example.org >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user