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>

brian m. carlson committed Apr 17, 2024 at 00:02 UTC 8470c94be33d639c943e051a802c0e28eabf4a96
4 files changed +71 -12
Documentation/git-credential.txt
+19 -9
@@ -211,6 +211,15 @@ can determine whether the operation was successful.
211 This value should not be sent unless the appropriate capability (see below) is
212 provided on input.
213
214 +`state[]`::
215 + This value provides an opaque state that will be passed back to this helper
216 + if it is called again. Each different credential helper may specify this
217 + once. The value should include a prefix unique to the credential helper and
218 + should ignore values that don't match its prefix.
219 ++
220 +This value should not be sent unless the appropriate capability (see below) is
221 +provided on input.
222 +
223 `wwwauth[]`::
224
225 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
232 to pass additional information to credential helpers.
233
234 `capability[]`::
226 - This signals that the caller supports the capability in question.
227 - This can be used to provide better, more specific data as part of the
235 + This signals that Git, or the helper, as appropriate, supports the capability
236 + in question. This can be used to provide better, more specific data as part
237 + of the protocol. A `capability[]` directive must precede any value depending
238 + on it and these directives _should_ be the first item announced in the
239 protocol.
240 +
230 -The only capability currently supported is `authtype`, which indicates that the
231 -`authtype`, `credential`, and `ephemeral` values are understood. It is not
232 -obligatory to use these values in such a case, but they should not be provided
233 -without this capability.
241 +There are two currently supported capabilities. The first is `authtype`, which
242 +indicates that the `authtype`, `credential`, and `ephemeral` values are
243 +understood. The second is `state`, which indicates that the `state[]` and
244 +`continue` values are understood.
245 +
235 -Callers of `git credential` and credential helpers should emit the
236 -capabilities they support unconditionally, and Git will gracefully
237 -handle passing them on.
246 +It is not obligatory to use the additional features just because the capability
247 +is supported, but they should not be provided without the capability.
248
249 Unrecognised attributes and capabilities are silently discarded.
250
credential.c
+17 -3
@@ -30,6 +30,7 @@ void credential_clear(struct credential *c)
30 free(c->authtype);
31 string_list_clear(&c->helpers, 0);
32 strvec_clear(&c->wwwauth_headers);
33 + strvec_clear(&c->state_headers);
34
35 credential_init(c);
36 }
@@ -293,8 +294,13 @@ int credential_read(struct credential *c, FILE *fp,
294 c->ephemeral = !!git_config_bool("ephemeral", value);
295 } else if (!strcmp(key, "wwwauth[]")) {
296 strvec_push(&c->wwwauth_headers, value);
296 - } else if (!strcmp(key, "capability[]") && !strcmp(value, "authtype")) {
297 - credential_set_capability(&c->capa_authtype, op_type);
297 + } else if (!strcmp(key, "state[]")) {
298 + strvec_push(&c->state_headers, value);
299 + } else if (!strcmp(key, "capability[]")) {
300 + if (!strcmp(value, "authtype"))
301 + credential_set_capability(&c->capa_authtype, op_type);
302 + else if (!strcmp(value, "state"))
303 + credential_set_capability(&c->capa_state, op_type);
304 } else if (!strcmp(key, "password_expiry_utc")) {
305 errno = 0;
306 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,
343 void credential_write(const struct credential *c, FILE *fp,
344 enum credential_op_type op_type)
345 {
340 - if (credential_has_capability(&c->capa_authtype, op_type)) {
346 + if (credential_has_capability(&c->capa_authtype, op_type))
347 credential_write_item(fp, "capability[]", "authtype", 0);
348 + if (credential_has_capability(&c->capa_state, op_type))
349 + credential_write_item(fp, "capability[]", "state", 0);
350 +
351 + if (credential_has_capability(&c->capa_authtype, op_type)) {
352 credential_write_item(fp, "authtype", c->authtype, 0);
353 credential_write_item(fp, "credential", c->credential, 0);
354 if (c->ephemeral)
@@ -357,6 +367,10 @@ void credential_write(const struct credential *c, FILE *fp,
367 }
368 for (size_t i = 0; i < c->wwwauth_headers.nr; i++)
369 credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0);
370 + if (credential_has_capability(&c->capa_state, op_type)) {
371 + for (size_t i = 0; i < c->state_headers.nr; i++)
372 + credential_write_item(fp, "state[]", c->state_headers.v[i], 0);
373 + }
374 }
375
376 static int run_credential_helper(struct credential *c,
credential.h
+7
@@ -144,6 +144,11 @@ struct credential {
144 */
145 struct strvec wwwauth_headers;
146
147 + /**
148 + * A `strvec` of state headers from credential helpers.
149 + */
150 + struct strvec state_headers;
151 +
152 /**
153 * Internal use only. Keeps track of if we previously matched against a
154 * WWW-Authenticate header line in order to re-fold future continuation
@@ -159,6 +164,7 @@ struct credential {
164 username_from_proto:1;
165
166 struct credential_capability capa_authtype;
167 + struct credential_capability capa_state;
168
169 char *username;
170 char *password;
@@ -180,6 +186,7 @@ struct credential {
186 .helpers = STRING_LIST_INIT_DUP, \
187 .password_expiry_utc = TIME_MAX, \
188 .wwwauth_headers = STRVEC_INIT, \
189 + .state_headers = STRVEC_INIT, \
190 }
191
192 /* Initialize a credential structure, setting all fields to empty. */
t/t0300-credentials.sh
+28
@@ -46,9 +46,12 @@ test_expect_success 'setup helper scripts' '
46 credential=$1; shift
47 . ./dump
48 echo capability[]=authtype
49 + echo capability[]=state
50 test -z "${capability##*authtype*}" || exit 0
51 test -z "$authtype" || echo authtype=$authtype
52 test -z "$credential" || echo credential=$credential
53 + test -z "${capability##*state*}" || exit 0
54 + echo state[]=verbatim-cred:foo
55 EOF
56
57 write_script git-credential-verbatim-ephemeral <<-\EOF &&
@@ -129,6 +132,28 @@ test_expect_success 'credential_fill invokes helper with ephemeral credential' '
132 verbatim-ephemeral: host=example.com
133 EOF
134 '
135 +test_expect_success 'credential_fill invokes helper with credential and state' '
136 + check fill "verbatim-cred Bearer token" <<-\EOF
137 + capability[]=authtype
138 + capability[]=state
139 + protocol=http
140 + host=example.com
141 + --
142 + capability[]=authtype
143 + capability[]=state
144 + authtype=Bearer
145 + credential=token
146 + protocol=http
147 + host=example.com
148 + state[]=verbatim-cred:foo
149 + --
150 + verbatim-cred: get
151 + verbatim-cred: capability[]=authtype
152 + verbatim-cred: capability[]=state
153 + verbatim-cred: protocol=http
154 + verbatim-cred: host=example.com
155 + EOF
156 +'
157
158 test_expect_success 'credential_fill invokes multiple helpers' '
159 check fill useless "verbatim foo bar" <<-\EOF
@@ -152,6 +177,7 @@ test_expect_success 'credential_fill invokes multiple helpers' '
177 test_expect_success 'credential_fill response does not get capabilities when helpers are incapable' '
178 check fill useless "verbatim foo bar" <<-\EOF
179 capability[]=authtype
180 + capability[]=state
181 protocol=http
182 host=example.com
183 --
@@ -162,10 +188,12 @@ test_expect_success 'credential_fill response does not get capabilities when hel
188 --
189 useless: get
190 useless: capability[]=authtype
191 + useless: capability[]=state
192 useless: protocol=http
193 useless: host=example.com
194 verbatim: get
195 verbatim: capability[]=authtype
196 + verbatim: capability[]=state
197 verbatim: protocol=http
198 verbatim: host=example.com
199 EOF