From: M Hickford <mirth.hickford@xxxxxxxxx> If password has expired, credential fill no longer returns early, so later helpers can generate a fresh credential. This is backwards compatible -- no change in behaviour with helpers that discard the expiry attribute. The expiry logic is entirely in the git credential layer; compatible helpers simply store and return the expiry attribute verbatim. Store new attribute in cache. Signed-off-by: M Hickford <mirth.hickford@xxxxxxxxx> --- credential: new attribute password_expiry_utc Some passwords, such as a personal access token or OAuth access token, may have an expiry date (as long as years for PATs or as short as hours for an OAuth access token). Add a new credential attribute password_expiry_utc. If password has expired, credential fill no longer returns early, so later helpers have opportunity to generate a fresh credential. This is backwards compatible -- no change in behaviour with helpers that discard the expiry attribute. The expiry logic is entirely in the git credential layer. Credential-generating helpers need only output the expiry attribute. Storage helpers should store the expiry if they can. Store expiry attribute in cache. This is particularly useful when a storage helper and a credential-generating helper are configured together, eg. [credential] helper = storage # eg. cache or osxkeychain helper = generate # eg. oauth Without this patch, credential fill may return an expired credential from storage, causing authentication to fail. With this patch: a fresh credential is generated if and only if the credential is expired. Example usage in a credential-generating helper https://github.com/hickford/git-credential-oauth/pull/16/files Signed-off-by: M Hickford mirth.hickford@xxxxxxxxx Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1443%2Fhickford%2Fpassword-expiry-v1 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1443/hickford/password-expiry-v1 Pull-Request: https://github.com/git/git/pull/1443 Documentation/git-credential.txt | 4 ++++ builtin/credential-cache--daemon.c | 3 +++ credential.c | 21 +++++++++++++++++++++ credential.h | 1 + 4 files changed, 29 insertions(+) diff --git a/Documentation/git-credential.txt b/Documentation/git-credential.txt index ac2818b9f66..15ace648bdd 100644 --- a/Documentation/git-credential.txt +++ b/Documentation/git-credential.txt @@ -144,6 +144,10 @@ Git understands the following attributes: The credential's password, if we are asking it to be stored. +`password_expiry_utc`:: + + If password is a personal access token or OAuth access token, it may have an expiry date. When getting credentials from a helper, `git credential fill` ignores the password attribute if the expiry date has passed. Storage helpers should store this attribute if possible. Helpers should not implement expiry logic themselves. Represented as Unix time UTC, seconds since 1970. + `url`:: When this special attribute is read by `git credential`, the diff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c index f3c89831d4a..5cb8a186b45 100644 --- a/builtin/credential-cache--daemon.c +++ b/builtin/credential-cache--daemon.c @@ -127,6 +127,9 @@ static void serve_one_client(FILE *in, FILE *out) if (e) { fprintf(out, "username=%s\n", e->item.username); fprintf(out, "password=%s\n", e->item.password); + if (e->item.password_expiry_utc != 0) { + fprintf(out, "password_expiry_utc=%ld\n", e->item.password_expiry_utc); + } } } else if (!strcmp(action.buf, "exit")) { diff --git a/credential.c b/credential.c index f6389a50684..0a3a9cbf0a2 100644 --- a/credential.c +++ b/credential.c @@ -7,6 +7,7 @@ #include "prompt.h" #include "sigchain.h" #include "urlmatch.h" +#include <time.h> void credential_init(struct credential *c) { @@ -21,6 +22,7 @@ void credential_clear(struct credential *c) free(c->path); free(c->username); free(c->password); + c->password_expiry_utc = 0; string_list_clear(&c->helpers, 0); credential_init(c); @@ -234,11 +236,23 @@ int credential_read(struct credential *c, FILE *fp) } else if (!strcmp(key, "path")) { free(c->path); c->path = xstrdup(value); + } else if (!strcmp(key, "password_expiry_utc")) { + // TODO: ignore if can't parse integer + c->password_expiry_utc = atoi(value); } else if (!strcmp(key, "url")) { credential_from_url(c, value); } else if (!strcmp(key, "quit")) { c->quit = !!git_config_bool("quit", value); } + + // if expiry date has passed, ignore password and expiry fields + if (c->password_expiry_utc != 0 && time(NULL) > c->password_expiry_utc) { + trace_printf(_("Password has expired.\n")); + FREE_AND_NULL(c->username); + FREE_AND_NULL(c->password); + c->password_expiry_utc = 0; + } + /* * Ignore other lines; we don't know what they mean, but * this future-proofs us when later versions of git do @@ -269,6 +283,13 @@ 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); + if (c->password_expiry_utc != 0) { + int length = snprintf( NULL, 0, "%ld", c->password_expiry_utc); + char* str = malloc( length + 1 ); + snprintf( str, length + 1, "%ld", c->password_expiry_utc ); + credential_write_item(fp, "password_expiry_utc", str, 0); + free(str); + } } static int run_credential_helper(struct credential *c, diff --git a/credential.h b/credential.h index f430e77fea4..e10f7c2b313 100644 --- a/credential.h +++ b/credential.h @@ -126,6 +126,7 @@ struct credential { char *protocol; char *host; char *path; + time_t password_expiry_utc; }; #define CREDENTIAL_INIT { \ base-commit: 5cc9858f1b470844dea5c5d3e936af183fdf2c68 -- gitgitgadget