Skip to content

Commit e839608

Browse files
committed
Merge branch 'mh/credential-libsecret-attrs'
The way authentication related data other than passwords (e.g. oath token and password expiration data) are stored in libsecret keyrings has been rethought. * mh/credential-libsecret-attrs: credential/libsecret: store new attributes
2 parents 6807fcf + 0ce02e2 commit e839608

File tree

4 files changed

+152
-6
lines changed

4 files changed

+152
-6
lines changed

contrib/credential/libsecret/git-credential-libsecret.c

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ struct credential {
3939
char *path;
4040
char *username;
4141
char *password;
42+
char *password_expiry_utc;
43+
char *oauth_refresh_token;
4244
};
4345

4446
#define CREDENTIAL_INIT { 0 }
@@ -54,6 +56,25 @@ struct credential_operation {
5456

5557
/* ----------------- Secret Service functions ----------------- */
5658

59+
static const SecretSchema schema = {
60+
"org.git.Password",
61+
/* Ignore schema name during search for backwards compatibility */
62+
SECRET_SCHEMA_DONT_MATCH_NAME,
63+
{
64+
/*
65+
* libsecret assumes attribute values are non-confidential and
66+
* unchanging, so we can't include oauth_refresh_token or
67+
* password_expiry_utc.
68+
*/
69+
{ "user", SECRET_SCHEMA_ATTRIBUTE_STRING },
70+
{ "object", SECRET_SCHEMA_ATTRIBUTE_STRING },
71+
{ "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING },
72+
{ "port", SECRET_SCHEMA_ATTRIBUTE_INTEGER },
73+
{ "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
74+
{ NULL, 0 },
75+
}
76+
};
77+
5778
static char *make_label(struct credential *c)
5879
{
5980
if (c->port)
@@ -101,7 +122,7 @@ static int keyring_get(struct credential *c)
101122

102123
attributes = make_attr_list(c);
103124
items = secret_service_search_sync(service,
104-
SECRET_SCHEMA_COMPAT_NETWORK,
125+
&schema,
105126
attributes,
106127
SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK,
107128
NULL,
@@ -117,6 +138,7 @@ static int keyring_get(struct credential *c)
117138
SecretItem *item;
118139
SecretValue *secret;
119140
const char *s;
141+
gchar **parts;
120142

121143
item = items->data;
122144
secret = secret_item_get_secret(item);
@@ -130,8 +152,27 @@ static int keyring_get(struct credential *c)
130152

131153
s = secret_value_get_text(secret);
132154
if (s) {
133-
g_free(c->password);
134-
c->password = g_strdup(s);
155+
/*
156+
* Passwords and other attributes encoded in following format:
157+
* hunter2
158+
* password_expiry_utc=1684189401
159+
* oauth_refresh_token=xyzzy
160+
*/
161+
parts = g_strsplit(s, "\n", 0);
162+
if (g_strv_length(parts) >= 1) {
163+
g_free(c->password);
164+
c->password = g_strdup(parts[0]);
165+
}
166+
for (int i = 1; i < g_strv_length(parts); i++) {
167+
if (g_str_has_prefix(parts[i], "password_expiry_utc=")) {
168+
g_free(c->password_expiry_utc);
169+
c->password_expiry_utc = g_strdup(&parts[i][20]);
170+
} else if (g_str_has_prefix(parts[i], "oauth_refresh_token=")) {
171+
g_free(c->oauth_refresh_token);
172+
c->oauth_refresh_token = g_strdup(&parts[i][20]);
173+
}
174+
}
175+
g_strfreev(parts);
135176
}
136177

137178
g_hash_table_unref(attributes);
@@ -148,6 +189,7 @@ static int keyring_store(struct credential *c)
148189
char *label = NULL;
149190
GHashTable *attributes = NULL;
150191
GError *error = NULL;
192+
GString *secret = NULL;
151193

152194
/*
153195
* Sanity check that what we are storing is actually sensible.
@@ -162,13 +204,23 @@ static int keyring_store(struct credential *c)
162204

163205
label = make_label(c);
164206
attributes = make_attr_list(c);
165-
secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK,
207+
secret = g_string_new(c->password);
208+
if (c->password_expiry_utc) {
209+
g_string_append_printf(secret, "\npassword_expiry_utc=%s",
210+
c->password_expiry_utc);
211+
}
212+
if (c->oauth_refresh_token) {
213+
g_string_append_printf(secret, "\noauth_refresh_token=%s",
214+
c->oauth_refresh_token);
215+
}
216+
secret_password_storev_sync(&schema,
166217
attributes,
167218
NULL,
168219
label,
169-
c->password,
220+
secret->str,
170221
NULL,
171222
&error);
223+
g_string_free(secret, TRUE);
172224
g_free(label);
173225
g_hash_table_unref(attributes);
174226

@@ -198,7 +250,7 @@ static int keyring_erase(struct credential *c)
198250
return EXIT_FAILURE;
199251

200252
attributes = make_attr_list(c);
201-
secret_password_clearv_sync(SECRET_SCHEMA_COMPAT_NETWORK,
253+
secret_password_clearv_sync(&schema,
202254
attributes,
203255
NULL,
204256
&error);
@@ -238,6 +290,8 @@ static void credential_clear(struct credential *c)
238290
g_free(c->path);
239291
g_free(c->username);
240292
g_free(c->password);
293+
g_free(c->password_expiry_utc);
294+
g_free(c->oauth_refresh_token);
241295

242296
credential_init(c);
243297
}
@@ -284,11 +338,19 @@ static int credential_read(struct credential *c)
284338
} else if (!strcmp(key, "username")) {
285339
g_free(c->username);
286340
c->username = g_strdup(value);
341+
} else if (!strcmp(key, "password_expiry_utc")) {
342+
g_free(c->password_expiry_utc);
343+
c->password_expiry_utc = g_strdup(value);
287344
} else if (!strcmp(key, "password")) {
288345
g_free(c->password);
289346
c->password = g_strdup(value);
290347
while (*value)
291348
*value++ = '\0';
349+
} else if (!strcmp(key, "oauth_refresh_token")) {
350+
g_free(c->oauth_refresh_token);
351+
c->oauth_refresh_token = g_strdup(value);
352+
while (*value)
353+
*value++ = '\0';
292354
}
293355
/*
294356
* Ignore other lines; we don't know what they mean, but
@@ -314,6 +376,10 @@ static void credential_write(const struct credential *c)
314376
/* only write username/password, if set */
315377
credential_write_item(stdout, "username", c->username);
316378
credential_write_item(stdout, "password", c->password);
379+
credential_write_item(stdout, "password_expiry_utc",
380+
c->password_expiry_utc);
381+
credential_write_item(stdout, "oauth_refresh_token",
382+
c->oauth_refresh_token);
317383
}
318384

319385
static void usage(const char *name)

t/lib-credential.sh

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ helper_test_clean() {
4343
reject $1 https example.com store-user
4444
reject $1 https example.com user1
4545
reject $1 https example.com user2
46+
reject $1 https example.com user-expiry
47+
reject $1 https example.com user-expiry-overwrite
4648
reject $1 https example.com user4
4749
reject $1 https example.com user-distinct-pass
4850
reject $1 https example.com user-overwrite
@@ -431,6 +433,81 @@ helper_test_timeout() {
431433
'
432434
}
433435

436+
helper_test_password_expiry_utc() {
437+
HELPER=$1
438+
439+
test_expect_success "helper ($HELPER) stores password_expiry_utc" '
440+
check approve $HELPER <<-\EOF
441+
protocol=https
442+
host=example.com
443+
username=user-expiry
444+
password=pass
445+
password_expiry_utc=9999999999
446+
EOF
447+
'
448+
449+
test_expect_success "helper ($HELPER) gets password_expiry_utc" '
450+
check fill $HELPER <<-\EOF
451+
protocol=https
452+
host=example.com
453+
username=user-expiry
454+
--
455+
protocol=https
456+
host=example.com
457+
username=user-expiry
458+
password=pass
459+
password_expiry_utc=9999999999
460+
--
461+
EOF
462+
'
463+
464+
test_expect_success "helper ($HELPER) overwrites when password_expiry_utc changes" '
465+
check approve $HELPER <<-\EOF &&
466+
protocol=https
467+
host=example.com
468+
username=user-expiry-overwrite
469+
password=pass1
470+
password_expiry_utc=9999999998
471+
EOF
472+
check approve $HELPER <<-\EOF &&
473+
protocol=https
474+
host=example.com
475+
username=user-expiry-overwrite
476+
password=pass2
477+
password_expiry_utc=9999999999
478+
EOF
479+
check fill $HELPER <<-\EOF &&
480+
protocol=https
481+
host=example.com
482+
username=user-expiry-overwrite
483+
--
484+
protocol=https
485+
host=example.com
486+
username=user-expiry-overwrite
487+
password=pass2
488+
password_expiry_utc=9999999999
489+
EOF
490+
check reject $HELPER <<-\EOF &&
491+
protocol=https
492+
host=example.com
493+
username=user-expiry-overwrite
494+
password=pass2
495+
EOF
496+
check fill $HELPER <<-\EOF
497+
protocol=https
498+
host=example.com
499+
username=user-expiry-overwrite
500+
--
501+
protocol=https
502+
host=example.com
503+
username=user-expiry-overwrite
504+
password=askpass-password
505+
--
506+
askpass: Password for '\''https://[email protected]'\'':
507+
EOF
508+
'
509+
}
510+
434511
helper_test_oauth_refresh_token() {
435512
HELPER=$1
436513

t/t0301-credential-cache.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ test_atexit 'git credential-cache exit'
2929

3030
# test that the daemon works with no special setup
3131
helper_test cache
32+
helper_test_password_expiry_utc cache
3233
helper_test_oauth_refresh_token cache
3334

3435
test_expect_success 'socket defaults to ~/.cache/git/credential/socket' '

t/t0303-credential-external.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" ||
4545
helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
4646

4747
helper_test "$GIT_TEST_CREDENTIAL_HELPER"
48+
helper_test_password_expiry_utc "$GIT_TEST_CREDENTIAL_HELPER"
49+
helper_test_oauth_refresh_token "$GIT_TEST_CREDENTIAL_HELPER"
4850

4951
if test -z "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"; then
5052
say "# skipping timeout tests (GIT_TEST_CREDENTIAL_HELPER_TIMEOUT not set)"

0 commit comments

Comments
 (0)