Skip to content

Commit 5ee8fcd

Browse files
committed
Merge branch 'mh/credential-erase-improvements'
* mh/credential-erase-improvements: credential: erase all matching credentials credential: avoid erasing distinct password
2 parents 01202f5 + 6c26da8 commit 5ee8fcd

File tree

7 files changed

+128
-20
lines changed

7 files changed

+128
-20
lines changed

Documentation/git-credential.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ for later use.
3939

4040
If the action is `reject`, git-credential will send the description to
4141
any configured credential helpers, which may erase any stored
42-
credential matching the description.
42+
credentials matching the description.
4343

4444
If the action is `approve` or `reject`, no output should be emitted.
4545

Documentation/gitcredentials.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ appended to its command line, which is one of:
260260

261261
`erase`::
262262

263-
Remove a matching credential, if any, from the helper's storage.
263+
Remove matching credentials, if any, from the helper's storage.
264264

265265
The details of the credential will be provided on the helper's stdin
266266
stream. The exact format is the same as the input/output format of the

builtin/credential-cache--daemon.c

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,22 @@ static struct credential_cache_entry *lookup_credential(const struct credential
3838
int i;
3939
for (i = 0; i < entries_nr; i++) {
4040
struct credential *e = &entries[i].item;
41-
if (credential_match(c, e))
41+
if (credential_match(c, e, 0))
4242
return &entries[i];
4343
}
4444
return NULL;
4545
}
4646

47-
static void remove_credential(const struct credential *c)
47+
static void remove_credential(const struct credential *c, int match_password)
4848
{
4949
struct credential_cache_entry *e;
5050

51-
e = lookup_credential(c);
52-
if (e)
53-
e->expiration = 0;
51+
int i;
52+
for (i = 0; i < entries_nr; i++) {
53+
e = &entries[i];
54+
if (credential_match(c, &e->item, match_password))
55+
e->expiration = 0;
56+
}
5457
}
5558

5659
static timestamp_t check_expirations(void)
@@ -151,14 +154,14 @@ static void serve_one_client(FILE *in, FILE *out)
151154
exit(0);
152155
}
153156
else if (!strcmp(action.buf, "erase"))
154-
remove_credential(&c);
157+
remove_credential(&c, 1);
155158
else if (!strcmp(action.buf, "store")) {
156159
if (timeout < 0)
157160
warning("cache client didn't specify a timeout");
158161
else if (!c.username || !c.password)
159162
warning("cache client gave us a partial credential");
160163
else {
161-
remove_credential(&c);
164+
remove_credential(&c, 0);
162165
cache_credential(&c, timeout);
163166
}
164167
}

builtin/credential-store.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ static struct lock_file credential_lock;
1313
static int parse_credential_file(const char *fn,
1414
struct credential *c,
1515
void (*match_cb)(struct credential *),
16-
void (*other_cb)(struct strbuf *))
16+
void (*other_cb)(struct strbuf *),
17+
int match_password)
1718
{
1819
FILE *fh;
1920
struct strbuf line = STRBUF_INIT;
@@ -30,7 +31,7 @@ static int parse_credential_file(const char *fn,
3031
while (strbuf_getline_lf(&line, fh) != EOF) {
3132
if (!credential_from_url_gently(&entry, line.buf, 1) &&
3233
entry.username && entry.password &&
33-
credential_match(c, &entry)) {
34+
credential_match(c, &entry, match_password)) {
3435
found_credential = 1;
3536
if (match_cb) {
3637
match_cb(&entry);
@@ -60,7 +61,7 @@ static void print_line(struct strbuf *buf)
6061
}
6162

6263
static void rewrite_credential_file(const char *fn, struct credential *c,
63-
struct strbuf *extra)
64+
struct strbuf *extra, int match_password)
6465
{
6566
int timeout_ms = 1000;
6667

@@ -69,7 +70,7 @@ static void rewrite_credential_file(const char *fn, struct credential *c,
6970
die_errno(_("unable to get credential storage lock in %d ms"), timeout_ms);
7071
if (extra)
7172
print_line(extra);
72-
parse_credential_file(fn, c, NULL, print_line);
73+
parse_credential_file(fn, c, NULL, print_line, match_password);
7374
if (commit_lock_file(&credential_lock) < 0)
7475
die_errno("unable to write credential store");
7576
}
@@ -91,7 +92,7 @@ static void store_credential_file(const char *fn, struct credential *c)
9192
is_rfc3986_reserved_or_unreserved);
9293
}
9394

94-
rewrite_credential_file(fn, c, &buf);
95+
rewrite_credential_file(fn, c, &buf, 0);
9596
strbuf_release(&buf);
9697
}
9798

@@ -138,15 +139,15 @@ static void remove_credential(const struct string_list *fns, struct credential *
138139
return;
139140
for_each_string_list_item(fn, fns)
140141
if (!access(fn->string, F_OK))
141-
rewrite_credential_file(fn->string, c, NULL);
142+
rewrite_credential_file(fn->string, c, NULL, 1);
142143
}
143144

144145
static void lookup_credential(const struct string_list *fns, struct credential *c)
145146
{
146147
struct string_list_item *fn;
147148

148149
for_each_string_list_item(fn, fns)
149-
if (parse_credential_file(fn->string, c, print_entry, NULL))
150+
if (parse_credential_file(fn->string, c, print_entry, NULL, 0))
150151
return; /* Found credential */
151152
}
152153

credential.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ void credential_clear(struct credential *c)
3333
}
3434

3535
int credential_match(const struct credential *want,
36-
const struct credential *have)
36+
const struct credential *have, int match_password)
3737
{
3838
#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x)))
3939
return CHECK(protocol) &&
4040
CHECK(host) &&
4141
CHECK(path) &&
42-
CHECK(username);
42+
CHECK(username) &&
43+
(!match_password || CHECK(password));
4344
#undef CHECK
4445
}
4546

@@ -102,7 +103,7 @@ static int match_partial_url(const char *url, void *cb)
102103
warning(_("skipping credential lookup for key: credential.%s"),
103104
url);
104105
else
105-
matches = credential_match(&want, c);
106+
matches = credential_match(&want, c, 0);
106107
credential_clear(&want);
107108

108109
return matches;

credential.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,6 @@ void credential_from_url(struct credential *, const char *url);
211211
int credential_from_url_gently(struct credential *, const char *url, int quiet);
212212

213213
int credential_match(const struct credential *want,
214-
const struct credential *have);
214+
const struct credential *have, int match_password);
215215

216216
#endif /* CREDENTIAL_H */

t/lib-credential.sh

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ helper_test_clean() {
4444
reject $1 https example.com user1
4545
reject $1 https example.com user2
4646
reject $1 https example.com user4
47+
reject $1 https example.com user-distinct-pass
48+
reject $1 https example.com user-overwrite
49+
reject $1 https example.com user-erase1
50+
reject $1 https example.com user-erase2
4751
reject $1 http path.tld user
4852
reject $1 https timeout.tld user
4953
reject $1 https sso.tld
@@ -167,6 +171,49 @@ helper_test() {
167171
EOF
168172
'
169173

174+
test_expect_success "helper ($HELPER) overwrites on store" '
175+
check approve $HELPER <<-\EOF &&
176+
protocol=https
177+
host=example.com
178+
username=user-overwrite
179+
password=pass1
180+
EOF
181+
check approve $HELPER <<-\EOF &&
182+
protocol=https
183+
host=example.com
184+
username=user-overwrite
185+
password=pass2
186+
EOF
187+
check fill $HELPER <<-\EOF &&
188+
protocol=https
189+
host=example.com
190+
username=user-overwrite
191+
--
192+
protocol=https
193+
host=example.com
194+
username=user-overwrite
195+
password=pass2
196+
EOF
197+
check reject $HELPER <<-\EOF &&
198+
protocol=https
199+
host=example.com
200+
username=user-overwrite
201+
password=pass2
202+
EOF
203+
check fill $HELPER <<-\EOF
204+
protocol=https
205+
host=example.com
206+
username=user-overwrite
207+
--
208+
protocol=https
209+
host=example.com
210+
username=user-overwrite
211+
password=askpass-password
212+
--
213+
askpass: Password for '\''https://[email protected]'\'':
214+
EOF
215+
'
216+
170217
test_expect_success "helper ($HELPER) can forget host" '
171218
check reject $HELPER <<-\EOF &&
172219
protocol=https
@@ -221,6 +268,31 @@ helper_test() {
221268
EOF
222269
'
223270

271+
test_expect_success "helper ($HELPER) does not erase a password distinct from input" '
272+
check approve $HELPER <<-\EOF &&
273+
protocol=https
274+
host=example.com
275+
username=user-distinct-pass
276+
password=pass1
277+
EOF
278+
check reject $HELPER <<-\EOF &&
279+
protocol=https
280+
host=example.com
281+
username=user-distinct-pass
282+
password=pass2
283+
EOF
284+
check fill $HELPER <<-\EOF
285+
protocol=https
286+
host=example.com
287+
username=user-distinct-pass
288+
--
289+
protocol=https
290+
host=example.com
291+
username=user-distinct-pass
292+
password=pass1
293+
EOF
294+
'
295+
224296
test_expect_success "helper ($HELPER) can forget user" '
225297
check reject $HELPER <<-\EOF &&
226298
protocol=https
@@ -272,6 +344,37 @@ helper_test() {
272344
EOF
273345
'
274346

347+
test_expect_success "helper ($HELPER) erases all matching credentials" '
348+
check approve $HELPER <<-\EOF &&
349+
protocol=https
350+
host=example.com
351+
username=user-erase1
352+
password=pass1
353+
EOF
354+
check approve $HELPER <<-\EOF &&
355+
protocol=https
356+
host=example.com
357+
username=user-erase2
358+
password=pass1
359+
EOF
360+
check reject $HELPER <<-\EOF &&
361+
protocol=https
362+
host=example.com
363+
EOF
364+
check fill $HELPER <<-\EOF
365+
protocol=https
366+
host=example.com
367+
--
368+
protocol=https
369+
host=example.com
370+
username=askpass-username
371+
password=askpass-password
372+
--
373+
askpass: Username for '\''https://example.com'\'':
374+
askpass: Password for '\''https://[email protected]'\'':
375+
EOF
376+
'
377+
275378
: ${GIT_TEST_LONG_CRED_BUFFER:=1024}
276379
# 23 bytes accounts for "wwwauth[]=basic realm=" plus NUL
277380
LONG_VALUE_LEN=$((GIT_TEST_LONG_CRED_BUFFER - 23))

0 commit comments

Comments
 (0)