diff mbox series

[v2] credential/libsecret: support password_expiry_utc

Message ID pull.1469.v2.git.git.1679729764851.gitgitgadget@gmail.com (mailing list archive)
State New, archived
Headers show
Series [v2] credential/libsecret: support password_expiry_utc | expand

Commit Message

M Hickford March 25, 2023, 7:36 a.m. UTC
From: M Hickford <mirth.hickford@gmail.com>

Signed-off-by: M Hickford <mirth.hickford@gmail.com>
---
    credential/libsecret: store password_expiry_utc
    
    Patch v2 adds tests.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1469%2Fhickford%2Flibsecret-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1469/hickford/libsecret-v2
Pull-Request: https://github.com/git/git/pull/1469

Range-diff vs v1:

 1:  46ba4863f33 ! 1:  1e27677b6f5 credential/libsecret: support password_expiry_utc
     @@ contrib/credential/libsecret/git-credential-libsecret.c: static void credential_
       }
       
       static void usage(const char *name)
     +
     + ## t/lib-credential.sh ##
     +@@ t/lib-credential.sh: helper_test_clean() {
     + 	reject $1 https example.com store-user
     + 	reject $1 https example.com user1
     + 	reject $1 https example.com user2
     ++	reject $1 https example.com user3
     + 	reject $1 http path.tld user
     + 	reject $1 https timeout.tld user
     + 	reject $1 https sso.tld
     +@@ t/lib-credential.sh: helper_test_timeout() {
     + 	'
     + }
     + 
     ++helper_test_password_expiry_utc() {
     ++	HELPER=$1
     ++
     ++	test_expect_success "helper ($HELPER) stores password_expiry_utc" '
     ++		check approve $HELPER <<-\EOF
     ++		protocol=https
     ++		host=example.com
     ++		username=user3
     ++		password=pass
     ++		password_expiry_utc=9999999999
     ++		EOF
     ++	'
     ++
     ++	test_expect_success "helper ($HELPER) gets password_expiry_utc" '
     ++		check fill $HELPER <<-\EOF
     ++		protocol=https
     ++		host=example.com
     ++		username=user3
     ++		--
     ++		protocol=https
     ++		host=example.com
     ++		username=user3
     ++		password=pass
     ++		password_expiry_utc=9999999999
     ++		--
     ++		EOF
     ++	'
     ++}
     ++
     + write_script askpass <<\EOF
     + echo >&2 askpass: $*
     + what=$(echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z)
     +
     + ## t/t0301-credential-cache.sh ##
     +@@ t/t0301-credential-cache.sh: test_atexit 'git credential-cache exit'
     + # test that the daemon works with no special setup
     + helper_test cache
     + 
     ++helper_test_password_expiry_utc cache
     ++
     + test_expect_success 'socket defaults to ~/.cache/git/credential/socket' '
     + 	test_when_finished "
     + 		git credential-cache exit &&
     +
     + ## t/t0303-credential-external.sh ##
     +@@ t/t0303-credential-external.sh: else
     + 	helper_test_timeout "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"
     + fi
     + 
     ++helper_test_password_expiry_utc "$GIT_TEST_CREDENTIAL_HELPER"
     ++
     + # clean afterwards so that we are good citizens
     + # and don't leave cruft in the helper's storage, which
     + # might be long-term system storage


 .../libsecret/git-credential-libsecret.c      | 42 ++++++++++++++++---
 t/lib-credential.sh                           | 30 +++++++++++++
 t/t0301-credential-cache.sh                   |  2 +
 t/t0303-credential-external.sh                |  2 +
 4 files changed, 71 insertions(+), 5 deletions(-)


base-commit: 27d43aaaf50ef0ae014b88bba294f93658016a2e

Comments

Junio C Hamano May 4, 2023, 5:42 p.m. UTC | #1
"M Hickford via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: M Hickford <mirth.hickford@gmail.com>
>
> Signed-off-by: M Hickford <mirth.hickford@gmail.com>
> ---
> diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c
> index 2c5d76d789f..3f2b530db79 100644
> --- a/contrib/credential/libsecret/git-credential-libsecret.c
> +++ b/contrib/credential/libsecret/git-credential-libsecret.c
> @@ -39,6 +39,7 @@ struct credential {
>  	char *path;
>  	char *username;
>  	char *password;
> +	char *password_expiry_utc;
>  };
>  
>  #define CREDENTIAL_INIT { 0 }
> @@ -54,6 +55,20 @@ struct credential_operation {
>  
>  /* ----------------- Secret Service functions ----------------- */
>  
> +static const SecretSchema schema = {
> +	"org.git.Password",
> +	SECRET_SCHEMA_NONE,
> +	{
> +		{  "user", SECRET_SCHEMA_ATTRIBUTE_STRING },
> +		{  "object", SECRET_SCHEMA_ATTRIBUTE_STRING },
> +		{  "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING },
> +		{  "port", SECRET_SCHEMA_ATTRIBUTE_INTEGER },
> +		{  "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
> +		{  "password_expiry_utc", SECRET_SCHEMA_ATTRIBUTE_INTEGER },
> +		{  NULL, 0 },
> +	}
> +};

We used to use the bog-standard "COMPAT_NETWORK" but now we are
adding an extra element, and that makes it necessary to define our
own?  OK.

>  static char *make_label(struct credential *c)
>  {
>  	if (c->port)
> @@ -78,6 +93,9 @@ static GHashTable *make_attr_list(struct credential *c)
>  		g_hash_table_insert(al, "port", g_strdup_printf("%hu", c->port));
>  	if (c->path)
>  		g_hash_table_insert(al, "object", g_strdup(c->path));
> +	if (c->password_expiry_utc)
> +		g_hash_table_insert(al, "password_expiry_utc",
> +			g_strdup(c->password_expiry_utc));
>  
>  	return al;
>  }
> @@ -101,9 +119,11 @@ static int keyring_get(struct credential *c)
>  
>  	attributes = make_attr_list(c);
>  	items = secret_service_search_sync(service,
> -					   SECRET_SCHEMA_COMPAT_NETWORK,
> +					   &schema,
>  					   attributes,
> -					   SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK,
> +					   SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK |
> +					   // for backwards compatibility

No // comments please.

> +					   SECRET_SCHEMA_DONT_MATCH_NAME,

SECRET_SCHEMA_DONT_MATCH_NAME does not seem to be listed as one of
the flags to be used with secret_service_search_sync(),

    https://www.manpagez.com/html/libsecret-1/libsecret-1-0.18.6/SecretService.php#secret-service-search-sync

and the only reference to it I found was as a flag to be placed in
the schema.

    https://www.manpagez.com/html/libsecret-1/libsecret-1-0.18.6/migrating-schemas.php
    https://www.manpagez.com/html/libsecret-1/libsecret-1-0.18.6/libsecret-SecretSchema.php

But I'll take your word for it.

I found nothing unexpected or surprising in the rest of the patch to
this file.  They all looked just a fallout of having to store and
retrieve one extra item from the database together with many other
things we already store and retrieve.  Cleanly written.

> diff --git a/t/lib-credential.sh b/t/lib-credential.sh
> index 5ea8bc9f1dc..9ebf7eeae48 100644
> --- a/t/lib-credential.sh
> +++ b/t/lib-credential.sh
> @@ -43,6 +43,7 @@ helper_test_clean() {
>  	reject $1 https example.com store-user
>  	reject $1 https example.com user1
>  	reject $1 https example.com user2
> +	reject $1 https example.com user3
>  	reject $1 http path.tld user
>  	reject $1 https timeout.tld user
>  	reject $1 https sso.tld
> @@ -298,6 +299,35 @@ helper_test_timeout() {
>  	'
>  }
>  
> +helper_test_password_expiry_utc() {
> +	HELPER=$1
> +
> +	test_expect_success "helper ($HELPER) stores password_expiry_utc" '
> +		check approve $HELPER <<-\EOF
> +		protocol=https
> +		host=example.com
> +		username=user3
> +		password=pass
> +		password_expiry_utc=9999999999
> +		EOF
> +	'
>
> +	test_expect_success "helper ($HELPER) gets password_expiry_utc" '
> +		check fill $HELPER <<-\EOF
> +		protocol=https
> +		host=example.com
> +		username=user3
> +		--
> +		protocol=https
> +		host=example.com
> +		username=user3
> +		password=pass
> +		password_expiry_utc=9999999999
> +		--
> +		EOF
> +	'
> +}
> +

Is any random number usable for this test, or is there some
constraints (like, "it cannot be too small to be a timestamp in the
past, because the entry will be expired immediately")?  If there is
some constraint like that, is it a good idea to also test that
(like, "make sure an entry with expiry already happened is
rejected")?

Thanks.
M Hickford May 5, 2023, 7 a.m. UTC | #2
On Thu, 4 May 2023 at 18:42, Junio C Hamano <gitster@pobox.com> wrote:
>
> "M Hickford via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> We used to use the bog-standard "COMPAT_NETWORK" but now we are
> adding an extra element, and that makes it necessary to define our
> own?  OK.

Exactly.

>
> >  static char *make_label(struct credential *c)
> >  {
> >       if (c->port)
> > @@ -78,6 +93,9 @@ static GHashTable *make_attr_list(struct credential *c)
> >               g_hash_table_insert(al, "port", g_strdup_printf("%hu", c->port));
> >       if (c->path)
> >               g_hash_table_insert(al, "object", g_strdup(c->path));
> > +     if (c->password_expiry_utc)
> > +             g_hash_table_insert(al, "password_expiry_utc",
> > +                     g_strdup(c->password_expiry_utc));
> >
> >       return al;
> >  }
> > @@ -101,9 +119,11 @@ static int keyring_get(struct credential *c)
> >
> >       attributes = make_attr_list(c);
> >       items = secret_service_search_sync(service,
> > -                                        SECRET_SCHEMA_COMPAT_NETWORK,
> > +                                        &schema,
> >                                          attributes,
> > -                                        SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK,
> > +                                        SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK |
> > +                                        // for backwards compatibility
>
> No // comments please.

I'll amend in v3.

>
> > +                                        SECRET_SCHEMA_DONT_MATCH_NAME,
>
> SECRET_SCHEMA_DONT_MATCH_NAME does not seem to be listed as one of
> the flags to be used with secret_service_search_sync(),
>
>     https://www.manpagez.com/html/libsecret-1/libsecret-1-0.18.6/SecretService.php#secret-service-search-sync
>
> and the only reference to it I found was as a flag to be placed in
> the schema.
>
>     https://www.manpagez.com/html/libsecret-1/libsecret-1-0.18.6/migrating-schemas.php
>     https://www.manpagez.com/html/libsecret-1/libsecret-1-0.18.6/libsecret-SecretSchema.php
>
> But I'll take your word for it.

Good spot. I must have misread the docs. I shall fix in v3 and test
backwards compatibility carefully.

[1] https://gnome.pages.gitlab.gnome.org/libsecret/migrating-libgnome-keyring.html#working-with-schemas

>
> I found nothing unexpected or surprising in the rest of the patch to
> this file.  They all looked just a fallout of having to store and
> retrieve one extra item from the database together with many other
> things we already store and retrieve.  Cleanly written.

Thanks Junio for the review.

>
> > diff --git a/t/lib-credential.sh b/t/lib-credential.sh
> > index 5ea8bc9f1dc..9ebf7eeae48 100644
> > --- a/t/lib-credential.sh
> > +++ b/t/lib-credential.sh
> > @@ -43,6 +43,7 @@ helper_test_clean() {
> >       reject $1 https example.com store-user
> >       reject $1 https example.com user1
> >       reject $1 https example.com user2
> > +     reject $1 https example.com user3
> >       reject $1 http path.tld user
> >       reject $1 https timeout.tld user
> >       reject $1 https sso.tld
> > @@ -298,6 +299,35 @@ helper_test_timeout() {
> >       '
> >  }
> >
> > +helper_test_password_expiry_utc() {
> > +     HELPER=$1
> > +
> > +     test_expect_success "helper ($HELPER) stores password_expiry_utc" '
> > +             check approve $HELPER <<-\EOF
> > +             protocol=https
> > +             host=example.com
> > +             username=user3
> > +             password=pass
> > +             password_expiry_utc=9999999999
> > +             EOF
> > +     '
> >
> > +     test_expect_success "helper ($HELPER) gets password_expiry_utc" '
> > +             check fill $HELPER <<-\EOF
> > +             protocol=https
> > +             host=example.com
> > +             username=user3
> > +             --
> > +             protocol=https
> > +             host=example.com
> > +             username=user3
> > +             password=pass
> > +             password_expiry_utc=9999999999
> > +             --
> > +             EOF
> > +     '
> > +}
> > +
>
> Is any random number usable for this test, or is there some
> constraints (like, "it cannot be too small to be a timestamp in the
> past, because the entry will be expired immediately")?  If there is
> some constraint like that, is it a good idea to also test that
> (like, "make sure an entry with expiry already happened is
> rejected")?

The date has to be in the future otherwise the credential will be
discarded by `credential approve` before it reaches the helper. That
logic is already tested in t/t0300-credentials.sh. There isn't any
expiry logic in the helper itself to test.

>
> Thanks.
>
diff mbox series

Patch

diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c
index 2c5d76d789f..3f2b530db79 100644
--- a/contrib/credential/libsecret/git-credential-libsecret.c
+++ b/contrib/credential/libsecret/git-credential-libsecret.c
@@ -39,6 +39,7 @@  struct credential {
 	char *path;
 	char *username;
 	char *password;
+	char *password_expiry_utc;
 };
 
 #define CREDENTIAL_INIT { 0 }
@@ -54,6 +55,20 @@  struct credential_operation {
 
 /* ----------------- Secret Service functions ----------------- */
 
+static const SecretSchema schema = {
+	"org.git.Password",
+	SECRET_SCHEMA_NONE,
+	{
+		{  "user", SECRET_SCHEMA_ATTRIBUTE_STRING },
+		{  "object", SECRET_SCHEMA_ATTRIBUTE_STRING },
+		{  "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING },
+		{  "port", SECRET_SCHEMA_ATTRIBUTE_INTEGER },
+		{  "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
+		{  "password_expiry_utc", SECRET_SCHEMA_ATTRIBUTE_INTEGER },
+		{  NULL, 0 },
+	}
+};
+
 static char *make_label(struct credential *c)
 {
 	if (c->port)
@@ -78,6 +93,9 @@  static GHashTable *make_attr_list(struct credential *c)
 		g_hash_table_insert(al, "port", g_strdup_printf("%hu", c->port));
 	if (c->path)
 		g_hash_table_insert(al, "object", g_strdup(c->path));
+	if (c->password_expiry_utc)
+		g_hash_table_insert(al, "password_expiry_utc",
+			g_strdup(c->password_expiry_utc));
 
 	return al;
 }
@@ -101,9 +119,11 @@  static int keyring_get(struct credential *c)
 
 	attributes = make_attr_list(c);
 	items = secret_service_search_sync(service,
-					   SECRET_SCHEMA_COMPAT_NETWORK,
+					   &schema,
 					   attributes,
-					   SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK,
+					   SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK |
+					   // for backwards compatibility
+					   SECRET_SCHEMA_DONT_MATCH_NAME,
 					   NULL,
 					   &error);
 	g_hash_table_unref(attributes);
@@ -128,6 +148,12 @@  static int keyring_get(struct credential *c)
 			c->username = g_strdup(s);
 		}
 
+		s = g_hash_table_lookup(attributes, "password_expiry_utc");
+		if (s) {
+			g_free(c->password_expiry_utc);
+			c->password_expiry_utc = g_strdup(s);
+		}
+
 		s = secret_value_get_text(secret);
 		if (s) {
 			g_free(c->password);
@@ -162,7 +188,7 @@  static int keyring_store(struct credential *c)
 
 	label = make_label(c);
 	attributes = make_attr_list(c);
-	secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK,
+	secret_password_storev_sync(&schema,
 				    attributes,
 				    NULL,
 				    label,
@@ -198,7 +224,7 @@  static int keyring_erase(struct credential *c)
 		return EXIT_FAILURE;
 
 	attributes = make_attr_list(c);
-	secret_password_clearv_sync(SECRET_SCHEMA_COMPAT_NETWORK,
+	secret_password_clearv_sync(&schema,
 				    attributes,
 				    NULL,
 				    &error);
@@ -238,6 +264,7 @@  static void credential_clear(struct credential *c)
 	g_free(c->path);
 	g_free(c->username);
 	g_free(c->password);
+	g_free(c->password_expiry_utc);
 
 	credential_init(c);
 }
@@ -285,6 +312,9 @@  static int credential_read(struct credential *c)
 		} else if (!strcmp(key, "username")) {
 			g_free(c->username);
 			c->username = g_strdup(value);
+		} else if (!strcmp(key, "password_expiry_utc")) {
+			g_free(c->password_expiry_utc);
+			c->password_expiry_utc = g_strdup(value);
 		} else if (!strcmp(key, "password")) {
 			g_free(c->password);
 			c->password = g_strdup(value);
@@ -312,9 +342,11 @@  static void credential_write_item(FILE *fp, const char *key, const char *value)
 
 static void credential_write(const struct credential *c)
 {
-	/* only write username/password, if set */
+	/* only write username/password/expiry, if set */
 	credential_write_item(stdout, "username", c->username);
 	credential_write_item(stdout, "password", c->password);
+	credential_write_item(stdout, "password_expiry_utc",
+		c->password_expiry_utc);
 }
 
 static void usage(const char *name)
diff --git a/t/lib-credential.sh b/t/lib-credential.sh
index 5ea8bc9f1dc..9ebf7eeae48 100644
--- a/t/lib-credential.sh
+++ b/t/lib-credential.sh
@@ -43,6 +43,7 @@  helper_test_clean() {
 	reject $1 https example.com store-user
 	reject $1 https example.com user1
 	reject $1 https example.com user2
+	reject $1 https example.com user3
 	reject $1 http path.tld user
 	reject $1 https timeout.tld user
 	reject $1 https sso.tld
@@ -298,6 +299,35 @@  helper_test_timeout() {
 	'
 }
 
+helper_test_password_expiry_utc() {
+	HELPER=$1
+
+	test_expect_success "helper ($HELPER) stores password_expiry_utc" '
+		check approve $HELPER <<-\EOF
+		protocol=https
+		host=example.com
+		username=user3
+		password=pass
+		password_expiry_utc=9999999999
+		EOF
+	'
+
+	test_expect_success "helper ($HELPER) gets password_expiry_utc" '
+		check fill $HELPER <<-\EOF
+		protocol=https
+		host=example.com
+		username=user3
+		--
+		protocol=https
+		host=example.com
+		username=user3
+		password=pass
+		password_expiry_utc=9999999999
+		--
+		EOF
+	'
+}
+
 write_script askpass <<\EOF
 echo >&2 askpass: $*
 what=$(echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z)
diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh
index 698b7159f03..f5ba727e53b 100755
--- a/t/t0301-credential-cache.sh
+++ b/t/t0301-credential-cache.sh
@@ -30,6 +30,8 @@  test_atexit 'git credential-cache exit'
 # test that the daemon works with no special setup
 helper_test cache
 
+helper_test_password_expiry_utc cache
+
 test_expect_success 'socket defaults to ~/.cache/git/credential/socket' '
 	test_when_finished "
 		git credential-cache exit &&
diff --git a/t/t0303-credential-external.sh b/t/t0303-credential-external.sh
index f028fd14182..f1478680bff 100755
--- a/t/t0303-credential-external.sh
+++ b/t/t0303-credential-external.sh
@@ -52,6 +52,8 @@  else
 	helper_test_timeout "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"
 fi
 
+helper_test_password_expiry_utc "$GIT_TEST_CREDENTIAL_HELPER"
+
 # clean afterwards so that we are good citizens
 # and don't leave cruft in the helper's storage, which
 # might be long-term system storage