Skip to content

Commit 5f2117b

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 6b8dda9 commit 5f2117b

File tree

3 files changed

+263
-1
lines changed

3 files changed

+263
-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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,9 @@ void credential_write(const struct credential *c, FILE *fp)
270270
credential_write_item(fp, "path", c->path, 0);
271271
credential_write_item(fp, "username", c->username, 0);
272272
credential_write_item(fp, "password", c->password, 0);
273+
for (size_t i = 0; i < c->wwwauth_headers.nr; i++)
274+
credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i],
275+
0);
273276
}
274277

275278
static int run_credential_helper(struct credential *c,

t/t5563-simple-http-auth.sh

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,248 @@ test_expect_success 'access using basic auth' '
7070
expect_credential_query get <<-EOF &&
7171
protocol=http
7272
host=$HTTPD_DEST
73+
wwwauth[]=Basic realm="example.com"
74+
EOF
75+
76+
expect_credential_query store <<-EOF
77+
protocol=http
78+
host=$HTTPD_DEST
79+
username=alice
80+
password=secret-passwd
81+
EOF
82+
'
83+
84+
test_expect_success 'access using basic auth invalid credentials' '
85+
test_when_finished "per_test_cleanup" &&
86+
87+
set_credential_reply get <<-EOF &&
88+
username=baduser
89+
password=wrong-passwd
90+
EOF
91+
92+
# Basic base64(alice:secret-passwd)
93+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
94+
Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
95+
EOF
96+
97+
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
98+
WWW-Authenticate: Basic realm="example.com"
99+
EOF
100+
101+
test_config_global credential.helper test-helper &&
102+
test_must_fail git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
103+
104+
expect_credential_query get <<-EOF &&
105+
protocol=http
106+
host=$HTTPD_DEST
107+
wwwauth[]=Basic realm="example.com"
108+
EOF
109+
110+
expect_credential_query erase <<-EOF
111+
protocol=http
112+
host=$HTTPD_DEST
113+
username=baduser
114+
password=wrong-passwd
115+
wwwauth[]=Basic realm="example.com"
116+
EOF
117+
'
118+
119+
test_expect_success 'access using basic auth with extra challenges' '
120+
test_when_finished "per_test_cleanup" &&
121+
122+
set_credential_reply get <<-EOF &&
123+
username=alice
124+
password=secret-passwd
125+
EOF
126+
127+
# Basic base64(alice:secret-passwd)
128+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
129+
Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
130+
EOF
131+
132+
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
133+
WWW-Authenticate: FooBar param1="value1" param2="value2"
134+
WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0
135+
WWW-Authenticate: Basic realm="example.com"
136+
EOF
137+
138+
test_config_global credential.helper test-helper &&
139+
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
140+
141+
expect_credential_query get <<-EOF &&
142+
protocol=http
143+
host=$HTTPD_DEST
144+
wwwauth[]=FooBar param1="value1" param2="value2"
145+
wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
146+
wwwauth[]=Basic realm="example.com"
147+
EOF
148+
149+
expect_credential_query store <<-EOF
150+
protocol=http
151+
host=$HTTPD_DEST
152+
username=alice
153+
password=secret-passwd
154+
EOF
155+
'
156+
157+
test_expect_success 'access using basic auth mixed-case wwwauth header name' '
158+
test_when_finished "per_test_cleanup" &&
159+
160+
set_credential_reply get <<-EOF &&
161+
username=alice
162+
password=secret-passwd
163+
EOF
164+
165+
# Basic base64(alice:secret-passwd)
166+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
167+
Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
168+
EOF
169+
170+
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
171+
www-authenticate: foobar param1="value1" param2="value2"
172+
WWW-AUTHENTICATE: BEARER authorize_uri="id.example.com" p=1 q=0
173+
WwW-aUtHeNtIcAtE: baSiC realm="example.com"
174+
EOF
175+
176+
test_config_global credential.helper test-helper &&
177+
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
178+
179+
expect_credential_query get <<-EOF &&
180+
protocol=http
181+
host=$HTTPD_DEST
182+
wwwauth[]=foobar param1="value1" param2="value2"
183+
wwwauth[]=BEARER authorize_uri="id.example.com" p=1 q=0
184+
wwwauth[]=baSiC realm="example.com"
185+
EOF
186+
187+
expect_credential_query store <<-EOF
188+
protocol=http
189+
host=$HTTPD_DEST
190+
username=alice
191+
password=secret-passwd
192+
EOF
193+
'
194+
195+
test_expect_success 'access using basic auth with wwwauth header continuations' '
196+
test_when_finished "per_test_cleanup" &&
197+
198+
set_credential_reply get <<-EOF &&
199+
username=alice
200+
password=secret-passwd
201+
EOF
202+
203+
# Basic base64(alice:secret-passwd)
204+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
205+
Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
206+
EOF
207+
208+
# Note that leading and trailing whitespace is important to correctly
209+
# simulate a continuation/folded header.
210+
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
211+
WWW-Authenticate: FooBar param1="value1"
212+
param2="value2"
213+
WWW-Authenticate: Bearer authorize_uri="id.example.com"
214+
p=1
215+
q=0
216+
WWW-Authenticate: Basic realm="example.com"
217+
EOF
218+
219+
test_config_global credential.helper test-helper &&
220+
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
221+
222+
expect_credential_query get <<-EOF &&
223+
protocol=http
224+
host=$HTTPD_DEST
225+
wwwauth[]=FooBar param1="value1" param2="value2"
226+
wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
227+
wwwauth[]=Basic realm="example.com"
228+
EOF
229+
230+
expect_credential_query store <<-EOF
231+
protocol=http
232+
host=$HTTPD_DEST
233+
username=alice
234+
password=secret-passwd
235+
EOF
236+
'
237+
238+
test_expect_success 'access using basic auth with wwwauth header empty continuations' '
239+
test_when_finished "per_test_cleanup" &&
240+
241+
set_credential_reply get <<-EOF &&
242+
username=alice
243+
password=secret-passwd
244+
EOF
245+
246+
# Basic base64(alice:secret-passwd)
247+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
248+
Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
249+
EOF
250+
251+
CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
252+
253+
# Note that leading and trailing whitespace is important to correctly
254+
# simulate a continuation/folded header.
255+
printf "">$CHALLENGE &&
256+
printf "WWW-Authenticate: FooBar param1=\"value1\"\r\n" >$CHALLENGE &&
257+
printf " \r\n" >>$CHALLENGE &&
258+
printf " param2=\"value2\"\r\n" >>$CHALLENGE &&
259+
printf "WWW-Authenticate: Bearer authorize_uri=\"id.example.com\"\r\n" >>$CHALLENGE &&
260+
printf " p=1\r\n" >>$CHALLENGE &&
261+
printf " \r\n" >>$CHALLENGE &&
262+
printf " q=0\r\n" >>$CHALLENGE &&
263+
printf "WWW-Authenticate: Basic realm=\"example.com\"\r\n" >>$CHALLENGE &&
264+
265+
test_config_global credential.helper test-helper &&
266+
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
267+
268+
expect_credential_query get <<-EOF &&
269+
protocol=http
270+
host=$HTTPD_DEST
271+
wwwauth[]=FooBar param1="value1" param2="value2"
272+
wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0
273+
wwwauth[]=Basic realm="example.com"
274+
EOF
275+
276+
expect_credential_query store <<-EOF
277+
protocol=http
278+
host=$HTTPD_DEST
279+
username=alice
280+
password=secret-passwd
281+
EOF
282+
'
283+
284+
test_expect_success 'access using basic auth with wwwauth header mixed line-endings' '
285+
test_when_finished "per_test_cleanup" &&
286+
287+
set_credential_reply get <<-EOF &&
288+
username=alice
289+
password=secret-passwd
290+
EOF
291+
292+
# Basic base64(alice:secret-passwd)
293+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
294+
Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
295+
EOF
296+
297+
CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" &&
298+
299+
# Note that leading and trailing whitespace is important to correctly
300+
# simulate a continuation/folded header.
301+
printf "">$CHALLENGE &&
302+
printf "WWW-Authenticate: FooBar param1=\"value1\"\r\n" >$CHALLENGE &&
303+
printf " \r\n" >>$CHALLENGE &&
304+
printf "\tparam2=\"value2\"\r\n" >>$CHALLENGE &&
305+
printf "WWW-Authenticate: Basic realm=\"example.com\"" >>$CHALLENGE &&
306+
307+
test_config_global credential.helper test-helper &&
308+
git ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
309+
310+
expect_credential_query get <<-EOF &&
311+
protocol=http
312+
host=$HTTPD_DEST
313+
wwwauth[]=FooBar param1="value1" param2="value2"
314+
wwwauth[]=Basic realm="example.com"
73315
EOF
74316
75317
expect_credential_query store <<-EOF

0 commit comments

Comments
 (0)