Browse Source

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
maint
Junio C Hamano 5 years ago
parent
commit
2cbb058669
  1. 1
      .mailmap
  2. 4
      Documentation/gitcredentials.txt
  3. 75
      credential.c
  4. 3
      credential.h
  5. 15
      strbuf.c
  6. 6
      strbuf.h
  7. 128
      t/t0300-credentials.sh
  8. 6
      t/t1300-config.sh
  9. 4
      urlmatch.c
  10. 9
      urlmatch.h

1
.mailmap

@ -31,6 +31,7 @@ Brandon Casey <drafnel@gmail.com> <casey@nrlssc.navy.mil> @@ -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>

4
Documentation/gitcredentials.txt

@ -131,7 +131,9 @@ context would not match: @@ -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

@ -6,6 +6,7 @@ @@ -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, @@ -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, @@ -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) @@ -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) @@ -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) @@ -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) @@ -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;
}

3
credential.h

@ -118,7 +118,8 @@ struct credential { @@ -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

@ -479,6 +479,21 @@ void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src) @@ -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

@ -378,6 +378,12 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, @@ -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).

128
t/t0300-credentials.sh

@ -240,6 +240,57 @@ test_expect_success 'do not match configured credential' ' @@ -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' ' @@ -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' ' @@ -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" \

6
t/t1300-config.sh

@ -1408,6 +1408,8 @@ test_expect_success 'urlmatch favors more specific URLs' ' @@ -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' ' @@ -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
'


4
urlmatch.c

@ -557,6 +557,8 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb) @@ -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) @@ -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.

9
urlmatch.h

@ -50,6 +50,15 @@ struct urlmatch_config { @@ -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);

Loading…
Cancel
Save