@@ -113,7 +113,13 @@ separated by an `=` (equals) sign, followed by a newline.
The key may contain any bytes except `=`, newline, or NUL. The value may
contain any bytes except newline or NUL.
-In both cases, all bytes are treated as-is (i.e., there is no quoting,
+Attributes with keys that end with C-style array brackets `[]` can have
+multiple values. Each instance of a multi-valued attribute forms an
+ordered list of values - the order of the repeated attributes defines
+the order of the values. An empty multi-valued attribute (`key[]=\n`)
+acts to clear any previous entries and reset the list.
+
+In all cases, all bytes are treated as-is (i.e., there is no quoting,
and one cannot transmit a value with newline or NUL in it). The list of
attributes is terminated by a blank line or end-of-file.
@@ -160,6 +166,17 @@ empty string.
Components which are missing from the URL (e.g., there is no
username in the example above) will be left unset.
+`wwwauth[]`::
+
+ When an HTTP response is received by Git that includes one or more
+ 'WWW-Authenticate' authentication headers, these will be passed by Git
+ to credential helpers.
++
+Each 'WWW-Authenticate' header value is passed as a multi-valued
+attribute 'wwwauth[]', where the order of the attributes is the same as
+they appear in the HTTP response. This attribute is 'one-way' from Git
+to pass additional information to credential helpers.
+
Unrecognised attributes are silently discarded.
GIT
@@ -263,6 +263,17 @@ static void credential_write_item(FILE *fp, const char *key, const char *value,
fprintf(fp, "%s=%s\n", key, value);
}
+static void credential_write_strvec(FILE *fp, const char *key,
+ const struct strvec *vec)
+{
+ int i = 0;
+ const char *full_key = xstrfmt("%s[]", key);
+ for (; i < vec->nr; i++) {
+ credential_write_item(fp, full_key, vec->v[i], 0);
+ }
+ free((void*)full_key);
+}
+
void credential_write(const struct credential *c, FILE *fp)
{
credential_write_item(fp, "protocol", c->protocol, 1);
@@ -270,6 +281,7 @@ void credential_write(const struct credential *c, FILE *fp)
credential_write_item(fp, "path", c->path, 0);
credential_write_item(fp, "username", c->username, 0);
credential_write_item(fp, "password", c->password, 0);
+ credential_write_strvec(fp, "wwwauth", &c->wwwauth_headers);
}
static int run_credential_helper(struct credential *c,
new file mode 100755
@@ -0,0 +1,14 @@
+cmd=$1
+teefile=$cmd-actual.cred
+catfile=$cmd-response.cred
+rm -f $teefile
+while read line;
+do
+ if test -z "$line"; then
+ break;
+ fi
+ echo "$line" >> $teefile
+done
+if test "$cmd" = "get"; then
+ cat $catfile
+fi
@@ -27,6 +27,8 @@ PID_FILE="$TRASH_DIRECTORY"/pid-file.pid
SERVER_LOG="$TRASH_DIRECTORY"/OUT.server.log
PATH="$GIT_BUILD_DIR/t/helper/:$PATH" && export PATH
+CREDENTIAL_HELPER="$GIT_BUILD_DIR/t/helper/test-credential-helper-replay.sh" \
+ && export CREDENTIAL_HELPER
test_expect_success 'setup repos' '
test_create_repo "$REPO_DIR" &&
@@ -92,15 +94,279 @@ start_http_server () {
per_test_cleanup () {
stop_http_server &&
- rm -f OUT.*
+ rm -f OUT.* &&
+ rm -f *.cred &&
+ rm -f auth.config
}
test_expect_success 'http auth anonymous no challenge' '
test_when_finished "per_test_cleanup" &&
- start_http_server &&
+
+ cat >auth.config <<-EOF &&
+ [auth]
+ allowAnonymous = true
+ EOF
+
+ start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
# Attempt to read from a protected repository
git ls-remote $ORIGIN_URL
'
+test_expect_success 'http auth www-auth headers to credential helper basic valid' '
+ test_when_finished "per_test_cleanup" &&
+ # base64("alice:secret-passwd")
+ USERPASS64=YWxpY2U6c2VjcmV0LXBhc3N3ZA== &&
+ export USERPASS64 &&
+
+ cat >auth.config <<-EOF &&
+ [auth]
+ challenge = basic:realm=\"example.com\"
+ token = basic:$USERPASS64
+ EOF
+
+ start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
+
+ cat >get-expected.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ wwwauth[]=basic realm="example.com"
+ EOF
+
+ cat >store-expected.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ username=alice
+ password=secret-passwd
+ EOF
+
+ cat >get-response.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ username=alice
+ password=secret-passwd
+ EOF
+
+ git -c credential.helper="$CREDENTIAL_HELPER" ls-remote $ORIGIN_URL &&
+
+ test_cmp get-expected.cred get-actual.cred &&
+ test_cmp store-expected.cred store-actual.cred
+'
+
+test_expect_success 'http auth www-auth headers to credential helper ignore case valid' '
+ test_when_finished "per_test_cleanup" &&
+ # base64("alice:secret-passwd")
+ USERPASS64=YWxpY2U6c2VjcmV0LXBhc3N3ZA== &&
+ export USERPASS64 &&
+
+ cat >auth.config <<-EOF &&
+ [auth]
+ challenge = basic:realm=\"example.com\"
+ token = basic:$USERPASS64
+ extraHeader = wWw-aUtHeNtIcAtE: bEaRer auThoRiTy=\"id.example.com\"
+ EOF
+
+ start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
+
+ cat >get-expected.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ wwwauth[]=basic realm="example.com"
+ wwwauth[]=bEaRer auThoRiTy="id.example.com"
+ EOF
+
+ cat >store-expected.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ username=alice
+ password=secret-passwd
+ EOF
+
+ cat >get-response.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ username=alice
+ password=secret-passwd
+ EOF
+
+ git -c credential.helper="$CREDENTIAL_HELPER" ls-remote $ORIGIN_URL &&
+
+ test_cmp get-expected.cred get-actual.cred &&
+ test_cmp store-expected.cred store-actual.cred
+'
+
+test_expect_success 'http auth www-auth headers to credential helper continuation hdr' '
+ test_when_finished "per_test_cleanup" &&
+ # base64("alice:secret-passwd")
+ USERPASS64=YWxpY2U6c2VjcmV0LXBhc3N3ZA== &&
+ export USERPASS64 &&
+
+ cat >auth.config <<-EOF &&
+ [auth]
+ challenge = "bearer:authority=\"id.example.com\"\\n q=1\\n \\t p=0"
+ challenge = basic:realm=\"example.com\"
+ token = basic:$USERPASS64
+ EOF
+
+ start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
+
+ cat >get-expected.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ wwwauth[]=bearer authority="id.example.com" q=1 p=0
+ wwwauth[]=basic realm="example.com"
+ EOF
+
+ cat >store-expected.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ username=alice
+ password=secret-passwd
+ EOF
+
+ cat >get-response.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ username=alice
+ password=secret-passwd
+ EOF
+
+ git -c credential.helper="$CREDENTIAL_HELPER" ls-remote $ORIGIN_URL &&
+
+ test_cmp get-expected.cred get-actual.cred &&
+ test_cmp store-expected.cred store-actual.cred
+'
+
+test_expect_success 'http auth www-auth headers to credential helper empty continuation hdrs' '
+ test_when_finished "per_test_cleanup" &&
+ # base64("alice:secret-passwd")
+ USERPASS64=YWxpY2U6c2VjcmV0LXBhc3N3ZA== &&
+ export USERPASS64 &&
+
+ cat >auth.config <<-EOF &&
+ [auth]
+ challenge = basic:realm=\"example.com\"
+ token = basic:$USERPASS64
+ extraheader = "WWW-Authenticate:"
+ extraheader = " "
+ extraheader = " bearer authority=\"id.example.com\""
+ EOF
+
+ start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
+
+ cat >get-expected.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ wwwauth[]=basic realm="example.com"
+ wwwauth[]=bearer authority="id.example.com"
+ EOF
+
+ cat >store-expected.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ username=alice
+ password=secret-passwd
+ EOF
+
+ cat >get-response.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ username=alice
+ password=secret-passwd
+ EOF
+
+ git -c credential.helper="$CREDENTIAL_HELPER" ls-remote $ORIGIN_URL &&
+
+ test_cmp get-expected.cred get-actual.cred &&
+ test_cmp store-expected.cred store-actual.cred
+'
+
+test_expect_success 'http auth www-auth headers to credential helper custom schemes' '
+ test_when_finished "per_test_cleanup" &&
+ # base64("alice:secret-passwd")
+ USERPASS64=YWxpY2U6c2VjcmV0LXBhc3N3ZA== &&
+ export USERPASS64 &&
+
+ cat >auth.config <<-EOF &&
+ [auth]
+ challenge = "foobar:alg=test widget=1"
+ challenge = "bearer:authority=\"id.example.com\" q=1 p=0"
+ challenge = basic:realm=\"example.com\"
+ token = basic:$USERPASS64
+ EOF
+
+ start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
+
+ cat >get-expected.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ wwwauth[]=foobar alg=test widget=1
+ wwwauth[]=bearer authority="id.example.com" q=1 p=0
+ wwwauth[]=basic realm="example.com"
+ EOF
+
+ cat >store-expected.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ username=alice
+ password=secret-passwd
+ EOF
+
+ cat >get-response.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ username=alice
+ password=secret-passwd
+ EOF
+
+ git -c credential.helper="$CREDENTIAL_HELPER" ls-remote $ORIGIN_URL &&
+
+ test_cmp get-expected.cred get-actual.cred &&
+ test_cmp store-expected.cred store-actual.cred
+'
+
+test_expect_success 'http auth www-auth headers to credential helper invalid' '
+ test_when_finished "per_test_cleanup" &&
+ # base64("alice:secret-passwd")
+ USERPASS64=YWxpY2U6c2VjcmV0LXBhc3N3ZA== &&
+ export USERPASS64 &&
+
+ cat >auth.config <<-EOF &&
+ [auth]
+ challenge = "bearer:authority=\"id.example.com\" q=1 p=0"
+ challenge = basic:realm=\"example.com\"
+ token = basic:$USERPASS64
+ EOF
+
+ start_http_server --auth-config="$TRASH_DIRECTORY/auth.config" &&
+
+ cat >get-expected.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ wwwauth[]=bearer authority="id.example.com" q=1 p=0
+ wwwauth[]=basic realm="example.com"
+ EOF
+
+ cat >erase-expected.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ username=alice
+ password=invalid-passwd
+ wwwauth[]=bearer authority="id.example.com" q=1 p=0
+ wwwauth[]=basic realm="example.com"
+ EOF
+
+ cat >get-response.cred <<-EOF &&
+ protocol=http
+ host=$HOST_PORT
+ username=alice
+ password=invalid-passwd
+ EOF
+
+ test_must_fail git -c credential.helper="$CREDENTIAL_HELPER" ls-remote $ORIGIN_URL &&
+
+ test_cmp get-expected.cred get-actual.cred &&
+ test_cmp erase-expected.cred erase-actual.cred
+'
+
test_done