Skip to content

Commit 026012c

Browse files
mjcheethamgitster
authored andcommitted
credential: add WWW-Authenticate header to cred requests
Add the value of the WWW-Authenticate response header to credential requests. Credential helpers that understand and support HTTP authentication and authorization can use this standard header (RFC 2616 Section 14.47 [1]) to generate valid credentials. WWW-Authenticate headers can contain information pertaining to the authority, authentication mechanism, or extra parameters/scopes that are required. The current I/O format for credential helpers only allows for unique names for properties/attributes, so in order to transmit multiple header values (with a specific order) we introduce a new convention whereby a C-style array syntax is used in the property name to denote multiple ordered values for the same property. In this case we send multiple `wwwauth[]` properties where the order that the repeated attributes appear in the conversation reflects the order that the WWW-Authenticate headers appeared in the HTTP response. Add a set of tests to exercise the HTTP authentication header parsing and the interop with credential helpers. Credential helpers will receive WWW-Authenticate information in credential requests. [1] https://datatracker.ietf.org/doc/html/rfc2616#section-14.47 Signed-off-by: Matthew John Cheetham <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent d3ffed2 commit 026012c

File tree

4 files changed

+298
-1
lines changed

4 files changed

+298
-1
lines changed

Documentation/git-credential.txt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,13 @@ separated by an `=` (equals) sign, followed by a newline.
113113
The key may contain any bytes except `=`, newline, or NUL. The value may
114114
contain any bytes except newline or NUL.
115115

116-
In both cases, all bytes are treated as-is (i.e., there is no quoting,
116+
Attributes with keys that end with C-style array brackets `[]` can have
117+
multiple values. Each instance of a multi-valued attribute forms an
118+
ordered list of values - the order of the repeated attributes defines
119+
the order of the values. An empty multi-valued attribute (`key[]=\n`)
120+
acts to clear any previous entries and reset the list.
121+
122+
In all cases, all bytes are treated as-is (i.e., there is no quoting,
117123
and one cannot transmit a value with newline or NUL in it). The list of
118124
attributes is terminated by a blank line or end-of-file.
119125

@@ -160,6 +166,17 @@ empty string.
160166
Components which are missing from the URL (e.g., there is no
161167
username in the example above) will be left unset.
162168

169+
`wwwauth[]`::
170+
171+
When an HTTP response is received by Git that includes one or more
172+
'WWW-Authenticate' authentication headers, these will be passed by Git
173+
to credential helpers.
174+
+
175+
Each 'WWW-Authenticate' header value is passed as a multi-valued
176+
attribute 'wwwauth[]', where the order of the attributes is the same as
177+
they appear in the HTTP response. This attribute is 'one-way' from Git
178+
to pass additional information to credential helpers.
179+
163180
Unrecognised attributes are silently discarded.
164181

165182
GIT

credential.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,24 @@ static void credential_write_item(FILE *fp, const char *key, const char *value,
263263
fprintf(fp, "%s=%s\n", key, value);
264264
}
265265

266+
static void credential_write_strvec(FILE *fp, const char *key,
267+
const struct strvec *vec)
268+
{
269+
char *full_key = xstrfmt("%s[]", key);
270+
for (size_t i = 0; i < vec->nr; i++) {
271+
credential_write_item(fp, full_key, vec->v[i], 0);
272+
}
273+
free(full_key);
274+
}
275+
266276
void credential_write(const struct credential *c, FILE *fp)
267277
{
268278
credential_write_item(fp, "protocol", c->protocol, 1);
269279
credential_write_item(fp, "host", c->host, 1);
270280
credential_write_item(fp, "path", c->path, 0);
271281
credential_write_item(fp, "username", c->username, 0);
272282
credential_write_item(fp, "password", c->password, 0);
283+
credential_write_strvec(fp, "wwwauth", &c->wwwauth_headers);
273284
}
274285

275286
static int run_credential_helper(struct credential *c,

t/lib-credential-helper.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
setup_credential_helper() {
2+
test_expect_success 'setup credential helper' '
3+
CREDENTIAL_HELPER="$TRASH_DIRECTORY/credential-helper.sh" &&
4+
export CREDENTIAL_HELPER &&
5+
echo $CREDENTIAL_HELPER &&
6+
7+
write_script "$CREDENTIAL_HELPER" <<-\EOF
8+
cmd=$1
9+
teefile=$cmd-query.cred
10+
catfile=$cmd-reply.cred
11+
sed -n -e "/^$/q" -e "p" >> $teefile
12+
if test "$cmd" = "get"; then
13+
cat $catfile
14+
fi
15+
EOF
16+
'
17+
}
18+
19+
set_credential_reply() {
20+
cat >"$TRASH_DIRECTORY/$1-reply.cred"
21+
}
22+
23+
expect_credential_query() {
24+
cat >"$TRASH_DIRECTORY/$1-expect.cred" &&
25+
test_cmp "$TRASH_DIRECTORY/$1-expect.cred" \
26+
"$TRASH_DIRECTORY/$1-query.cred"
27+
}

t/t5556-http-auth.sh

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ test_description='test http auth header and credential helper interop'
44

55
TEST_NO_CREATE_REPO=1
66
. ./test-lib.sh
7+
. "$TEST_DIRECTORY"/lib-credential-helper.sh
78

89
test_set_port GIT_TEST_HTTP_PROTOCOL_PORT
910

@@ -33,6 +34,8 @@ test_expect_success 'setup repos' '
3334
git -C "$REPO_DIR" branch -M main
3435
'
3536

37+
setup_credential_helper
38+
3639
run_http_server_worker() {
3740
(
3841
cd "$REPO_DIR"
@@ -101,6 +104,7 @@ per_test_cleanup () {
101104
stop_http_server &&
102105
rm -f OUT.* &&
103106
rm -f IN.* &&
107+
rm -f *.cred &&
104108
rm -f auth.config
105109
}
106110

@@ -218,4 +222,242 @@ test_expect_success 'http auth anonymous no challenge' '
218222
git ls-remote $ORIGIN_URL
219223
'
220224

225+
test_expect_success 'http auth www-auth headers to credential helper basic valid' '
226+
test_when_finished "per_test_cleanup" &&
227+
# base64("alice:secret-passwd")
228+
USERPASS64=YWxpY2U6c2VjcmV0LXBhc3N3ZA== &&
229+
export USERPASS64 &&
230+
231+
cat >auth.config <<-EOF &&
232+
[auth]
233+
challenge = basic:realm=\"example.com\"
234+
token = basic:$USERPASS64
235+
EOF
236+
237+
start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
238+
239+
set_credential_reply get <<-EOF &&
240+
protocol=http
241+
host=$HOST_PORT
242+
username=alice
243+
password=secret-passwd
244+
EOF
245+
246+
git -c "credential.helper=!\"$CREDENTIAL_HELPER\"" ls-remote $ORIGIN_URL &&
247+
248+
expect_credential_query get <<-EOF &&
249+
protocol=http
250+
host=$HOST_PORT
251+
wwwauth[]=basic realm="example.com"
252+
EOF
253+
254+
expect_credential_query store <<-EOF
255+
protocol=http
256+
host=$HOST_PORT
257+
username=alice
258+
password=secret-passwd
259+
EOF
260+
'
261+
262+
test_expect_success 'http auth www-auth headers to credential helper ignore case valid' '
263+
test_when_finished "per_test_cleanup" &&
264+
# base64("alice:secret-passwd")
265+
USERPASS64=YWxpY2U6c2VjcmV0LXBhc3N3ZA== &&
266+
export USERPASS64 &&
267+
268+
cat >auth.config <<-EOF &&
269+
[auth]
270+
challenge = basic:realm=\"example.com\"
271+
token = basic:$USERPASS64
272+
extraHeader = wWw-aUtHeNtIcAtE: bEaRer auThoRiTy=\"id.example.com\"
273+
EOF
274+
275+
start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
276+
277+
set_credential_reply get <<-EOF &&
278+
protocol=http
279+
host=$HOST_PORT
280+
username=alice
281+
password=secret-passwd
282+
EOF
283+
284+
git -c "credential.helper=!\"$CREDENTIAL_HELPER\"" ls-remote $ORIGIN_URL &&
285+
286+
expect_credential_query get <<-EOF &&
287+
protocol=http
288+
host=$HOST_PORT
289+
wwwauth[]=basic realm="example.com"
290+
wwwauth[]=bEaRer auThoRiTy="id.example.com"
291+
EOF
292+
293+
expect_credential_query store <<-EOF
294+
protocol=http
295+
host=$HOST_PORT
296+
username=alice
297+
password=secret-passwd
298+
EOF
299+
'
300+
301+
test_expect_success 'http auth www-auth headers to credential helper continuation hdr' '
302+
test_when_finished "per_test_cleanup" &&
303+
# base64("alice:secret-passwd")
304+
USERPASS64=YWxpY2U6c2VjcmV0LXBhc3N3ZA== &&
305+
export USERPASS64 &&
306+
307+
cat >auth.config <<-EOF &&
308+
[auth]
309+
challenge = "bearer:authority=\"id.example.com\"\\n q=1\\n \\t p=0"
310+
challenge = basic:realm=\"example.com\"
311+
token = basic:$USERPASS64
312+
EOF
313+
314+
start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
315+
316+
set_credential_reply get <<-EOF &&
317+
protocol=http
318+
host=$HOST_PORT
319+
username=alice
320+
password=secret-passwd
321+
EOF
322+
323+
git -c "credential.helper=!\"$CREDENTIAL_HELPER\"" ls-remote $ORIGIN_URL &&
324+
325+
expect_credential_query get <<-EOF &&
326+
protocol=http
327+
host=$HOST_PORT
328+
wwwauth[]=bearer authority="id.example.com" q=1 p=0
329+
wwwauth[]=basic realm="example.com"
330+
EOF
331+
332+
expect_credential_query store <<-EOF
333+
protocol=http
334+
host=$HOST_PORT
335+
username=alice
336+
password=secret-passwd
337+
EOF
338+
'
339+
340+
test_expect_success 'http auth www-auth headers to credential helper empty continuation hdrs' '
341+
test_when_finished "per_test_cleanup" &&
342+
# base64("alice:secret-passwd")
343+
USERPASS64=YWxpY2U6c2VjcmV0LXBhc3N3ZA== &&
344+
export USERPASS64 &&
345+
346+
cat >auth.config <<-EOF &&
347+
[auth]
348+
challenge = basic:realm=\"example.com\"
349+
token = basic:$USERPASS64
350+
extraheader = "WWW-Authenticate:"
351+
extraheader = " "
352+
extraheader = " bearer authority=\"id.example.com\""
353+
EOF
354+
355+
start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
356+
357+
set_credential_reply get <<-EOF &&
358+
protocol=http
359+
host=$HOST_PORT
360+
username=alice
361+
password=secret-passwd
362+
EOF
363+
364+
git -c "credential.helper=!\"$CREDENTIAL_HELPER\"" ls-remote $ORIGIN_URL &&
365+
366+
expect_credential_query get <<-EOF &&
367+
protocol=http
368+
host=$HOST_PORT
369+
wwwauth[]=basic realm="example.com"
370+
wwwauth[]=bearer authority="id.example.com"
371+
EOF
372+
373+
expect_credential_query store <<-EOF
374+
protocol=http
375+
host=$HOST_PORT
376+
username=alice
377+
password=secret-passwd
378+
EOF
379+
'
380+
381+
test_expect_success 'http auth www-auth headers to credential helper custom schemes' '
382+
test_when_finished "per_test_cleanup" &&
383+
# base64("alice:secret-passwd")
384+
USERPASS64=YWxpY2U6c2VjcmV0LXBhc3N3ZA== &&
385+
export USERPASS64 &&
386+
387+
cat >auth.config <<-EOF &&
388+
[auth]
389+
challenge = "foobar:alg=test widget=1"
390+
challenge = "bearer:authority=\"id.example.com\" q=1 p=0"
391+
challenge = basic:realm=\"example.com\"
392+
token = basic:$USERPASS64
393+
EOF
394+
395+
start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
396+
397+
set_credential_reply get <<-EOF &&
398+
protocol=http
399+
host=$HOST_PORT
400+
username=alice
401+
password=secret-passwd
402+
EOF
403+
404+
git -c "credential.helper=!\"$CREDENTIAL_HELPER\"" ls-remote $ORIGIN_URL &&
405+
406+
expect_credential_query get <<-EOF &&
407+
protocol=http
408+
host=$HOST_PORT
409+
wwwauth[]=foobar alg=test widget=1
410+
wwwauth[]=bearer authority="id.example.com" q=1 p=0
411+
wwwauth[]=basic realm="example.com"
412+
EOF
413+
414+
expect_credential_query store <<-EOF
415+
protocol=http
416+
host=$HOST_PORT
417+
username=alice
418+
password=secret-passwd
419+
EOF
420+
'
421+
422+
test_expect_success 'http auth www-auth headers to credential helper invalid' '
423+
test_when_finished "per_test_cleanup" &&
424+
# base64("alice:secret-passwd")
425+
USERPASS64=YWxpY2U6c2VjcmV0LXBhc3N3ZA== &&
426+
export USERPASS64 &&
427+
428+
cat >auth.config <<-EOF &&
429+
[auth]
430+
challenge = "bearer:authority=\"id.example.com\" q=1 p=0"
431+
challenge = basic:realm=\"example.com\"
432+
token = basic:$USERPASS64
433+
EOF
434+
435+
start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
436+
437+
set_credential_reply get <<-EOF &&
438+
protocol=http
439+
host=$HOST_PORT
440+
username=alice
441+
password=invalid-passwd
442+
EOF
443+
444+
test_must_fail git -c "credential.helper=!\"$CREDENTIAL_HELPER\"" ls-remote $ORIGIN_URL &&
445+
446+
expect_credential_query get <<-EOF &&
447+
protocol=http
448+
host=$HOST_PORT
449+
wwwauth[]=bearer authority="id.example.com" q=1 p=0
450+
wwwauth[]=basic realm="example.com"
451+
EOF
452+
453+
expect_credential_query erase <<-EOF
454+
protocol=http
455+
host=$HOST_PORT
456+
username=alice
457+
password=invalid-passwd
458+
wwwauth[]=bearer authority="id.example.com" q=1 p=0
459+
wwwauth[]=basic realm="example.com"
460+
EOF
461+
'
462+
221463
test_done

0 commit comments

Comments
 (0)