git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] credential: new attribute password_expiry_utc

From
M Hickford via GitGitGadget <gitgitgadget@gmail.com>
Date
Jan 28, 2023, 14:04 UTC
Message-ID
<pull.1443.git.git.1674914650588.gitgitgadget@gmail.com>
From: M Hickford <mirth.hickford@gmail.com>

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@gmail.com>
---
    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@gmail.com
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
Next: Junio C Hamano
Message 1 of 26 in “credential: new attribute password_expiry_utc”
  1. credential: new attribute password_expiry_utcM Hickford via GitGitGadget, Jan 28, 2023
  2. Junio C HamanoJan 29, 2023
  3. M HickfordFeb 1, 2023
  4. Junio C HamanoFeb 1, 2023
  5. Eric SunshineJan 30, 2023
  6. M HickfordFeb 5, 2023
  7. credential: new attribute password_expiry_utcM Hickford via GitGitGadget, Feb 1, 2023
  8. Jeff KingFeb 1, 2023
  9. Junio C HamanoFeb 1, 2023
  10. Jeff KingFeb 2, 2023
  11. Matthew John CheethamFeb 1, 2023
  12. Jeff KingFeb 2, 2023
  13. M HickfordFeb 5, 2023
  14. Matthew John CheethamFeb 6, 2023
  15. M HickfordFeb 5, 2023
  16. credential: new attribute password_expiry_utcM Hickford via GitGitGadget, Feb 4, 2023
  17. Junio C HamanoFeb 14, 2023
  18. M HickfordFeb 14, 2023
  19. Lessley DenningtonFeb 17, 2023
  20. Junio C HamanoFeb 17, 2023
  21. M HickfordFeb 18, 2023
  22. Martin ÅgrenFeb 14, 2023
  23. Calvin WanFeb 16, 2023
  24. M HickfordFeb 18, 2023
  25. credential: new attribute password_expiry_utcM Hickford via GitGitGadget, Feb 18, 2023
  26. Calvin WanFeb 22, 2023

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.