diff --git a/.gitignore b/.gitignore index a6b0bd4035..2b7a3f9876 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ /git-count-objects /git-credential-cache /git-credential-cache--daemon +/git-credential-store /git-cvsexportcommit /git-cvsimport /git-cvsserver diff --git a/Documentation/git-credential-store.txt b/Documentation/git-credential-store.txt new file mode 100644 index 0000000000..9fc0f764a1 --- /dev/null +++ b/Documentation/git-credential-store.txt @@ -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=:: + + Use `` 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 :: + + 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: +Password: + +[several days later] +$ git push http://example.com/repo.git +[your credentials are used automatically] +------------------------------------ + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt index bd1a3b66f3..33ea56cb7d 100644 --- a/Documentation/gitcredentials.txt +++ b/Documentation/gitcredentials.txt @@ -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, diff --git a/Makefile b/Makefile index 442e249b08..22e2afc946 100644 --- a/Makefile +++ b/Makefile @@ -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)) diff --git a/credential-store.c b/credential-store.c new file mode 100644 index 0000000000..8ab858215b --- /dev/null +++ b/credential-store.c @@ -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 "), + OPT_STRING_LIST(0, "chain", &chain, "helper", + "use 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; +} diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index 994a0aae91..5d5497619a 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -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