Commit de81aee3 by Russell Belfer

Merge pull request #1298 from ben/user-at

Handle "user@" prefix for credentials partially included in URLs
parents 3261a3e9 630146bd
......@@ -61,6 +61,7 @@ static void checkout_progress(const char *path, size_t cur, size_t tot, void *pa
static int cred_acquire(git_cred **out,
const char * UNUSED(url),
const char * UNUSED(username_from_url),
unsigned int UNUSED(allowed_types),
void * UNUSED(payload))
{
......
......@@ -34,6 +34,8 @@ typedef struct git_cred_userpass_payload {
*
* @param cred The newly created credential object.
* @param url The resource for which we are demanding a credential.
* @param username_from_url The username that was embedded in a "user@host"
* remote url, or NULL if not included.
* @param allowed_types A bitmask stating which cred types are OK to return.
* @param payload The payload provided when specifying this callback. (This is
* interpreted as a `git_cred_userpass_payload*`.)
......@@ -41,6 +43,7 @@ typedef struct git_cred_userpass_payload {
GIT_EXTERN(int) git_cred_userpass(
git_cred **cred,
const char *url,
const char *user_from_url,
unsigned int allowed_types,
void *payload);
......
......@@ -62,6 +62,8 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new(
*
* @param cred The newly created credential object.
* @param url The resource for which we are demanding a credential.
* @param username_from_url The username that was embedded in a "user@host"
* remote url, or NULL if not included.
* @param allowed_types A bitmask stating which cred types are OK to return.
* @param payload The payload provided when specifying this callback.
* @return 0 for success or an error code for failure
......@@ -69,6 +71,7 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new(
typedef int (*git_cred_acquire_cb)(
git_cred **cred,
const char *url,
const char *username_from_url,
unsigned int allowed_types,
void *payload);
......
......@@ -577,27 +577,54 @@ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv);
}
int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port)
int gitno_extract_url_parts(
char **host,
char **port,
char **username,
char **password,
const char *url,
const char *default_port)
{
char *colon, *slash, *delim;
char *colon, *slash, *at, *end;
const char *start;
/*
*
* ==> [user[:pass]@]hostname.tld[:port]/resource
*/
colon = strchr(url, ':');
slash = strchr(url, '/');
at = strchr(url, '@');
if (slash == NULL) {
giterr_set(GITERR_NET, "Malformed URL: missing /");
return -1;
}
start = url;
if (at && at < slash) {
start = at+1;
*username = git__substrdup(url, at - url);
}
if (colon && colon < at) {
git__free(*username);
*username = git__substrdup(url, colon-url);
*password = git__substrdup(colon+1, at-colon-1);
colon = strchr(at, ':');
}
if (colon == NULL) {
*port = git__strdup(default_port);
} else {
*port = git__strndup(colon + 1, slash - colon - 1);
*port = git__substrdup(colon + 1, slash - colon - 1);
}
GITERR_CHECK_ALLOC(*port);
delim = colon == NULL ? slash : colon;
*host = git__strndup(url, delim - url);
end = colon == NULL ? slash : colon;
*host = git__substrdup(start, end - start);
GITERR_CHECK_ALLOC(*host);
return 0;
......
......@@ -66,6 +66,12 @@ int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags);
int gitno_close(gitno_socket *s);
int gitno_select_in(gitno_buffer *buf, long int sec, long int usec);
int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port);
int gitno_extract_url_parts(
char **host,
char **port,
char **username,
char **password,
const char *url,
const char *default_port);
#endif
......@@ -11,17 +11,38 @@
int git_cred_userpass(
git_cred **cred,
const char *url,
const char *user_from_url,
unsigned int allowed_types,
void *payload)
{
git_cred_userpass_payload *userpass = (git_cred_userpass_payload*)payload;
const char *effective_username = NULL;
GIT_UNUSED(url);
if (!userpass || !userpass->username || !userpass->password) return -1;
if (!userpass || !userpass->password) return -1;
/* Username resolution: a username can be passed with the URL, the
* credentials payload, or both. Here's what we do. Note that if we get
* this far, we know that any password the url may contain has already
* failed at least once, so we ignore it.
*
* | Payload | URL | Used |
* +-------------+----------+-----------+
* | yes | no | payload |
* | yes | yes | payload |
* | no | yes | url |
* | no | no | FAIL |
*/
if (userpass->username)
effective_username = userpass->username;
else if (user_from_url)
effective_username = user_from_url;
else
return -1;
if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 ||
git_cred_userpass_plaintext_new(cred, userpass->username, userpass->password) < 0)
git_cred_userpass_plaintext_new(cred, effective_username, userpass->password) < 0)
return -1;
return 0;
......
......@@ -179,7 +179,7 @@ static int _git_uploadpack_ls(
const char *url,
git_smart_subtransport_stream **stream)
{
char *host, *port;
char *host, *port, *user=NULL, *pass=NULL;
git_stream *s;
*stream = NULL;
......@@ -192,7 +192,7 @@ static int _git_uploadpack_ls(
s = (git_stream *)*stream;
if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)
goto on_error;
if (gitno_connect(&s->socket, host, port, 0) < 0)
......@@ -201,6 +201,8 @@ static int _git_uploadpack_ls(
t->current_stream = s;
git__free(host);
git__free(port);
git__free(user);
git__free(pass);
return 0;
on_error:
......@@ -233,7 +235,7 @@ static int _git_receivepack_ls(
const char *url,
git_smart_subtransport_stream **stream)
{
char *host, *port;
char *host, *port, *user=NULL, *pass=NULL;
git_stream *s;
*stream = NULL;
......@@ -246,7 +248,7 @@ static int _git_receivepack_ls(
s = (git_stream *)*stream;
if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)
goto on_error;
if (gitno_connect(&s->socket, host, port, 0) < 0)
......@@ -255,6 +257,8 @@ static int _git_receivepack_ls(
t->current_stream = s;
git__free(host);
git__free(port);
git__free(user);
git__free(pass);
return 0;
on_error:
......
......@@ -60,7 +60,10 @@ typedef struct {
const char *path;
char *host;
char *port;
char *user_from_url;
char *pass_from_url;
git_cred *cred;
git_cred *url_cred;
http_authmechanism_t auth_mechanism;
unsigned connected : 1,
use_ssl : 1;
......@@ -144,6 +147,14 @@ static int gen_request(
apply_basic_credential(buf, t->cred) < 0)
return -1;
/* Use url-parsed basic auth if username and password are both provided */
if (!t->cred && t->user_from_url && t->pass_from_url) {
if (!t->url_cred &&
git_cred_userpass_plaintext_new(&t->url_cred, t->user_from_url, t->pass_from_url) < 0)
return -1;
if (apply_basic_credential(buf, t->url_cred) < 0) return -1;
}
git_buf_puts(buf, "\r\n");
if (git_buf_oom(buf))
......@@ -256,6 +267,7 @@ static int on_headers_complete(http_parser *parser)
if (t->owner->cred_acquire_cb(&t->cred,
t->owner->url,
t->user_from_url,
allowed_types,
t->owner->cred_acquire_payload) < 0)
return PARSE_ERROR_GENERIC;
......@@ -742,8 +754,8 @@ static int http_action(
if (!default_port)
return -1;
if ((ret = gitno_extract_host_and_port(&t->host, &t->port,
url, default_port)) < 0)
if ((ret = gitno_extract_url_parts(&t->host, &t->port,
&t->user_from_url, &t->pass_from_url, url, default_port)) < 0)
return ret;
t->path = strchr(url, '/');
......@@ -809,6 +821,11 @@ static int http_close(git_smart_subtransport *subtransport)
t->cred = NULL;
}
if (t->url_cred) {
t->url_cred->free(t->url_cred);
t->url_cred = NULL;
}
if (t->host) {
git__free(t->host);
t->host = NULL;
......@@ -819,6 +836,16 @@ static int http_close(git_smart_subtransport *subtransport)
t->port = NULL;
}
if (t->user_from_url) {
git__free(t->user_from_url);
t->user_from_url = NULL;
}
if (t->pass_from_url) {
git__free(t->pass_from_url);
t->pass_from_url = NULL;
}
return 0;
}
......
......@@ -73,7 +73,10 @@ typedef struct {
const char *path;
char *host;
char *port;
char *user_from_url;
char *pass_from_url;
git_cred *cred;
git_cred *url_cred;
int auth_mechanism;
HINTERNET session;
HINTERNET connection;
......@@ -250,6 +253,16 @@ static int winhttp_stream_connect(winhttp_stream *s)
apply_basic_credential(s->request, t->cred) < 0)
goto on_error;
/* If no other credentials have been applied and the URL has username and
* password, use those */
if (!t->cred && t->user_from_url && t->pass_from_url) {
if (!t->url_cred &&
git_cred_userpass_plaintext_new(&t->url_cred, t->user_from_url, t->pass_from_url) < 0)
goto on_error;
if (apply_basic_credential(s->request, t->url_cred) < 0)
goto on_error;
}
/* We've done everything up to calling WinHttpSendRequest. */
error = 0;
......@@ -447,7 +460,7 @@ replay:
if (allowed_types &&
(!t->cred || 0 == (t->cred->credtype & allowed_types))) {
if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, allowed_types, t->owner->cred_acquire_payload) < 0)
if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, t->user_from_url, allowed_types, t->owner->cred_acquire_payload) < 0)
return -1;
assert(t->cred);
......@@ -788,7 +801,8 @@ static int winhttp_connect(
t->use_ssl = 1;
}
if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0)
if ((ret = gitno_extract_url_parts(&t->host, &t->port, &t->user_from_url,
&t->pass_from_url, url, default_port)) < 0)
return ret;
t->path = strchr(url, '/');
......@@ -944,11 +958,26 @@ static int winhttp_close(git_smart_subtransport *subtransport)
t->port = NULL;
}
if (t->user_from_url) {
git__free(t->user_from_url);
t->user_from_url = NULL;
}
if (t->pass_from_url) {
git__free(t->pass_from_url);
t->pass_from_url = NULL;
}
if (t->cred) {
t->cred->free(t->cred);
t->cred = NULL;
}
if (t->url_cred) {
t->url_cred->free(t->url_cred);
t->url_cred = NULL;
}
if (t->connection) {
if (!WinHttpCloseHandle(t->connection)) {
giterr_set(GITERR_OS, "Unable to close connection");
......
......@@ -51,11 +51,7 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n)
while (length < n && str[length])
++length;
ptr = (char*)malloc(length + 1);
if (!ptr) {
giterr_set_oom();
return NULL;
}
ptr = (char*)git__malloc(length + 1);
if (length)
memcpy(ptr, str, length);
......@@ -65,6 +61,15 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n)
return ptr;
}
/* NOTE: This doesn't do null or '\0' checking. Watch those boundaries! */
GIT_INLINE(char *) git__substrdup(const char *start, size_t n)
{
char *ptr = (char*)git__malloc(n+1);
memcpy(ptr, start, n);
ptr[n] = '\0';
return ptr;
}
GIT_INLINE(void *) git__realloc(void *ptr, size_t size)
{
void *new_ptr = realloc(ptr, size);
......
......@@ -6,14 +6,14 @@ void test_network_cred__stock_userpass_validates_args(void)
{
git_cred_userpass_payload payload = {0};
cl_git_fail(git_cred_userpass(NULL, NULL, 0, NULL));
cl_git_fail(git_cred_userpass(NULL, NULL, NULL, 0, NULL));
payload.username = "user";
cl_git_fail(git_cred_userpass(NULL, NULL, 0, &payload));
cl_git_fail(git_cred_userpass(NULL, NULL, NULL, 0, &payload));
payload.username = NULL;
payload.username = "pass";
cl_git_fail(git_cred_userpass(NULL, NULL, 0, &payload));
cl_git_fail(git_cred_userpass(NULL, NULL, NULL, 0, &payload));
}
void test_network_cred__stock_userpass_validates_that_method_is_allowed(void)
......@@ -21,7 +21,30 @@ void test_network_cred__stock_userpass_validates_that_method_is_allowed(void)
git_cred *cred;
git_cred_userpass_payload payload = {"user", "pass"};
cl_git_fail(git_cred_userpass(&cred, NULL, 0, &payload));
cl_git_pass(git_cred_userpass(&cred, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
cl_git_fail(git_cred_userpass(&cred, NULL, NULL, 0, &payload));
cl_git_pass(git_cred_userpass(&cred, NULL, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
cred->free(cred);
}
void test_network_cred__stock_userpass_properly_handles_username_in_url(void)
{
git_cred *cred;
git_cred_userpass_plaintext *plain;
git_cred_userpass_payload payload = {"alice", "password"};
cl_git_pass(git_cred_userpass(&cred, NULL, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
plain = (git_cred_userpass_plaintext*)cred;
cl_assert_equal_s(plain->username, "alice");
cred->free(cred);
cl_git_pass(git_cred_userpass(&cred, NULL, "bob", GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
plain = (git_cred_userpass_plaintext*)cred;
cl_assert_equal_s(plain->username, "alice");
cred->free(cred);
payload.username = NULL;
cl_git_pass(git_cred_userpass(&cred, NULL, "bob", GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
plain = (git_cred_userpass_plaintext*)cred;
cl_assert_equal_s(plain->username, "bob");
cred->free(cred);
}
#include "clar_libgit2.h"
#include "netops.h"
char *host, *port, *user, *pass;
void test_network_urlparse__initialize(void)
{
host = port = user = pass = NULL;
}
void test_network_urlparse__cleanup(void)
{
#define FREE_AND_NULL(x) if (x) { git__free(x); x = NULL; }
FREE_AND_NULL(host);
FREE_AND_NULL(port);
FREE_AND_NULL(user);
FREE_AND_NULL(pass);
}
void test_network_urlparse__trivial(void)
{
cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass,
"example.com/resource", "8080"));
cl_assert_equal_s(host, "example.com");
cl_assert_equal_s(port, "8080");
cl_assert_equal_sz(user, NULL);
cl_assert_equal_sz(pass, NULL);
}
void test_network_urlparse__user(void)
{
cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass,
"user@example.com/resource", "8080"));
cl_assert_equal_s(host, "example.com");
cl_assert_equal_s(port, "8080");
cl_assert_equal_s(user, "user");
cl_assert_equal_sz(pass, NULL);
}
void test_network_urlparse__user_pass(void)
{
/* user:pass@hostname.tld/resource */
cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass,
"user:pass@example.com/resource", "8080"));
cl_assert_equal_s(host, "example.com");
cl_assert_equal_s(port, "8080");
cl_assert_equal_s(user, "user");
cl_assert_equal_s(pass, "pass");
}
void test_network_urlparse__port(void)
{
/* hostname.tld:port/resource */
cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass,
"example.com:9191/resource", "8080"));
cl_assert_equal_s(host, "example.com");
cl_assert_equal_s(port, "9191");
cl_assert_equal_sz(user, NULL);
cl_assert_equal_sz(pass, NULL);
}
void test_network_urlparse__user_port(void)
{
/* user@hostname.tld:port/resource */
cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass,
"user@example.com:9191/resource", "8080"));
cl_assert_equal_s(host, "example.com");
cl_assert_equal_s(port, "9191");
cl_assert_equal_s(user, "user");
cl_assert_equal_sz(pass, NULL);
}
void test_network_urlparse__user_pass_port(void)
{
/* user:pass@hostname.tld:port/resource */
cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass,
"user:pass@example.com:9191/resource", "8080"));
cl_assert_equal_s(host, "example.com");
cl_assert_equal_s(port, "9191");
cl_assert_equal_s(user, "user");
cl_assert_equal_s(pass, "pass");
}
......@@ -6,6 +6,9 @@
#define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository"
#define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository"
#define BB_REPO_URL "https://libgit2@bitbucket.org/libgit2/testgitrepository.git"
#define BB_REPO_URL_WITH_PASS "https://libgit2:libgit2@bitbucket.org/libgit2/testgitrepository.git"
#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit2:wrong@bitbucket.org/libgit2/testgitrepository.git"
static git_repository *g_repo;
static git_clone_options g_options;
......@@ -150,4 +153,32 @@ void test_online_clone__credentials(void)
g_options.cred_acquire_payload = &user_pass;
cl_git_pass(git_clone(&g_repo, remote_url, "./foo", &g_options));
git_repository_free(g_repo); g_repo = NULL;
cl_fixture_cleanup("./foo");
}
void test_online_clone__bitbucket_style(void)
{
git_cred_userpass_payload user_pass = {
"libgit2", "libgit2"
};
g_options.cred_acquire_cb = git_cred_userpass;
g_options.cred_acquire_payload = &user_pass;
cl_git_pass(git_clone(&g_repo, BB_REPO_URL, "./foo", &g_options));
git_repository_free(g_repo); g_repo = NULL;
cl_fixture_cleanup("./foo");
/* User and pass from URL */
user_pass.password = "wrong";
cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_PASS, "./foo", &g_options));
git_repository_free(g_repo); g_repo = NULL;
cl_fixture_cleanup("./foo");
/* Wrong password in URL, fall back to user_pass */
user_pass.password = "libgit2";
cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_WRONG_PASS, "./foo", &g_options));
git_repository_free(g_repo); g_repo = NULL;
cl_fixture_cleanup("./foo");
}
......@@ -30,9 +30,15 @@ static git_oid _tag_tree;
static git_oid _tag_blob;
static git_oid _tag_lightweight;
static int cred_acquire_cb(git_cred **cred, const char *url, unsigned int allowed_types, void *payload)
static int cred_acquire_cb(
git_cred **cred,
const char *url,
const char *user_from_url,
unsigned int allowed_types,
void *payload)
{
GIT_UNUSED(url);
GIT_UNUSED(user_from_url);
*((bool*)payload) = true;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment