2011-12-10 10:31:11 +00:00
|
|
|
#include "cache.h"
|
2017-06-14 18:07:36 +00:00
|
|
|
#include "config.h"
|
2011-12-10 10:31:11 +00:00
|
|
|
#include "credential.h"
|
|
|
|
#include "string-list.h"
|
|
|
|
#include "run-command.h"
|
2011-12-10 10:31:17 +00:00
|
|
|
#include "url.h"
|
2011-12-10 10:40:54 +00:00
|
|
|
#include "prompt.h"
|
2018-03-29 18:00:56 +00:00
|
|
|
#include "sigchain.h"
|
credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:13 +00:00
|
|
|
#include "urlmatch.h"
|
credential: new attribute password_expiry_utc
Some passwords have an expiry date known at generation. This may be
years away for a personal access token or hours for an OAuth access
token.
When multiple credential helpers are configured, `credential fill` tries
each helper in turn until it has a username and password, returning
early. If Git authentication succeeds, `credential approve`
stores the successful credential in all helpers. If authentication
fails, `credential reject` erases matching credentials in all helpers.
Helpers implement corresponding operations: get, store, erase.
The credential protocol has no expiry attribute, so helpers cannot
store expiry information. Even if a helper returned an improvised
expiry attribute, git credential discards unrecognised attributes
between operations and between helpers.
This is a particular issue when a storage helper and a
credential-generating helper are configured together:
[credential]
helper = storage # eg. cache or osxkeychain
helper = generate # eg. oauth
`credential approve` stores the generated credential in both helpers
without expiry information. Later `credential fill` may return an
expired credential from storage. There is no workaround, no matter how
clever the second helper. The user sees authentication fail (a retry
will succeed).
Introduce a password expiry attribute. In `credential fill`, ignore
expired passwords and continue to query subsequent helpers.
In the example above, `credential fill` ignores the expired password
and a fresh credential is generated. If authentication succeeds,
`credential approve` replaces the expired password in storage.
If authentication fails, the expired credential is erased by
`credential reject`. It is unnecessary but harmless for storage
helpers to self prune expired credentials.
Add support for the new attribute to credential-cache.
Eventually, I hope to see support in other popular storage helpers.
Example usage in a credential-generating helper
https://github.com/hickford/git-credential-oauth/pull/16
Signed-off-by: M Hickford <mirth.hickford@gmail.com>
Reviewed-by: Calvin Wan <calvinwan@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-02-18 06:32:57 +00:00
|
|
|
#include "git-compat-util.h"
|
2011-12-10 10:31:11 +00:00
|
|
|
|
|
|
|
void credential_init(struct credential *c)
|
|
|
|
{
|
2021-07-01 10:51:26 +00:00
|
|
|
struct credential blank = CREDENTIAL_INIT;
|
|
|
|
memcpy(c, &blank, sizeof(*c));
|
2011-12-10 10:31:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void credential_clear(struct credential *c)
|
|
|
|
{
|
|
|
|
free(c->protocol);
|
|
|
|
free(c->host);
|
|
|
|
free(c->path);
|
|
|
|
free(c->username);
|
|
|
|
free(c->password);
|
|
|
|
string_list_clear(&c->helpers, 0);
|
|
|
|
|
|
|
|
credential_init(c);
|
|
|
|
}
|
|
|
|
|
2011-12-10 10:31:24 +00:00
|
|
|
int credential_match(const struct credential *want,
|
|
|
|
const struct credential *have)
|
|
|
|
{
|
|
|
|
#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x)))
|
|
|
|
return CHECK(protocol) &&
|
|
|
|
CHECK(host) &&
|
|
|
|
CHECK(path) &&
|
|
|
|
CHECK(username);
|
|
|
|
#undef CHECK
|
|
|
|
}
|
|
|
|
|
2020-04-24 11:49:52 +00:00
|
|
|
|
|
|
|
static int credential_from_potentially_partial_url(struct credential *c,
|
|
|
|
const char *url);
|
|
|
|
|
2011-12-10 10:31:24 +00:00
|
|
|
static int credential_config_callback(const char *var, const char *value,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct credential *c = data;
|
credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:13 +00:00
|
|
|
const char *key;
|
2011-12-10 10:31:24 +00:00
|
|
|
|
refactor skip_prefix to return a boolean
The skip_prefix() function returns a pointer to the content
past the prefix, or NULL if the prefix was not found. While
this is nice and simple, in practice it makes it hard to use
for two reasons:
1. When you want to conditionally skip or keep the string
as-is, you have to introduce a temporary variable.
For example:
tmp = skip_prefix(buf, "foo");
if (tmp)
buf = tmp;
2. It is verbose to check the outcome in a conditional, as
you need extra parentheses to silence compiler
warnings. For example:
if ((cp = skip_prefix(buf, "foo"))
/* do something with cp */
Both of these make it harder to use for long if-chains, and
we tend to use starts_with() instead. However, the first line
of "do something" is often to then skip forward in buf past
the prefix, either using a magic constant or with an extra
strlen(3) (which is generally computed at compile time, but
means we are repeating ourselves).
This patch refactors skip_prefix() to return a simple boolean,
and to provide the pointer value as an out-parameter. If the
prefix is not found, the out-parameter is untouched. This
lets you write:
if (skip_prefix(arg, "foo ", &arg))
do_foo(arg);
else if (skip_prefix(arg, "bar ", &arg))
do_bar(arg);
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-06-18 19:44:19 +00:00
|
|
|
if (!skip_prefix(var, "credential.", &key))
|
2011-12-10 10:31:24 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!value)
|
|
|
|
return config_error_nonbool(var);
|
|
|
|
|
2016-02-26 10:51:35 +00:00
|
|
|
if (!strcmp(key, "helper")) {
|
|
|
|
if (*value)
|
|
|
|
string_list_append(&c->helpers, value);
|
|
|
|
else
|
|
|
|
string_list_clear(&c->helpers, 0);
|
|
|
|
} else if (!strcmp(key, "username")) {
|
credential: use the last matching username in the config
Everywhere else in the codebase, we use the rule that the last matching
configuration option is the one that takes effect. This is helpful
because it allows more specific configuration settings (e.g., per-repo
configuration) to override less specific settings (e.g., per-user
configuration).
However, in the credential code, we didn't honor this setting, and
instead picked the first setting we had, and stuck with it. This was
likely to ensure we picked the value from the URL, which we want to
honor over the configuration.
It's possible to do both, though, so let's check if the value is the one
we've gotten over our protocol connection, which if present will have
come from the URL, and keep it if so. Otherwise, let's overwrite the
value with the latest version we've got from the configuration, so we
keep the last configuration value.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:12 +00:00
|
|
|
if (!c->username_from_proto) {
|
|
|
|
free(c->username);
|
2011-12-10 10:31:30 +00:00
|
|
|
c->username = xstrdup(value);
|
credential: use the last matching username in the config
Everywhere else in the codebase, we use the rule that the last matching
configuration option is the one that takes effect. This is helpful
because it allows more specific configuration settings (e.g., per-repo
configuration) to override less specific settings (e.g., per-user
configuration).
However, in the credential code, we didn't honor this setting, and
instead picked the first setting we had, and stuck with it. This was
likely to ensure we picked the value from the URL, which we want to
honor over the configuration.
It's possible to do both, though, so let's check if the value is the one
we've gotten over our protocol connection, which if present will have
come from the URL, and keep it if so. Otherwise, let's overwrite the
value with the latest version we've got from the configuration, so we
keep the last configuration value.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:12 +00:00
|
|
|
}
|
2011-12-10 10:31:30 +00:00
|
|
|
}
|
credential: make relevance of http path configurable
When parsing a URL into a credential struct, we carefully
record each part of the URL, including the path on the
remote host, and use the result as part of the credential
context.
This had two practical implications:
1. Credential helpers which store a credential for later
access are likely to use the "path" portion as part of
the storage key. That means that a request to
https://example.com/foo.git
would not use the same credential that was stored in an
earlier request for:
https://example.com/bar.git
2. The prompt shown to the user includes all relevant
context, including the path.
In most cases, however, users will have a single password
per host. The behavior in (1) will be inconvenient, and the
prompt in (2) will be overly long.
This patch introduces a config option to toggle the
relevance of http paths. When turned on, we use the path as
before. When turned off, we drop the path component from the
context: helpers don't see it, and it does not appear in the
prompt.
This is nothing you couldn't do with a clever credential
helper at the start of your stack, like:
[credential "http://"]
helper = "!f() { grep -v ^path= ; }; f"
helper = your_real_helper
But doing this:
[credential]
useHttpPath = false
is way easier and more readable. Furthermore, since most
users will want the "off" behavior, that is the new default.
Users who want it "on" can set the variable (either for all
credentials, or just for a subset using
credential.*.useHttpPath).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-12-10 10:31:34 +00:00
|
|
|
else if (!strcmp(key, "usehttppath"))
|
|
|
|
c->use_http_path = git_config_bool(var, value);
|
2011-12-10 10:31:24 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
credential: make relevance of http path configurable
When parsing a URL into a credential struct, we carefully
record each part of the URL, including the path on the
remote host, and use the result as part of the credential
context.
This had two practical implications:
1. Credential helpers which store a credential for later
access are likely to use the "path" portion as part of
the storage key. That means that a request to
https://example.com/foo.git
would not use the same credential that was stored in an
earlier request for:
https://example.com/bar.git
2. The prompt shown to the user includes all relevant
context, including the path.
In most cases, however, users will have a single password
per host. The behavior in (1) will be inconvenient, and the
prompt in (2) will be overly long.
This patch introduces a config option to toggle the
relevance of http paths. When turned on, we use the path as
before. When turned off, we drop the path component from the
context: helpers don't see it, and it does not appear in the
prompt.
This is nothing you couldn't do with a clever credential
helper at the start of your stack, like:
[credential "http://"]
helper = "!f() { grep -v ^path= ; }; f"
helper = your_real_helper
But doing this:
[credential]
useHttpPath = false
is way easier and more readable. Furthermore, since most
users will want the "off" behavior, that is the new default.
Users who want it "on" can set the variable (either for all
credentials, or just for a subset using
credential.*.useHttpPath).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-12-10 10:31:34 +00:00
|
|
|
static int proto_is_http(const char *s)
|
|
|
|
{
|
|
|
|
if (!s)
|
|
|
|
return 0;
|
|
|
|
return !strcmp(s, "https") || !strcmp(s, "http");
|
|
|
|
}
|
|
|
|
|
credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:13 +00:00
|
|
|
static void credential_describe(struct credential *c, struct strbuf *out);
|
|
|
|
static void credential_format(struct credential *c, struct strbuf *out);
|
|
|
|
|
|
|
|
static int select_all(const struct urlmatch_item *a,
|
|
|
|
const struct urlmatch_item *b)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-04-24 22:35:49 +00:00
|
|
|
static int match_partial_url(const char *url, void *cb)
|
|
|
|
{
|
|
|
|
struct credential *c = cb;
|
|
|
|
struct credential want = CREDENTIAL_INIT;
|
|
|
|
int matches = 0;
|
|
|
|
|
|
|
|
if (credential_from_potentially_partial_url(&want, url) < 0)
|
|
|
|
warning(_("skipping credential lookup for key: credential.%s"),
|
|
|
|
url);
|
|
|
|
else
|
|
|
|
matches = credential_match(&want, c);
|
|
|
|
credential_clear(&want);
|
|
|
|
|
|
|
|
return matches;
|
|
|
|
}
|
|
|
|
|
2011-12-10 10:31:24 +00:00
|
|
|
static void credential_apply_config(struct credential *c)
|
|
|
|
{
|
credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:13 +00:00
|
|
|
char *normalized_url;
|
2021-10-01 10:27:33 +00:00
|
|
|
struct urlmatch_config config = URLMATCH_CONFIG_INIT;
|
credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:13 +00:00
|
|
|
struct strbuf url = STRBUF_INIT;
|
|
|
|
|
credential: refuse to operate when missing host or protocol
The credential helper protocol was designed to be very flexible: the
fields it takes as input are treated as a pattern, and any missing
fields are taken as wildcards. This allows unusual things like:
echo protocol=https | git credential reject
to delete all stored https credentials (assuming the helpers themselves
treat the input that way). But when helpers are invoked automatically by
Git, this flexibility works against us. If for whatever reason we don't
have a "host" field, then we'd match _any_ host. When you're filling a
credential to send to a remote server, this is almost certainly not what
you want.
Prevent this at the layer that writes to the credential helper. Add a
check to the credential API that the host and protocol are always passed
in, and add an assertion to the credential_write function that speaks
credential helper protocol to be doubly sure.
There are a few ways this can be triggered in practice:
- the "git credential" command passes along arbitrary credential
parameters it reads from stdin.
- until the previous patch, when the host field of a URL is empty, we
would leave it unset (rather than setting it to the empty string)
- a URL like "example.com/foo.git" is treated by curl as if "http://"
was present, but our parser sees it as a non-URL and leaves all
fields unset
- the recent fix for URLs with embedded newlines blanks the URL but
otherwise continues. Rather than having the desired effect of
looking up no credential at all, many helpers will return _any_
credential
Our earlier test for an embedded newline didn't catch this because it
only checked that the credential was cleared, but didn't configure an
actual helper. Configuring the "verbatim" helper in the test would show
that it is invoked (it's obviously a silly helper which doesn't look at
its input, but the point is that it shouldn't be run at all). Since
we're switching this case to die(), we don't need to bother with a
helper. We can see the new behavior just by checking that the operation
fails.
We'll add new tests covering partial input as well (these can be
triggered through various means with url-parsing, but it's simpler to
just check them directly, as we know we are covered even if the url
parser changes behavior in the future).
[jn: changed to die() instead of logging and showing a manual
username/password prompt]
Reported-by: Carlo Arenas <carenas@gmail.com>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
2020-04-19 03:50:48 +00:00
|
|
|
if (!c->host)
|
|
|
|
die(_("refusing to work with credential missing host field"));
|
|
|
|
if (!c->protocol)
|
|
|
|
die(_("refusing to work with credential missing protocol field"));
|
|
|
|
|
2011-12-10 10:31:24 +00:00
|
|
|
if (c->configured)
|
|
|
|
return;
|
credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:13 +00:00
|
|
|
|
|
|
|
config.section = "credential";
|
|
|
|
config.key = NULL;
|
|
|
|
config.collect_fn = credential_config_callback;
|
|
|
|
config.cascade_fn = NULL;
|
|
|
|
config.select_fn = select_all;
|
2020-04-24 22:35:49 +00:00
|
|
|
config.fallback_match_fn = match_partial_url;
|
credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:13 +00:00
|
|
|
config.cb = c;
|
|
|
|
|
|
|
|
credential_format(c, &url);
|
|
|
|
normalized_url = url_normalize(url.buf, &config.url);
|
|
|
|
|
|
|
|
git_config(urlmatch_config_entry, &config);
|
2021-08-20 08:44:13 +00:00
|
|
|
string_list_clear(&config.vars, 1);
|
credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:13 +00:00
|
|
|
free(normalized_url);
|
2022-03-04 18:32:07 +00:00
|
|
|
urlmatch_config_release(&config);
|
credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:13 +00:00
|
|
|
strbuf_release(&url);
|
|
|
|
|
2011-12-10 10:31:24 +00:00
|
|
|
c->configured = 1;
|
credential: make relevance of http path configurable
When parsing a URL into a credential struct, we carefully
record each part of the URL, including the path on the
remote host, and use the result as part of the credential
context.
This had two practical implications:
1. Credential helpers which store a credential for later
access are likely to use the "path" portion as part of
the storage key. That means that a request to
https://example.com/foo.git
would not use the same credential that was stored in an
earlier request for:
https://example.com/bar.git
2. The prompt shown to the user includes all relevant
context, including the path.
In most cases, however, users will have a single password
per host. The behavior in (1) will be inconvenient, and the
prompt in (2) will be overly long.
This patch introduces a config option to toggle the
relevance of http paths. When turned on, we use the path as
before. When turned off, we drop the path component from the
context: helpers don't see it, and it does not appear in the
prompt.
This is nothing you couldn't do with a clever credential
helper at the start of your stack, like:
[credential "http://"]
helper = "!f() { grep -v ^path= ; }; f"
helper = your_real_helper
But doing this:
[credential]
useHttpPath = false
is way easier and more readable. Furthermore, since most
users will want the "off" behavior, that is the new default.
Users who want it "on" can set the variable (either for all
credentials, or just for a subset using
credential.*.useHttpPath).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-12-10 10:31:34 +00:00
|
|
|
|
|
|
|
if (!c->use_http_path && proto_is_http(c->protocol)) {
|
2017-06-15 23:15:46 +00:00
|
|
|
FREE_AND_NULL(c->path);
|
credential: make relevance of http path configurable
When parsing a URL into a credential struct, we carefully
record each part of the URL, including the path on the
remote host, and use the result as part of the credential
context.
This had two practical implications:
1. Credential helpers which store a credential for later
access are likely to use the "path" portion as part of
the storage key. That means that a request to
https://example.com/foo.git
would not use the same credential that was stored in an
earlier request for:
https://example.com/bar.git
2. The prompt shown to the user includes all relevant
context, including the path.
In most cases, however, users will have a single password
per host. The behavior in (1) will be inconvenient, and the
prompt in (2) will be overly long.
This patch introduces a config option to toggle the
relevance of http paths. When turned on, we use the path as
before. When turned off, we drop the path component from the
context: helpers don't see it, and it does not appear in the
prompt.
This is nothing you couldn't do with a clever credential
helper at the start of your stack, like:
[credential "http://"]
helper = "!f() { grep -v ^path= ; }; f"
helper = your_real_helper
But doing this:
[credential]
useHttpPath = false
is way easier and more readable. Furthermore, since most
users will want the "off" behavior, that is the new default.
Users who want it "on" can set the variable (either for all
credentials, or just for a subset using
credential.*.useHttpPath).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-12-10 10:31:34 +00:00
|
|
|
}
|
2011-12-10 10:31:24 +00:00
|
|
|
}
|
|
|
|
|
2011-12-10 10:31:11 +00:00
|
|
|
static void credential_describe(struct credential *c, struct strbuf *out)
|
|
|
|
{
|
|
|
|
if (!c->protocol)
|
|
|
|
return;
|
|
|
|
strbuf_addf(out, "%s://", c->protocol);
|
|
|
|
if (c->username && *c->username)
|
|
|
|
strbuf_addf(out, "%s@", c->username);
|
|
|
|
if (c->host)
|
|
|
|
strbuf_addstr(out, c->host);
|
|
|
|
if (c->path)
|
|
|
|
strbuf_addf(out, "/%s", c->path);
|
|
|
|
}
|
|
|
|
|
credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:13 +00:00
|
|
|
static void credential_format(struct credential *c, struct strbuf *out)
|
|
|
|
{
|
|
|
|
if (!c->protocol)
|
|
|
|
return;
|
|
|
|
strbuf_addf(out, "%s://", c->protocol);
|
|
|
|
if (c->username && *c->username) {
|
credential: fix matching URLs with multiple levels in path
46fd7b3900 ("credential: allow wildcard patterns when matching config",
2020-02-20) introduced support for matching credential helpers using
urlmatch. In doing so, it introduced code to percent-encode the paths
we get from the credential helper so that they could be effectively
matched by the urlmatch code.
Unfortunately, that code had a bug: it percent-encoded the slashes in
the path, resulting in any URL path that contained multiple levels
(i.e., a directory component) not matching.
We are currently the only caller of the percent-encoding code and could
simply change it not to encode slashes. However, we still want to
encode slashes in the username component, so we need to have both
behaviors available.
So instead, let's add a flag to control encoding slashes, which is the
behavior we want here, and use it when calling the code in this case.
Add a test for credential helper URLs using multiple slashes in the
path, which our test suite previously lacked, as well as one ensuring
that we handle usernames with slashes gracefully. Since we're testing
other percent-encoding handling, let's add one for non-ASCII UTF-8
characters as well.
Reported-by: Ilya Tretyakov <it@it3xl.ru>
Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-27 01:18:08 +00:00
|
|
|
strbuf_add_percentencode(out, c->username, STRBUF_ENCODE_SLASH);
|
credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:13 +00:00
|
|
|
strbuf_addch(out, '@');
|
|
|
|
}
|
|
|
|
if (c->host)
|
|
|
|
strbuf_addstr(out, c->host);
|
|
|
|
if (c->path) {
|
|
|
|
strbuf_addch(out, '/');
|
credential: fix matching URLs with multiple levels in path
46fd7b3900 ("credential: allow wildcard patterns when matching config",
2020-02-20) introduced support for matching credential helpers using
urlmatch. In doing so, it introduced code to percent-encode the paths
we get from the credential helper so that they could be effectively
matched by the urlmatch code.
Unfortunately, that code had a bug: it percent-encoded the slashes in
the path, resulting in any URL path that contained multiple levels
(i.e., a directory component) not matching.
We are currently the only caller of the percent-encoding code and could
simply change it not to encode slashes. However, we still want to
encode slashes in the username component, so we need to have both
behaviors available.
So instead, let's add a flag to control encoding slashes, which is the
behavior we want here, and use it when calling the code in this case.
Add a test for credential helper URLs using multiple slashes in the
path, which our test suite previously lacked, as well as one ensuring
that we handle usernames with slashes gracefully. Since we're testing
other percent-encoding handling, let's add one for non-ASCII UTF-8
characters as well.
Reported-by: Ilya Tretyakov <it@it3xl.ru>
Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-27 01:18:08 +00:00
|
|
|
strbuf_add_percentencode(out, c->path, 0);
|
credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-10 10:41:23 +00:00
|
|
|
static char *credential_ask_one(const char *what, struct credential *c,
|
|
|
|
int flags)
|
2011-12-10 10:31:11 +00:00
|
|
|
{
|
|
|
|
struct strbuf desc = STRBUF_INIT;
|
|
|
|
struct strbuf prompt = STRBUF_INIT;
|
|
|
|
char *r;
|
|
|
|
|
|
|
|
credential_describe(c, &desc);
|
|
|
|
if (desc.len)
|
|
|
|
strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
|
|
|
|
else
|
|
|
|
strbuf_addf(&prompt, "%s: ", what);
|
|
|
|
|
2011-12-10 10:41:23 +00:00
|
|
|
r = git_prompt(prompt.buf, flags);
|
2011-12-10 10:31:11 +00:00
|
|
|
|
|
|
|
strbuf_release(&desc);
|
|
|
|
strbuf_release(&prompt);
|
|
|
|
return xstrdup(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void credential_getpass(struct credential *c)
|
|
|
|
{
|
|
|
|
if (!c->username)
|
2011-12-10 10:41:23 +00:00
|
|
|
c->username = credential_ask_one("Username", c,
|
|
|
|
PROMPT_ASKPASS|PROMPT_ECHO);
|
2011-12-10 10:31:11 +00:00
|
|
|
if (!c->password)
|
2011-12-10 10:41:23 +00:00
|
|
|
c->password = credential_ask_one("Password", c,
|
|
|
|
PROMPT_ASKPASS);
|
2011-12-10 10:31:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int credential_read(struct credential *c, FILE *fp)
|
|
|
|
{
|
|
|
|
struct strbuf line = STRBUF_INIT;
|
|
|
|
|
credential: treat CR/LF as line endings in the credential protocol
This fix makes using Git credentials more friendly to Windows users: it
allows a credential helper to communicate using CR/LF line endings ("DOS
line endings" commonly found on Windows) instead of LF-only line endings
("Unix line endings").
Note that this changes the behavior a bit: if a credential helper
produces, say, a password with a trailing Carriage Return character,
that will now be culled even when the rest of the lines end only in Line
Feed characters, indicating that the Carriage Return was not meant to be
part of the line ending.
In practice, it seems _very_ unlikely that something like this happens.
Passwords usually need to consist of non-control characters, URLs need
to have special characters URL-encoded, and user names, well, are names.
However, it _does_ help on Windows, where CR/LF line endings are common:
as unrecognized commands are simply ignored by the credential machinery,
even a command like `quit\r` (which is clearly intended to abort) would
simply be ignored (silently) by Git.
So let's change the credential machinery to accept both CR/LF and LF
line endings.
While we do this for the credential helper protocol, we do _not_ adjust
`git credential-cache--daemon` (which won't work on Windows, anyway,
because it requires Unix sockets) nor `git credential-store` (which
writes the file `~/.git-credentials` which we consider an implementation
detail that should be opaque to the user, read: we do expect users _not_
to edit this file manually).
Signed-off-by: Nikita Leonov <nykyta.leonov@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-10-03 13:29:12 +00:00
|
|
|
while (strbuf_getline(&line, fp) != EOF) {
|
2011-12-10 10:31:11 +00:00
|
|
|
char *key = line.buf;
|
|
|
|
char *value = strchr(key, '=');
|
|
|
|
|
|
|
|
if (!line.len)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (!value) {
|
|
|
|
warning("invalid credential line: %s", key);
|
|
|
|
strbuf_release(&line);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
*value++ = '\0';
|
|
|
|
|
|
|
|
if (!strcmp(key, "username")) {
|
|
|
|
free(c->username);
|
|
|
|
c->username = xstrdup(value);
|
credential: use the last matching username in the config
Everywhere else in the codebase, we use the rule that the last matching
configuration option is the one that takes effect. This is helpful
because it allows more specific configuration settings (e.g., per-repo
configuration) to override less specific settings (e.g., per-user
configuration).
However, in the credential code, we didn't honor this setting, and
instead picked the first setting we had, and stuck with it. This was
likely to ensure we picked the value from the URL, which we want to
honor over the configuration.
It's possible to do both, though, so let's check if the value is the one
we've gotten over our protocol connection, which if present will have
come from the URL, and keep it if so. Otherwise, let's overwrite the
value with the latest version we've got from the configuration, so we
keep the last configuration value.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:12 +00:00
|
|
|
c->username_from_proto = 1;
|
2011-12-10 10:31:11 +00:00
|
|
|
} else if (!strcmp(key, "password")) {
|
|
|
|
free(c->password);
|
|
|
|
c->password = xstrdup(value);
|
|
|
|
} else if (!strcmp(key, "protocol")) {
|
|
|
|
free(c->protocol);
|
|
|
|
c->protocol = xstrdup(value);
|
|
|
|
} else if (!strcmp(key, "host")) {
|
|
|
|
free(c->host);
|
|
|
|
c->host = xstrdup(value);
|
|
|
|
} else if (!strcmp(key, "path")) {
|
|
|
|
free(c->path);
|
|
|
|
c->path = xstrdup(value);
|
credential: new attribute password_expiry_utc
Some passwords have an expiry date known at generation. This may be
years away for a personal access token or hours for an OAuth access
token.
When multiple credential helpers are configured, `credential fill` tries
each helper in turn until it has a username and password, returning
early. If Git authentication succeeds, `credential approve`
stores the successful credential in all helpers. If authentication
fails, `credential reject` erases matching credentials in all helpers.
Helpers implement corresponding operations: get, store, erase.
The credential protocol has no expiry attribute, so helpers cannot
store expiry information. Even if a helper returned an improvised
expiry attribute, git credential discards unrecognised attributes
between operations and between helpers.
This is a particular issue when a storage helper and a
credential-generating helper are configured together:
[credential]
helper = storage # eg. cache or osxkeychain
helper = generate # eg. oauth
`credential approve` stores the generated credential in both helpers
without expiry information. Later `credential fill` may return an
expired credential from storage. There is no workaround, no matter how
clever the second helper. The user sees authentication fail (a retry
will succeed).
Introduce a password expiry attribute. In `credential fill`, ignore
expired passwords and continue to query subsequent helpers.
In the example above, `credential fill` ignores the expired password
and a fresh credential is generated. If authentication succeeds,
`credential approve` replaces the expired password in storage.
If authentication fails, the expired credential is erased by
`credential reject`. It is unnecessary but harmless for storage
helpers to self prune expired credentials.
Add support for the new attribute to credential-cache.
Eventually, I hope to see support in other popular storage helpers.
Example usage in a credential-generating helper
https://github.com/hickford/git-credential-oauth/pull/16
Signed-off-by: M Hickford <mirth.hickford@gmail.com>
Reviewed-by: Calvin Wan <calvinwan@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-02-18 06:32:57 +00:00
|
|
|
} else if (!strcmp(key, "password_expiry_utc")) {
|
|
|
|
errno = 0;
|
|
|
|
c->password_expiry_utc = parse_timestamp(value, NULL, 10);
|
|
|
|
if (c->password_expiry_utc == 0 || errno == ERANGE)
|
|
|
|
c->password_expiry_utc = TIME_MAX;
|
2012-07-18 12:06:26 +00:00
|
|
|
} else if (!strcmp(key, "url")) {
|
|
|
|
credential_from_url(c, value);
|
credential: let helpers tell us to quit
When we are trying to fill a credential, we loop over the
set of defined credential-helpers, then fall back to running
askpass, and then finally prompt on the terminal. Helpers
which cannot find a credential are free to tell us nothing,
but they cannot currently ask us to stop prompting.
This patch lets them provide a "quit" attribute, which asks
us to stop the process entirely (avoiding running more
helpers, as well as the askpass/terminal prompt).
This has a few possible uses:
1. A helper which prompts the user itself (e.g., in a
dialog) can provide a "cancel" button to the user to
stop further prompts.
2. Some helpers may know that prompting cannot possibly
work. For example, if their role is to broker a ticket
from an external auth system and that auth system
cannot be contacted, there is no point in continuing
(we need a ticket to authenticate, and the user cannot
provide one by typing it in).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-12-04 03:46:48 +00:00
|
|
|
} else if (!strcmp(key, "quit")) {
|
|
|
|
c->quit = !!git_config_bool("quit", value);
|
2011-12-10 10:31:11 +00:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Ignore other lines; we don't know what they mean, but
|
|
|
|
* this future-proofs us when later versions of git do
|
|
|
|
* learn new lines, and the helpers are updated to match.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_release(&line);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
credential: refuse to operate when missing host or protocol
The credential helper protocol was designed to be very flexible: the
fields it takes as input are treated as a pattern, and any missing
fields are taken as wildcards. This allows unusual things like:
echo protocol=https | git credential reject
to delete all stored https credentials (assuming the helpers themselves
treat the input that way). But when helpers are invoked automatically by
Git, this flexibility works against us. If for whatever reason we don't
have a "host" field, then we'd match _any_ host. When you're filling a
credential to send to a remote server, this is almost certainly not what
you want.
Prevent this at the layer that writes to the credential helper. Add a
check to the credential API that the host and protocol are always passed
in, and add an assertion to the credential_write function that speaks
credential helper protocol to be doubly sure.
There are a few ways this can be triggered in practice:
- the "git credential" command passes along arbitrary credential
parameters it reads from stdin.
- until the previous patch, when the host field of a URL is empty, we
would leave it unset (rather than setting it to the empty string)
- a URL like "example.com/foo.git" is treated by curl as if "http://"
was present, but our parser sees it as a non-URL and leaves all
fields unset
- the recent fix for URLs with embedded newlines blanks the URL but
otherwise continues. Rather than having the desired effect of
looking up no credential at all, many helpers will return _any_
credential
Our earlier test for an embedded newline didn't catch this because it
only checked that the credential was cleared, but didn't configure an
actual helper. Configuring the "verbatim" helper in the test would show
that it is invoked (it's obviously a silly helper which doesn't look at
its input, but the point is that it shouldn't be run at all). Since
we're switching this case to die(), we don't need to bother with a
helper. We can see the new behavior just by checking that the operation
fails.
We'll add new tests covering partial input as well (these can be
triggered through various means with url-parsing, but it's simpler to
just check them directly, as we know we are covered even if the url
parser changes behavior in the future).
[jn: changed to die() instead of logging and showing a manual
username/password prompt]
Reported-by: Carlo Arenas <carenas@gmail.com>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
2020-04-19 03:50:48 +00:00
|
|
|
static void credential_write_item(FILE *fp, const char *key, const char *value,
|
|
|
|
int required)
|
2011-12-10 10:31:11 +00:00
|
|
|
{
|
credential: refuse to operate when missing host or protocol
The credential helper protocol was designed to be very flexible: the
fields it takes as input are treated as a pattern, and any missing
fields are taken as wildcards. This allows unusual things like:
echo protocol=https | git credential reject
to delete all stored https credentials (assuming the helpers themselves
treat the input that way). But when helpers are invoked automatically by
Git, this flexibility works against us. If for whatever reason we don't
have a "host" field, then we'd match _any_ host. When you're filling a
credential to send to a remote server, this is almost certainly not what
you want.
Prevent this at the layer that writes to the credential helper. Add a
check to the credential API that the host and protocol are always passed
in, and add an assertion to the credential_write function that speaks
credential helper protocol to be doubly sure.
There are a few ways this can be triggered in practice:
- the "git credential" command passes along arbitrary credential
parameters it reads from stdin.
- until the previous patch, when the host field of a URL is empty, we
would leave it unset (rather than setting it to the empty string)
- a URL like "example.com/foo.git" is treated by curl as if "http://"
was present, but our parser sees it as a non-URL and leaves all
fields unset
- the recent fix for URLs with embedded newlines blanks the URL but
otherwise continues. Rather than having the desired effect of
looking up no credential at all, many helpers will return _any_
credential
Our earlier test for an embedded newline didn't catch this because it
only checked that the credential was cleared, but didn't configure an
actual helper. Configuring the "verbatim" helper in the test would show
that it is invoked (it's obviously a silly helper which doesn't look at
its input, but the point is that it shouldn't be run at all). Since
we're switching this case to die(), we don't need to bother with a
helper. We can see the new behavior just by checking that the operation
fails.
We'll add new tests covering partial input as well (these can be
triggered through various means with url-parsing, but it's simpler to
just check them directly, as we know we are covered even if the url
parser changes behavior in the future).
[jn: changed to die() instead of logging and showing a manual
username/password prompt]
Reported-by: Carlo Arenas <carenas@gmail.com>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
2020-04-19 03:50:48 +00:00
|
|
|
if (!value && required)
|
|
|
|
BUG("credential value for %s is missing", key);
|
2011-12-10 10:31:11 +00:00
|
|
|
if (!value)
|
|
|
|
return;
|
2020-03-11 21:53:41 +00:00
|
|
|
if (strchr(value, '\n'))
|
|
|
|
die("credential value for %s contains newline", key);
|
2011-12-10 10:31:11 +00:00
|
|
|
fprintf(fp, "%s=%s\n", key, value);
|
|
|
|
}
|
|
|
|
|
2012-06-24 11:40:00 +00:00
|
|
|
void credential_write(const struct credential *c, FILE *fp)
|
2011-12-10 10:31:11 +00:00
|
|
|
{
|
credential: refuse to operate when missing host or protocol
The credential helper protocol was designed to be very flexible: the
fields it takes as input are treated as a pattern, and any missing
fields are taken as wildcards. This allows unusual things like:
echo protocol=https | git credential reject
to delete all stored https credentials (assuming the helpers themselves
treat the input that way). But when helpers are invoked automatically by
Git, this flexibility works against us. If for whatever reason we don't
have a "host" field, then we'd match _any_ host. When you're filling a
credential to send to a remote server, this is almost certainly not what
you want.
Prevent this at the layer that writes to the credential helper. Add a
check to the credential API that the host and protocol are always passed
in, and add an assertion to the credential_write function that speaks
credential helper protocol to be doubly sure.
There are a few ways this can be triggered in practice:
- the "git credential" command passes along arbitrary credential
parameters it reads from stdin.
- until the previous patch, when the host field of a URL is empty, we
would leave it unset (rather than setting it to the empty string)
- a URL like "example.com/foo.git" is treated by curl as if "http://"
was present, but our parser sees it as a non-URL and leaves all
fields unset
- the recent fix for URLs with embedded newlines blanks the URL but
otherwise continues. Rather than having the desired effect of
looking up no credential at all, many helpers will return _any_
credential
Our earlier test for an embedded newline didn't catch this because it
only checked that the credential was cleared, but didn't configure an
actual helper. Configuring the "verbatim" helper in the test would show
that it is invoked (it's obviously a silly helper which doesn't look at
its input, but the point is that it shouldn't be run at all). Since
we're switching this case to die(), we don't need to bother with a
helper. We can see the new behavior just by checking that the operation
fails.
We'll add new tests covering partial input as well (these can be
triggered through various means with url-parsing, but it's simpler to
just check them directly, as we know we are covered even if the url
parser changes behavior in the future).
[jn: changed to die() instead of logging and showing a manual
username/password prompt]
Reported-by: Carlo Arenas <carenas@gmail.com>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
2020-04-19 03:50:48 +00:00
|
|
|
credential_write_item(fp, "protocol", c->protocol, 1);
|
|
|
|
credential_write_item(fp, "host", c->host, 1);
|
|
|
|
credential_write_item(fp, "path", c->path, 0);
|
|
|
|
credential_write_item(fp, "username", c->username, 0);
|
|
|
|
credential_write_item(fp, "password", c->password, 0);
|
credential: new attribute password_expiry_utc
Some passwords have an expiry date known at generation. This may be
years away for a personal access token or hours for an OAuth access
token.
When multiple credential helpers are configured, `credential fill` tries
each helper in turn until it has a username and password, returning
early. If Git authentication succeeds, `credential approve`
stores the successful credential in all helpers. If authentication
fails, `credential reject` erases matching credentials in all helpers.
Helpers implement corresponding operations: get, store, erase.
The credential protocol has no expiry attribute, so helpers cannot
store expiry information. Even if a helper returned an improvised
expiry attribute, git credential discards unrecognised attributes
between operations and between helpers.
This is a particular issue when a storage helper and a
credential-generating helper are configured together:
[credential]
helper = storage # eg. cache or osxkeychain
helper = generate # eg. oauth
`credential approve` stores the generated credential in both helpers
without expiry information. Later `credential fill` may return an
expired credential from storage. There is no workaround, no matter how
clever the second helper. The user sees authentication fail (a retry
will succeed).
Introduce a password expiry attribute. In `credential fill`, ignore
expired passwords and continue to query subsequent helpers.
In the example above, `credential fill` ignores the expired password
and a fresh credential is generated. If authentication succeeds,
`credential approve` replaces the expired password in storage.
If authentication fails, the expired credential is erased by
`credential reject`. It is unnecessary but harmless for storage
helpers to self prune expired credentials.
Add support for the new attribute to credential-cache.
Eventually, I hope to see support in other popular storage helpers.
Example usage in a credential-generating helper
https://github.com/hickford/git-credential-oauth/pull/16
Signed-off-by: M Hickford <mirth.hickford@gmail.com>
Reviewed-by: Calvin Wan <calvinwan@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-02-18 06:32:57 +00:00
|
|
|
if (c->password_expiry_utc != TIME_MAX) {
|
|
|
|
char *s = xstrfmt("%"PRItime, c->password_expiry_utc);
|
|
|
|
credential_write_item(fp, "password_expiry_utc", s, 0);
|
|
|
|
free(s);
|
|
|
|
}
|
2011-12-10 10:31:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int run_credential_helper(struct credential *c,
|
|
|
|
const char *cmd,
|
|
|
|
int want_output)
|
|
|
|
{
|
2014-08-19 19:09:35 +00:00
|
|
|
struct child_process helper = CHILD_PROCESS_INIT;
|
2011-12-10 10:31:11 +00:00
|
|
|
FILE *fp;
|
|
|
|
|
2020-08-26 22:25:03 +00:00
|
|
|
strvec_push(&helper.args, cmd);
|
2011-12-10 10:31:11 +00:00
|
|
|
helper.use_shell = 1;
|
|
|
|
helper.in = -1;
|
|
|
|
if (want_output)
|
|
|
|
helper.out = -1;
|
|
|
|
else
|
|
|
|
helper.no_stdout = 1;
|
|
|
|
|
|
|
|
if (start_command(&helper) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
fp = xfdopen(helper.in, "w");
|
2018-03-29 18:00:56 +00:00
|
|
|
sigchain_push(SIGPIPE, SIG_IGN);
|
2011-12-10 10:31:11 +00:00
|
|
|
credential_write(c, fp);
|
|
|
|
fclose(fp);
|
2018-03-29 18:00:56 +00:00
|
|
|
sigchain_pop(SIGPIPE);
|
2011-12-10 10:31:11 +00:00
|
|
|
|
|
|
|
if (want_output) {
|
|
|
|
int r;
|
|
|
|
fp = xfdopen(helper.out, "r");
|
|
|
|
r = credential_read(c, fp);
|
|
|
|
fclose(fp);
|
|
|
|
if (r < 0) {
|
|
|
|
finish_command(&helper);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (finish_command(&helper))
|
|
|
|
return -1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int credential_do(struct credential *c, const char *helper,
|
|
|
|
const char *operation)
|
|
|
|
{
|
|
|
|
struct strbuf cmd = STRBUF_INIT;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if (helper[0] == '!')
|
|
|
|
strbuf_addstr(&cmd, helper + 1);
|
|
|
|
else if (is_absolute_path(helper))
|
|
|
|
strbuf_addstr(&cmd, helper);
|
|
|
|
else
|
|
|
|
strbuf_addf(&cmd, "git credential-%s", helper);
|
|
|
|
|
|
|
|
strbuf_addf(&cmd, " %s", operation);
|
|
|
|
r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
|
|
|
|
|
|
|
|
strbuf_release(&cmd);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
void credential_fill(struct credential *c)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (c->username && c->password)
|
|
|
|
return;
|
|
|
|
|
2011-12-10 10:31:24 +00:00
|
|
|
credential_apply_config(c);
|
|
|
|
|
2011-12-10 10:31:11 +00:00
|
|
|
for (i = 0; i < c->helpers.nr; i++) {
|
|
|
|
credential_do(c, c->helpers.items[i].string, "get");
|
credential: new attribute password_expiry_utc
Some passwords have an expiry date known at generation. This may be
years away for a personal access token or hours for an OAuth access
token.
When multiple credential helpers are configured, `credential fill` tries
each helper in turn until it has a username and password, returning
early. If Git authentication succeeds, `credential approve`
stores the successful credential in all helpers. If authentication
fails, `credential reject` erases matching credentials in all helpers.
Helpers implement corresponding operations: get, store, erase.
The credential protocol has no expiry attribute, so helpers cannot
store expiry information. Even if a helper returned an improvised
expiry attribute, git credential discards unrecognised attributes
between operations and between helpers.
This is a particular issue when a storage helper and a
credential-generating helper are configured together:
[credential]
helper = storage # eg. cache or osxkeychain
helper = generate # eg. oauth
`credential approve` stores the generated credential in both helpers
without expiry information. Later `credential fill` may return an
expired credential from storage. There is no workaround, no matter how
clever the second helper. The user sees authentication fail (a retry
will succeed).
Introduce a password expiry attribute. In `credential fill`, ignore
expired passwords and continue to query subsequent helpers.
In the example above, `credential fill` ignores the expired password
and a fresh credential is generated. If authentication succeeds,
`credential approve` replaces the expired password in storage.
If authentication fails, the expired credential is erased by
`credential reject`. It is unnecessary but harmless for storage
helpers to self prune expired credentials.
Add support for the new attribute to credential-cache.
Eventually, I hope to see support in other popular storage helpers.
Example usage in a credential-generating helper
https://github.com/hickford/git-credential-oauth/pull/16
Signed-off-by: M Hickford <mirth.hickford@gmail.com>
Reviewed-by: Calvin Wan <calvinwan@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-02-18 06:32:57 +00:00
|
|
|
if (c->password_expiry_utc < time(NULL)) {
|
|
|
|
/* Discard expired password */
|
|
|
|
FREE_AND_NULL(c->password);
|
|
|
|
/* Reset expiry to maintain consistency */
|
|
|
|
c->password_expiry_utc = TIME_MAX;
|
|
|
|
}
|
2011-12-10 10:31:11 +00:00
|
|
|
if (c->username && c->password)
|
|
|
|
return;
|
credential: let helpers tell us to quit
When we are trying to fill a credential, we loop over the
set of defined credential-helpers, then fall back to running
askpass, and then finally prompt on the terminal. Helpers
which cannot find a credential are free to tell us nothing,
but they cannot currently ask us to stop prompting.
This patch lets them provide a "quit" attribute, which asks
us to stop the process entirely (avoiding running more
helpers, as well as the askpass/terminal prompt).
This has a few possible uses:
1. A helper which prompts the user itself (e.g., in a
dialog) can provide a "cancel" button to the user to
stop further prompts.
2. Some helpers may know that prompting cannot possibly
work. For example, if their role is to broker a ticket
from an external auth system and that auth system
cannot be contacted, there is no point in continuing
(we need a ticket to authenticate, and the user cannot
provide one by typing it in).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-12-04 03:46:48 +00:00
|
|
|
if (c->quit)
|
|
|
|
die("credential helper '%s' told us to quit",
|
|
|
|
c->helpers.items[i].string);
|
2011-12-10 10:31:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
credential_getpass(c);
|
|
|
|
if (!c->username && !c->password)
|
|
|
|
die("unable to get password from user");
|
|
|
|
}
|
|
|
|
|
|
|
|
void credential_approve(struct credential *c)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (c->approved)
|
|
|
|
return;
|
credential: new attribute password_expiry_utc
Some passwords have an expiry date known at generation. This may be
years away for a personal access token or hours for an OAuth access
token.
When multiple credential helpers are configured, `credential fill` tries
each helper in turn until it has a username and password, returning
early. If Git authentication succeeds, `credential approve`
stores the successful credential in all helpers. If authentication
fails, `credential reject` erases matching credentials in all helpers.
Helpers implement corresponding operations: get, store, erase.
The credential protocol has no expiry attribute, so helpers cannot
store expiry information. Even if a helper returned an improvised
expiry attribute, git credential discards unrecognised attributes
between operations and between helpers.
This is a particular issue when a storage helper and a
credential-generating helper are configured together:
[credential]
helper = storage # eg. cache or osxkeychain
helper = generate # eg. oauth
`credential approve` stores the generated credential in both helpers
without expiry information. Later `credential fill` may return an
expired credential from storage. There is no workaround, no matter how
clever the second helper. The user sees authentication fail (a retry
will succeed).
Introduce a password expiry attribute. In `credential fill`, ignore
expired passwords and continue to query subsequent helpers.
In the example above, `credential fill` ignores the expired password
and a fresh credential is generated. If authentication succeeds,
`credential approve` replaces the expired password in storage.
If authentication fails, the expired credential is erased by
`credential reject`. It is unnecessary but harmless for storage
helpers to self prune expired credentials.
Add support for the new attribute to credential-cache.
Eventually, I hope to see support in other popular storage helpers.
Example usage in a credential-generating helper
https://github.com/hickford/git-credential-oauth/pull/16
Signed-off-by: M Hickford <mirth.hickford@gmail.com>
Reviewed-by: Calvin Wan <calvinwan@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-02-18 06:32:57 +00:00
|
|
|
if (!c->username || !c->password || c->password_expiry_utc < time(NULL))
|
2011-12-10 10:31:11 +00:00
|
|
|
return;
|
|
|
|
|
2011-12-10 10:31:24 +00:00
|
|
|
credential_apply_config(c);
|
|
|
|
|
2011-12-10 10:31:11 +00:00
|
|
|
for (i = 0; i < c->helpers.nr; i++)
|
|
|
|
credential_do(c, c->helpers.items[i].string, "store");
|
|
|
|
c->approved = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void credential_reject(struct credential *c)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2011-12-10 10:31:24 +00:00
|
|
|
credential_apply_config(c);
|
|
|
|
|
2011-12-10 10:31:11 +00:00
|
|
|
for (i = 0; i < c->helpers.nr; i++)
|
|
|
|
credential_do(c, c->helpers.items[i].string, "erase");
|
|
|
|
|
2017-06-15 23:15:49 +00:00
|
|
|
FREE_AND_NULL(c->username);
|
|
|
|
FREE_AND_NULL(c->password);
|
credential: new attribute password_expiry_utc
Some passwords have an expiry date known at generation. This may be
years away for a personal access token or hours for an OAuth access
token.
When multiple credential helpers are configured, `credential fill` tries
each helper in turn until it has a username and password, returning
early. If Git authentication succeeds, `credential approve`
stores the successful credential in all helpers. If authentication
fails, `credential reject` erases matching credentials in all helpers.
Helpers implement corresponding operations: get, store, erase.
The credential protocol has no expiry attribute, so helpers cannot
store expiry information. Even if a helper returned an improvised
expiry attribute, git credential discards unrecognised attributes
between operations and between helpers.
This is a particular issue when a storage helper and a
credential-generating helper are configured together:
[credential]
helper = storage # eg. cache or osxkeychain
helper = generate # eg. oauth
`credential approve` stores the generated credential in both helpers
without expiry information. Later `credential fill` may return an
expired credential from storage. There is no workaround, no matter how
clever the second helper. The user sees authentication fail (a retry
will succeed).
Introduce a password expiry attribute. In `credential fill`, ignore
expired passwords and continue to query subsequent helpers.
In the example above, `credential fill` ignores the expired password
and a fresh credential is generated. If authentication succeeds,
`credential approve` replaces the expired password in storage.
If authentication fails, the expired credential is erased by
`credential reject`. It is unnecessary but harmless for storage
helpers to self prune expired credentials.
Add support for the new attribute to credential-cache.
Eventually, I hope to see support in other popular storage helpers.
Example usage in a credential-generating helper
https://github.com/hickford/git-credential-oauth/pull/16
Signed-off-by: M Hickford <mirth.hickford@gmail.com>
Reviewed-by: Calvin Wan <calvinwan@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-02-18 06:32:57 +00:00
|
|
|
c->password_expiry_utc = TIME_MAX;
|
2011-12-10 10:31:11 +00:00
|
|
|
c->approved = 0;
|
|
|
|
}
|
2011-12-10 10:31:17 +00:00
|
|
|
|
2020-03-12 05:31:11 +00:00
|
|
|
static int check_url_component(const char *url, int quiet,
|
|
|
|
const char *name, const char *value)
|
|
|
|
{
|
|
|
|
if (!value)
|
|
|
|
return 0;
|
|
|
|
if (!strchr(value, '\n'))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!quiet)
|
|
|
|
warning(_("url contains a newline in its %s component: %s"),
|
|
|
|
name, url);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
credential: optionally allow partial URLs in credential_from_url_gently()
Prior to the fixes for CVE-2020-11008, we were _very_ lenient in what we
required from a URL in order to parse it into a `struct credential`.
That led to serious vulnerabilities.
There was one call site, though, that really needed that leniency: when
parsing config settings a la `credential.dev.azure.com.useHTTPPath`.
Settings like this might be desired when users want to use, say, a given
user name on a given host, regardless of the protocol to be used.
In preparation for fixing that bug, let's refactor the code to
optionally allow for partial URLs. For the moment, this functionality is
only exposed via the now-renamed function `credential_from_url_1()`, but
it is not used. The intention is to make it easier to verify that this
commit does not change the existing behavior unless explicitly allowing
for partial URLs.
Please note that this patch does more than just reinstating a way to
imitate the behavior before those CVE-2020-11008 fixes: Before that, we
would simply ignore URLs without a protocol. In other words,
misleadingly, the following setting would be applied to _all_ URLs:
[credential "example.com"]
username = that-me
The obvious intention is to match the host name only. With this patch,
we allow precisely that: when parsing the URL with non-zero
`allow_partial_url`, we do not simply return success if there was no
protocol, but we simply leave the protocol unset and continue parsing
the URL.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Reviewed-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-24 11:49:51 +00:00
|
|
|
/*
|
|
|
|
* Potentially-partial URLs can, but do not have to, contain
|
|
|
|
*
|
|
|
|
* - a protocol (or scheme) of the form "<protocol>://"
|
|
|
|
*
|
|
|
|
* - a host name (the part after the protocol and before the first slash after
|
|
|
|
* that, if any)
|
|
|
|
*
|
|
|
|
* - a user name and potentially a password (as "<user>[:<password>]@" part of
|
|
|
|
* the host name)
|
|
|
|
*
|
|
|
|
* - a path (the part after the host name, if any, starting with the slash)
|
|
|
|
*
|
|
|
|
* Missing parts will be left unset in `struct credential`. Thus, `https://`
|
|
|
|
* will have only the `protocol` set, `example.com` only the host name, and
|
|
|
|
* `/git` only the path.
|
|
|
|
*
|
|
|
|
* Note that an empty host name in an otherwise fully-qualified URL (e.g.
|
|
|
|
* `cert:///path/to/cert.pem`) will be treated as unset if we expect the URL to
|
|
|
|
* be potentially partial, and only then (otherwise, the empty string is used).
|
|
|
|
*
|
|
|
|
* The credential_from_url() function does not allow partial URLs.
|
|
|
|
*/
|
|
|
|
static int credential_from_url_1(struct credential *c, const char *url,
|
|
|
|
int allow_partial_url, int quiet)
|
2011-12-10 10:31:17 +00:00
|
|
|
{
|
|
|
|
const char *at, *colon, *cp, *slash, *host, *proto_end;
|
|
|
|
|
|
|
|
credential_clear(c);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Match one of:
|
|
|
|
* (1) proto://<host>/...
|
|
|
|
* (2) proto://<user>@<host>/...
|
|
|
|
* (3) proto://<user>:<pass>@<host>/...
|
|
|
|
*/
|
|
|
|
proto_end = strstr(url, "://");
|
credential: optionally allow partial URLs in credential_from_url_gently()
Prior to the fixes for CVE-2020-11008, we were _very_ lenient in what we
required from a URL in order to parse it into a `struct credential`.
That led to serious vulnerabilities.
There was one call site, though, that really needed that leniency: when
parsing config settings a la `credential.dev.azure.com.useHTTPPath`.
Settings like this might be desired when users want to use, say, a given
user name on a given host, regardless of the protocol to be used.
In preparation for fixing that bug, let's refactor the code to
optionally allow for partial URLs. For the moment, this functionality is
only exposed via the now-renamed function `credential_from_url_1()`, but
it is not used. The intention is to make it easier to verify that this
commit does not change the existing behavior unless explicitly allowing
for partial URLs.
Please note that this patch does more than just reinstating a way to
imitate the behavior before those CVE-2020-11008 fixes: Before that, we
would simply ignore URLs without a protocol. In other words,
misleadingly, the following setting would be applied to _all_ URLs:
[credential "example.com"]
username = that-me
The obvious intention is to match the host name only. With this patch,
we allow precisely that: when parsing the URL with non-zero
`allow_partial_url`, we do not simply return success if there was no
protocol, but we simply leave the protocol unset and continue parsing
the URL.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Reviewed-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-24 11:49:51 +00:00
|
|
|
if (!allow_partial_url && (!proto_end || proto_end == url)) {
|
credential: treat URL without scheme as invalid
libcurl permits making requests without a URL scheme specified. In
this case, it guesses the URL from the hostname, so I can run
git ls-remote http::ftp.example.com/path/to/repo
and it would make an FTP request.
Any user intentionally using such a URL is likely to have made a typo.
Unfortunately, credential_from_url is not able to determine the host and
protocol in order to determine appropriate credentials to send, and
until "credential: refuse to operate when missing host or protocol",
this resulted in another host's credentials being leaked to the named
host.
Teach credential_from_url_gently to consider such a URL to be invalid
so that fsck can detect and block gitmodules files with such URLs,
allowing server operators to avoid serving them to downstream users
running older versions of Git.
This also means that when such URLs are passed on the command line, Git
will print a clearer error so affected users can switch to the simpler
URL that explicitly specifies the host and protocol they intend.
One subtlety: .gitmodules files can contain relative URLs, representing
a URL relative to the URL they were cloned from. The relative URL
resolver used for .gitmodules can follow ".." components out of the path
part and past the host part of a URL, meaning that such a relative URL
can be used to traverse from a https://foo.example.com/innocent
superproject to a https::attacker.example.com/exploit submodule.
Fortunately a leading ':' in the first path component after a series of
leading './' and '../' components is unlikely to show up in other
contexts, so we can catch this by detecting that pattern.
Reported-by: Jeff King <peff@peff.net>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Reviewed-by: Jeff King <peff@peff.net>
2020-04-19 03:54:13 +00:00
|
|
|
if (!quiet)
|
|
|
|
warning(_("url has no scheme: %s"), url);
|
|
|
|
return -1;
|
|
|
|
}
|
credential: optionally allow partial URLs in credential_from_url_gently()
Prior to the fixes for CVE-2020-11008, we were _very_ lenient in what we
required from a URL in order to parse it into a `struct credential`.
That led to serious vulnerabilities.
There was one call site, though, that really needed that leniency: when
parsing config settings a la `credential.dev.azure.com.useHTTPPath`.
Settings like this might be desired when users want to use, say, a given
user name on a given host, regardless of the protocol to be used.
In preparation for fixing that bug, let's refactor the code to
optionally allow for partial URLs. For the moment, this functionality is
only exposed via the now-renamed function `credential_from_url_1()`, but
it is not used. The intention is to make it easier to verify that this
commit does not change the existing behavior unless explicitly allowing
for partial URLs.
Please note that this patch does more than just reinstating a way to
imitate the behavior before those CVE-2020-11008 fixes: Before that, we
would simply ignore URLs without a protocol. In other words,
misleadingly, the following setting would be applied to _all_ URLs:
[credential "example.com"]
username = that-me
The obvious intention is to match the host name only. With this patch,
we allow precisely that: when parsing the URL with non-zero
`allow_partial_url`, we do not simply return success if there was no
protocol, but we simply leave the protocol unset and continue parsing
the URL.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Reviewed-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-24 11:49:51 +00:00
|
|
|
cp = proto_end ? proto_end + 3 : url;
|
2011-12-10 10:31:17 +00:00
|
|
|
at = strchr(cp, '@');
|
|
|
|
colon = strchr(cp, ':');
|
2020-04-14 21:43:04 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* A query or fragment marker before the slash ends the host portion.
|
|
|
|
* We'll just continue to call this "slash" for simplicity. Notably our
|
|
|
|
* "trim leading slashes" part won't skip over this part of the path,
|
|
|
|
* but that's what we'd want.
|
|
|
|
*/
|
|
|
|
slash = cp + strcspn(cp, "/?#");
|
2011-12-10 10:31:17 +00:00
|
|
|
|
|
|
|
if (!at || slash <= at) {
|
|
|
|
/* Case (1) */
|
|
|
|
host = cp;
|
|
|
|
}
|
|
|
|
else if (!colon || at <= colon) {
|
|
|
|
/* Case (2) */
|
|
|
|
c->username = url_decode_mem(cp, at - cp);
|
credential: use the last matching username in the config
Everywhere else in the codebase, we use the rule that the last matching
configuration option is the one that takes effect. This is helpful
because it allows more specific configuration settings (e.g., per-repo
configuration) to override less specific settings (e.g., per-user
configuration).
However, in the credential code, we didn't honor this setting, and
instead picked the first setting we had, and stuck with it. This was
likely to ensure we picked the value from the URL, which we want to
honor over the configuration.
It's possible to do both, though, so let's check if the value is the one
we've gotten over our protocol connection, which if present will have
come from the URL, and keep it if so. Otherwise, let's overwrite the
value with the latest version we've got from the configuration, so we
keep the last configuration value.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:12 +00:00
|
|
|
if (c->username && *c->username)
|
|
|
|
c->username_from_proto = 1;
|
2011-12-10 10:31:17 +00:00
|
|
|
host = at + 1;
|
|
|
|
} else {
|
|
|
|
/* Case (3) */
|
|
|
|
c->username = url_decode_mem(cp, colon - cp);
|
credential: use the last matching username in the config
Everywhere else in the codebase, we use the rule that the last matching
configuration option is the one that takes effect. This is helpful
because it allows more specific configuration settings (e.g., per-repo
configuration) to override less specific settings (e.g., per-user
configuration).
However, in the credential code, we didn't honor this setting, and
instead picked the first setting we had, and stuck with it. This was
likely to ensure we picked the value from the URL, which we want to
honor over the configuration.
It's possible to do both, though, so let's check if the value is the one
we've gotten over our protocol connection, which if present will have
come from the URL, and keep it if so. Otherwise, let's overwrite the
value with the latest version we've got from the configuration, so we
keep the last configuration value.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-20 02:24:12 +00:00
|
|
|
if (c->username && *c->username)
|
|
|
|
c->username_from_proto = 1;
|
2011-12-10 10:31:17 +00:00
|
|
|
c->password = url_decode_mem(colon + 1, at - (colon + 1));
|
|
|
|
host = at + 1;
|
|
|
|
}
|
|
|
|
|
credential: optionally allow partial URLs in credential_from_url_gently()
Prior to the fixes for CVE-2020-11008, we were _very_ lenient in what we
required from a URL in order to parse it into a `struct credential`.
That led to serious vulnerabilities.
There was one call site, though, that really needed that leniency: when
parsing config settings a la `credential.dev.azure.com.useHTTPPath`.
Settings like this might be desired when users want to use, say, a given
user name on a given host, regardless of the protocol to be used.
In preparation for fixing that bug, let's refactor the code to
optionally allow for partial URLs. For the moment, this functionality is
only exposed via the now-renamed function `credential_from_url_1()`, but
it is not used. The intention is to make it easier to verify that this
commit does not change the existing behavior unless explicitly allowing
for partial URLs.
Please note that this patch does more than just reinstating a way to
imitate the behavior before those CVE-2020-11008 fixes: Before that, we
would simply ignore URLs without a protocol. In other words,
misleadingly, the following setting would be applied to _all_ URLs:
[credential "example.com"]
username = that-me
The obvious intention is to match the host name only. With this patch,
we allow precisely that: when parsing the URL with non-zero
`allow_partial_url`, we do not simply return success if there was no
protocol, but we simply leave the protocol unset and continue parsing
the URL.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Reviewed-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-24 11:49:51 +00:00
|
|
|
if (proto_end && proto_end - url > 0)
|
|
|
|
c->protocol = xmemdupz(url, proto_end - url);
|
|
|
|
if (!allow_partial_url || slash - host > 0)
|
|
|
|
c->host = url_decode_mem(host, slash - host);
|
2011-12-10 10:31:17 +00:00
|
|
|
/* Trim leading and trailing slashes from path */
|
|
|
|
while (*slash == '/')
|
|
|
|
slash++;
|
|
|
|
if (*slash) {
|
|
|
|
char *p;
|
|
|
|
c->path = url_decode(slash);
|
|
|
|
p = c->path + strlen(c->path) - 1;
|
|
|
|
while (p > c->path && *p == '/')
|
|
|
|
*p-- = '\0';
|
|
|
|
}
|
2020-03-12 05:31:11 +00:00
|
|
|
|
|
|
|
if (check_url_component(url, quiet, "username", c->username) < 0 ||
|
|
|
|
check_url_component(url, quiet, "password", c->password) < 0 ||
|
|
|
|
check_url_component(url, quiet, "protocol", c->protocol) < 0 ||
|
|
|
|
check_url_component(url, quiet, "host", c->host) < 0 ||
|
|
|
|
check_url_component(url, quiet, "path", c->path) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-04-24 11:49:52 +00:00
|
|
|
static int credential_from_potentially_partial_url(struct credential *c,
|
|
|
|
const char *url)
|
|
|
|
{
|
|
|
|
return credential_from_url_1(c, url, 1, 0);
|
|
|
|
}
|
|
|
|
|
credential: optionally allow partial URLs in credential_from_url_gently()
Prior to the fixes for CVE-2020-11008, we were _very_ lenient in what we
required from a URL in order to parse it into a `struct credential`.
That led to serious vulnerabilities.
There was one call site, though, that really needed that leniency: when
parsing config settings a la `credential.dev.azure.com.useHTTPPath`.
Settings like this might be desired when users want to use, say, a given
user name on a given host, regardless of the protocol to be used.
In preparation for fixing that bug, let's refactor the code to
optionally allow for partial URLs. For the moment, this functionality is
only exposed via the now-renamed function `credential_from_url_1()`, but
it is not used. The intention is to make it easier to verify that this
commit does not change the existing behavior unless explicitly allowing
for partial URLs.
Please note that this patch does more than just reinstating a way to
imitate the behavior before those CVE-2020-11008 fixes: Before that, we
would simply ignore URLs without a protocol. In other words,
misleadingly, the following setting would be applied to _all_ URLs:
[credential "example.com"]
username = that-me
The obvious intention is to match the host name only. With this patch,
we allow precisely that: when parsing the URL with non-zero
`allow_partial_url`, we do not simply return success if there was no
protocol, but we simply leave the protocol unset and continue parsing
the URL.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Reviewed-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-24 11:49:51 +00:00
|
|
|
int credential_from_url_gently(struct credential *c, const char *url, int quiet)
|
|
|
|
{
|
|
|
|
return credential_from_url_1(c, url, 0, quiet);
|
|
|
|
}
|
|
|
|
|
2020-03-12 05:31:11 +00:00
|
|
|
void credential_from_url(struct credential *c, const char *url)
|
|
|
|
{
|
credential: die() when parsing invalid urls
When we try to initialize credential loading by URL and find that the
URL is invalid, we set all fields to NULL in order to avoid acting on
malicious input. Later when we request credentials, we diagonse the
erroneous input:
fatal: refusing to work with credential missing host field
This is problematic in two ways:
- The message doesn't tell the user *why* we are missing the host
field, so they can't tell from this message alone how to recover.
There can be intervening messages after the original warning of
bad input, so the user may not have the context to put two and two
together.
- The error only occurs when we actually need to get a credential. If
the URL permits anonymous access, the only encouragement the user gets
to correct their bogus URL is a quiet warning.
This is inconsistent with the check we perform in fsck, where any use
of such a URL as a submodule is an error.
When we see such a bogus URL, let's not try to be nice and continue
without helpers. Instead, die() immediately. This is simpler and
obviously safe. And there's very little chance of disrupting a normal
workflow.
It's _possible_ that somebody has a legitimate URL with a raw newline in
it. It already wouldn't work with credential helpers, so this patch
steps that up from an inconvenience to "we will refuse to work with it
at all". If such a case does exist, we should figure out a way to work
with it (especially if the newline is only in the path component, which
we normally don't even pass to helpers). But until we see a real report,
we're better off being defensive.
Reported-by: Carlo Arenas <carenas@gmail.com>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
2020-04-19 03:53:09 +00:00
|
|
|
if (credential_from_url_gently(c, url, 0) < 0)
|
|
|
|
die(_("credential url cannot be parsed: %s"), url);
|
2011-12-10 10:31:17 +00:00
|
|
|
}
|