1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-05-19 01:16:08 +02:00

credential: add an argument to keep state

Until now, our credential code has mostly deal with usernames and
passwords and we've let libcurl deal with the variant of authentication
to be used.  However, now that we have the credential value, the
credential helper can take control of the authentication, so the value
provided might be something that's generated, such as a Digest hash
value.

In such a case, it would be helpful for a credential helper that gets an
erase or store command to be able to keep track of an identifier for the
original secret that went into the computation.  Furthermore, some types
of authentication, such as NTLM and Kerberos, actually need two round
trips to authenticate, which will require that the credential helper
keep some state.

In order to allow for these use cases and others, allow storing state in
a field called "state[]".  This value is passed back to the credential
helper that created it, which avoids confusion caused by parsing values
from different helpers.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
brian m. carlson 2024-04-17 00:02:33 +00:00 committed by Junio C Hamano
parent ad9bb6dfe6
commit 8470c94be3
4 changed files with 71 additions and 12 deletions

View File

@ -211,6 +211,15 @@ can determine whether the operation was successful.
This value should not be sent unless the appropriate capability (see below) is
provided on input.
`state[]`::
This value provides an opaque state that will be passed back to this helper
if it is called again. Each different credential helper may specify this
once. The value should include a prefix unique to the credential helper and
should ignore values that don't match its prefix.
+
This value should not be sent unless the appropriate capability (see below) is
provided on input.
`wwwauth[]`::
When an HTTP response is received by Git that includes one or more
@ -223,18 +232,19 @@ they appear in the HTTP response. This attribute is 'one-way' from Git
to pass additional information to credential helpers.
`capability[]`::
This signals that the caller supports the capability in question.
This can be used to provide better, more specific data as part of the
This signals that Git, or the helper, as appropriate, supports the capability
in question. This can be used to provide better, more specific data as part
of the protocol. A `capability[]` directive must precede any value depending
on it and these directives _should_ be the first item announced in the
protocol.
+
The only capability currently supported is `authtype`, which indicates that the
`authtype`, `credential`, and `ephemeral` values are understood. It is not
obligatory to use these values in such a case, but they should not be provided
without this capability.
There are two currently supported capabilities. The first is `authtype`, which
indicates that the `authtype`, `credential`, and `ephemeral` values are
understood. The second is `state`, which indicates that the `state[]` and
`continue` values are understood.
+
Callers of `git credential` and credential helpers should emit the
capabilities they support unconditionally, and Git will gracefully
handle passing them on.
It is not obligatory to use the additional features just because the capability
is supported, but they should not be provided without the capability.
Unrecognised attributes and capabilities are silently discarded.

View File

@ -30,6 +30,7 @@ void credential_clear(struct credential *c)
free(c->authtype);
string_list_clear(&c->helpers, 0);
strvec_clear(&c->wwwauth_headers);
strvec_clear(&c->state_headers);
credential_init(c);
}
@ -293,8 +294,13 @@ int credential_read(struct credential *c, FILE *fp,
c->ephemeral = !!git_config_bool("ephemeral", value);
} else if (!strcmp(key, "wwwauth[]")) {
strvec_push(&c->wwwauth_headers, value);
} else if (!strcmp(key, "capability[]") && !strcmp(value, "authtype")) {
credential_set_capability(&c->capa_authtype, op_type);
} else if (!strcmp(key, "state[]")) {
strvec_push(&c->state_headers, value);
} else if (!strcmp(key, "capability[]")) {
if (!strcmp(value, "authtype"))
credential_set_capability(&c->capa_authtype, op_type);
else if (!strcmp(value, "state"))
credential_set_capability(&c->capa_state, op_type);
} else if (!strcmp(key, "password_expiry_utc")) {
errno = 0;
c->password_expiry_utc = parse_timestamp(value, NULL, 10);
@ -337,8 +343,12 @@ static void credential_write_item(FILE *fp, const char *key, const char *value,
void credential_write(const struct credential *c, FILE *fp,
enum credential_op_type op_type)
{
if (credential_has_capability(&c->capa_authtype, op_type)) {
if (credential_has_capability(&c->capa_authtype, op_type))
credential_write_item(fp, "capability[]", "authtype", 0);
if (credential_has_capability(&c->capa_state, op_type))
credential_write_item(fp, "capability[]", "state", 0);
if (credential_has_capability(&c->capa_authtype, op_type)) {
credential_write_item(fp, "authtype", c->authtype, 0);
credential_write_item(fp, "credential", c->credential, 0);
if (c->ephemeral)
@ -357,6 +367,10 @@ void credential_write(const struct credential *c, FILE *fp,
}
for (size_t i = 0; i < c->wwwauth_headers.nr; i++)
credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0);
if (credential_has_capability(&c->capa_state, op_type)) {
for (size_t i = 0; i < c->state_headers.nr; i++)
credential_write_item(fp, "state[]", c->state_headers.v[i], 0);
}
}
static int run_credential_helper(struct credential *c,

View File

@ -144,6 +144,11 @@ struct credential {
*/
struct strvec wwwauth_headers;
/**
* A `strvec` of state headers from credential helpers.
*/
struct strvec state_headers;
/**
* Internal use only. Keeps track of if we previously matched against a
* WWW-Authenticate header line in order to re-fold future continuation
@ -159,6 +164,7 @@ struct credential {
username_from_proto:1;
struct credential_capability capa_authtype;
struct credential_capability capa_state;
char *username;
char *password;
@ -180,6 +186,7 @@ struct credential {
.helpers = STRING_LIST_INIT_DUP, \
.password_expiry_utc = TIME_MAX, \
.wwwauth_headers = STRVEC_INIT, \
.state_headers = STRVEC_INIT, \
}
/* Initialize a credential structure, setting all fields to empty. */

View File

@ -46,9 +46,12 @@ test_expect_success 'setup helper scripts' '
credential=$1; shift
. ./dump
echo capability[]=authtype
echo capability[]=state
test -z "${capability##*authtype*}" || exit 0
test -z "$authtype" || echo authtype=$authtype
test -z "$credential" || echo credential=$credential
test -z "${capability##*state*}" || exit 0
echo state[]=verbatim-cred:foo
EOF
write_script git-credential-verbatim-ephemeral <<-\EOF &&
@ -129,6 +132,28 @@ test_expect_success 'credential_fill invokes helper with ephemeral credential' '
verbatim-ephemeral: host=example.com
EOF
'
test_expect_success 'credential_fill invokes helper with credential and state' '
check fill "verbatim-cred Bearer token" <<-\EOF
capability[]=authtype
capability[]=state
protocol=http
host=example.com
--
capability[]=authtype
capability[]=state
authtype=Bearer
credential=token
protocol=http
host=example.com
state[]=verbatim-cred:foo
--
verbatim-cred: get
verbatim-cred: capability[]=authtype
verbatim-cred: capability[]=state
verbatim-cred: protocol=http
verbatim-cred: host=example.com
EOF
'
test_expect_success 'credential_fill invokes multiple helpers' '
check fill useless "verbatim foo bar" <<-\EOF
@ -152,6 +177,7 @@ test_expect_success 'credential_fill invokes multiple helpers' '
test_expect_success 'credential_fill response does not get capabilities when helpers are incapable' '
check fill useless "verbatim foo bar" <<-\EOF
capability[]=authtype
capability[]=state
protocol=http
host=example.com
--
@ -162,10 +188,12 @@ test_expect_success 'credential_fill response does not get capabilities when hel
--
useless: get
useless: capability[]=authtype
useless: capability[]=state
useless: protocol=http
useless: host=example.com
verbatim: get
verbatim: capability[]=authtype
verbatim: capability[]=state
verbatim: protocol=http
verbatim: host=example.com
EOF