credentials: add "store" helper

This is like "cache", except that we actually put the
credentials on disk. This can be terribly insecure, of
course, but we do what we can to protect them by filesystem
permissions, and we warn the user in the documentation.

This is not unlike using .netrc to store entries, but it's a
little more user-friendly. Instead of putting credentials in
place ahead of time, we transparently store them after
prompting the user for them once.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Jeff King 2011-07-18 03:58:14 -04:00 committed by Junio C Hamano
parent 2d6874d83a
commit 3fc3ee7332
6 changed files with 218 additions and 0 deletions

1
.gitignore vendored
View file

@ -32,6 +32,7 @@
/git-count-objects
/git-credential-cache
/git-credential-cache--daemon
/git-credential-store
/git-cvsexportcommit
/git-cvsimport
/git-cvsserver

View file

@ -0,0 +1,69 @@
git-credential-store(1)
=======================
NAME
----
git-credential-store - helper to store credentials on disk
SYNOPSIS
--------
-------------------
git config credential.helper 'store [options]'
-------------------
DESCRIPTION
-----------
NOTE: Using this helper will store your passwords unencrypted on disk,
protected only by filesystem permissions. If this is not an acceptable
security tradeoff, try linkgit:git-credential-cache[1], or find a helper
that integrates with secure storage provided by your operating system.
This command requests credentials from the user and stores them
indefinitely on disk for use by future git programs.
You probably don't want to invoke this command directly; it is meant to
be used as a credential helper by other parts of git. See
linkgit:gitcredentials[7] or `EXAMPLES` below.
OPTIONS
-------
--store=<path>::
Use `<path>` to store credentials. The file will have its
filesystem permissions set to prevent other users on the system
from reading it, but will not be encrypted or otherwise
protected.
--chain <helper>::
Specify an external helper to use for retrieving credentials
from the user, instead of the default method. The resulting
credentials are then stored as normal. This option can be
given multiple times; each chained helper will be tried until
credentials are received.
Git may provide other options to the program when it is called as a
credential helper; see linkgit:gitcredentials[7].
EXAMPLES
--------
The point of this helper is to reduce the number of times you must type
your username or password. For example:
------------------------------------
$ git config credential.helper store
$ git push http://example.com/repo.git
Username: <type your username>
Password: <type your password>
[several days later]
$ git push http://example.com/repo.git
[your credentials are used automatically]
------------------------------------
GIT
---
Part of the linkgit:git[1] suite

View file

@ -88,6 +88,11 @@ cache::
Cache credentials in memory for a short period of time. See
linkgit:git-credential-cache[1] for details.
store::
Store credentials indefinitely on disk. See
linkgit:git-credential-store[1] for details.
You may may also have third-party helpers installed; search for
`credential-*` in the output of `git help -a`, and consult the
documentation of individual helpers. Once you have selected a helper,

View file

@ -422,6 +422,7 @@ PROGRAM_OBJS += http-backend.o
PROGRAM_OBJS += sh-i18n--envsubst.o
PROGRAM_OBJS += credential-cache.o
PROGRAM_OBJS += credential-cache--daemon.o
PROGRAM_OBJS += credential-store.o
PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))

87
credential-store.c Normal file
View file

@ -0,0 +1,87 @@
#include "cache.h"
#include "credential.h"
#include "string-list.h"
#include "parse-options.h"
static int lookup_credential(const char *fn, struct credential *c)
{
config_exclusive_filename = fn;
credential_from_config(c);
return c->username && c->password;
}
static void store_item(const char *fn, const char *unique,
const char *item, const char *value)
{
struct strbuf key = STRBUF_INIT;
if (!unique)
return;
config_exclusive_filename = fn;
umask(077);
strbuf_addf(&key, "credential.%s.%s", unique, item);
git_config_set(key.buf, value);
strbuf_release(&key);
}
static void store_credential(const char *fn, struct credential *c)
{
store_item(fn, c->unique, "username", c->username);
store_item(fn, c->unique, "password", c->password);
}
static void remove_credential(const char *fn, struct credential *c)
{
store_item(fn, c->unique, "username", NULL);
store_item(fn, c->unique, "password", NULL);
}
int main(int argc, const char **argv)
{
const char * const usage[] = {
"git credential-store [options]",
NULL
};
struct credential c = { NULL };
struct string_list chain = STRING_LIST_INIT_NODUP;
char *store = NULL;
int reject = 0;
struct option options[] = {
OPT_STRING_LIST(0, "store", &store, "file",
"fetch and store credentials in <file>"),
OPT_STRING_LIST(0, "chain", &chain, "helper",
"use <helper> to get non-cached credentials"),
OPT_BOOLEAN(0, "reject", &reject,
"reject a stored credential"),
OPT_STRING(0, "username", &c.username, "name",
"an existing username"),
OPT_STRING(0, "description", &c.description, "desc",
"human-readable description of the credential"),
OPT_STRING(0, "unique", &c.unique, "token",
"a unique context for the credential"),
OPT_END()
};
argc = parse_options(argc, argv, NULL, options, usage, 0);
if (argc)
usage_with_options(usage, options);
if (!store)
store = expand_user_path("~/.git-credentials");
if (!store)
die("unable to set up default store; use --store");
if (reject)
remove_credential(store, &c);
else {
if (!lookup_credential(store, &c)) {
credential_fill(&c, &chain);
store_credential(store, &c);
}
printf("username=%s\n", c.username);
printf("password=%s\n", c.password);
}
return 0;
}

View file

@ -273,4 +273,59 @@ test_expect_success 'credential-cache removes rejected credentials' '
EOF
'
test_expect_success 'credential-store stores password' '
test_when_finished "rm -f .git-credentials" &&
check --unique=host store <<-\EOF &&
username=askpass-result
password=askpass-result
--
askpass: Username:
askpass: Password:
EOF
check --unique=host store <<-\EOF
username=askpass-result
password=askpass-result
--
EOF
'
test_expect_success 'credential-store requires matching unique token' '
test_when_finished "rm -f .git-credentials" &&
check --unique=host store <<-\EOF &&
username=askpass-result
password=askpass-result
--
askpass: Username:
askpass: Password:
EOF
check --unique=host2 store <<-\EOF
username=askpass-result
password=askpass-result
--
askpass: Username:
askpass: Password:
EOF
'
test_expect_success 'credential-store removes rejected credentials' '
test_when_finished "rm -f .git-credentials" &&
check --unique=host store <<-\EOF &&
username=askpass-result
password=askpass-result
--
askpass: Username:
askpass: Password:
EOF
check --reject --unique=host --username=askpass-result store <<-\EOF &&
--
EOF
check --unique=host store <<-\EOF
username=askpass-result
password=askpass-result
--
askpass: Username:
askpass: Password:
EOF
'
test_done