Files
git/credential.c
Jeff King 59f5226028 introduce credentials API
There are a few places in git that need to get a username
and password credential from the user; the most notable one
is HTTP authentication for smart-http pushing.

Right now the only choices for providing credentials are to
put them plaintext into your ~/.netrc, or to have git prompt
you (either on the terminal or via an askpass program). The
former is not very secure, and the latter is not very
convenient.

Unfortunately, there is no "always best" solution for
password management. The details will depend on the tradeoff
you want between security and convenience, as well as how
git can integrate with other security systems (e.g., many
operating systems provide a keychain or password wallet for
single sign-on).

This patch abstracts the notion of gathering user
credentials into a few simple functions. These functions can
be backed by our internal git_getpass implementation (which
just prompts the user), or by external helpers which are
free to consult system-specific password wallets, make
custom policy decisions on password caching and storage, or
prompt the user in a non-traditional manner.

The helper protocol aims for simplicity of helper
implementation; see the newly added documentation for
details.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-03 15:25:11 -07:00

191 lines
4.1 KiB
C

#include "cache.h"
#include "credential.h"
#include "quote.h"
#include "string-list.h"
#include "run-command.h"
static char *credential_ask_one(const char *what, const char *desc)
{
struct strbuf prompt = STRBUF_INIT;
char *r;
if (desc)
strbuf_addf(&prompt, "%s for '%s': ", what, desc);
else
strbuf_addf(&prompt, "%s: ", what);
/* FIXME: for usernames, we should do something less magical that
* actually echoes the characters. However, we need to read from
* /dev/tty and not stdio, which is not portable (but getpass will do
* it for us). http.c uses the same workaround. */
r = git_getpass(prompt.buf);
strbuf_release(&prompt);
return xstrdup(r);
}
int credential_getpass(struct credential *c)
{
if (!c->username)
c->username = credential_ask_one("Username", c->description);
if (!c->password)
c->password = credential_ask_one("Password", c->description);
return 0;
}
static int read_credential_response(struct credential *c, FILE *fp)
{
struct strbuf response = STRBUF_INIT;
while (strbuf_getline(&response, fp, '\n') != EOF) {
char *key = response.buf;
char *value = strchr(key, '=');
if (!value) {
warning("bad output from credential helper: %s", key);
strbuf_release(&response);
return -1;
}
*value++ = '\0';
if (!strcmp(key, "username")) {
free(c->username);
c->username = xstrdup(value);
}
else if (!strcmp(key, "password")) {
free(c->password);
c->password = xstrdup(value);
}
/* ignore other responses; we don't know what they mean */
}
strbuf_release(&response);
return 0;
}
static int run_credential_helper(struct credential *c, const char *cmd)
{
struct child_process helper;
const char *argv[] = { NULL, NULL };
FILE *fp;
int r;
memset(&helper, 0, sizeof(helper));
argv[0] = cmd;
helper.argv = argv;
helper.use_shell = 1;
helper.no_stdin = 1;
helper.out = -1;
if (start_command(&helper))
return -1;
fp = xfdopen(helper.out, "r");
r = read_credential_response(c, fp);
fclose(fp);
if (finish_command(&helper))
r = -1;
return r;
}
static void add_item(struct strbuf *out, const char *key, const char *value)
{
if (!value)
return;
strbuf_addf(out, " --%s=", key);
sq_quote_buf(out, value);
}
static int first_word_is_alnum(const char *s)
{
for (; *s && *s != ' '; s++)
if (!isalnum(*s))
return 0;
return 1;
}
static int credential_do(struct credential *c, const char *method,
const char *extra)
{
struct strbuf cmd = STRBUF_INIT;
int r;
if (first_word_is_alnum(method))
strbuf_addf(&cmd, "git credential-%s", method);
else
strbuf_addstr(&cmd, method);
if (extra)
strbuf_addf(&cmd, " %s", extra);
add_item(&cmd, "description", c->description);
add_item(&cmd, "unique", c->unique);
add_item(&cmd, "username", c->username);
r = run_credential_helper(c, cmd.buf);
strbuf_release(&cmd);
return r;
}
void credential_fill(struct credential *c, const struct string_list *methods)
{
struct strbuf err = STRBUF_INIT;
if (!credential_fill_gently(c, methods))
return;
strbuf_addstr(&err, "unable to get credentials");
if (c->description)
strbuf_addf(&err, "for '%s'", c->description);
if (methods && methods->nr == 1)
strbuf_addf(&err, "; tried '%s'", methods->items[0].string);
else if (methods) {
int i;
strbuf_addstr(&err, "; tried:");
for (i = 0; i < methods->nr; i++)
strbuf_addf(&err, "\n %s", methods->items[i].string);
}
die("%s", err.buf);
}
int credential_fill_gently(struct credential *c,
const struct string_list *methods)
{
int i;
if (c->username && c->password)
return 0;
if (!methods || !methods->nr)
return credential_getpass(c);
for (i = 0; i < methods->nr; i++) {
if (!credential_do(c, methods->items[i].string, NULL) &&
c->username && c->password)
return 0;
}
return -1;
}
void credential_reject(struct credential *c, const struct string_list *methods)
{
int i;
if (methods && c->username) {
for (i = 0; i < methods->nr; i++) {
/* ignore errors, there's nothing we can do */
credential_do(c, methods->items[i].string, "--reject");
}
}
free(c->username);
c->username = NULL;
free(c->password);
c->password = NULL;
}