Skip to content

Commit

Permalink
Merge branch 'jk/maint-http-half-auth-push' into maint-1.7.11
Browse files Browse the repository at this point in the history
Pushing to smart HTTP server with recent Git fails without having
the username in the URL to force authentication, if the server is
configured to allow GET anonymously, while requiring authentication
for POST.

* jk/maint-http-half-auth-push:
  http: prompt for credentials on failed POST
  http: factor out http error code handling
  t: test http access to "half-auth" repositories
  t: test basic smart-http authentication
  t/lib-httpd: recognize */smart/* repos as smart-http
  t/lib-httpd: only route auth/dumb to dumb repos
  t5550: factor out http auth setup
  t5550: put auth-required repo in auth/dumb
  • Loading branch information
Junio C Hamano committed Sep 12, 2012
2 parents 92c830d + b81401c commit 7d9483c
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 106 deletions.
51 changes: 28 additions & 23 deletions http.c
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,33 @@ char *get_remote_object_url(const char *url, const char *hex,
return strbuf_detach(&buf, NULL);
}

int handle_curl_result(struct active_request_slot *slot)
{
struct slot_results *results = slot->results;

if (results->curl_result == CURLE_OK) {
credential_approve(&http_auth);
return HTTP_OK;
} else if (missing_target(results))
return HTTP_MISSING_TARGET;
else if (results->http_code == 401) {
if (http_auth.username && http_auth.password) {
credential_reject(&http_auth);
return HTTP_NOAUTH;
} else {
credential_fill(&http_auth);
init_curl_http_auth(slot->curl);
return HTTP_REAUTH;
}
} else {
if (!curl_errorstr[0])
strlcpy(curl_errorstr,
curl_easy_strerror(results->curl_result),
sizeof(curl_errorstr));
return HTTP_ERROR;
}
}

/* http_request() targets */
#define HTTP_REQUEST_STRBUF 0
#define HTTP_REQUEST_FILE 1
Expand Down Expand Up @@ -791,26 +818,7 @@ static int http_request(const char *url, void *result, int target, int options)

if (start_active_slot(slot)) {
run_active_slot(slot);
if (results.curl_result == CURLE_OK)
ret = HTTP_OK;
else if (missing_target(&results))
ret = HTTP_MISSING_TARGET;
else if (results.http_code == 401) {
if (http_auth.username && http_auth.password) {
credential_reject(&http_auth);
ret = HTTP_NOAUTH;
} else {
credential_fill(&http_auth);
init_curl_http_auth(slot->curl);
ret = HTTP_REAUTH;
}
} else {
if (!curl_errorstr[0])
strlcpy(curl_errorstr,
curl_easy_strerror(results.curl_result),
sizeof(curl_errorstr));
ret = HTTP_ERROR;
}
ret = handle_curl_result(slot);
} else {
error("Unable to start HTTP request for %s", url);
ret = HTTP_START_FAILED;
Expand All @@ -819,9 +827,6 @@ static int http_request(const char *url, void *result, int target, int options)
curl_slist_free_all(headers);
strbuf_release(&buf);

if (ret == HTTP_OK)
credential_approve(&http_auth);

return ret;
}

Expand Down
1 change: 1 addition & 0 deletions http.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ extern int start_active_slot(struct active_request_slot *slot);
extern void run_active_slot(struct active_request_slot *slot);
extern void finish_active_slot(struct active_request_slot *slot);
extern void finish_all_active_slots(void);
extern int handle_curl_result(struct active_request_slot *slot);

#ifdef USE_CURL_MULTI
extern void fill_active_slots(void);
Expand Down
23 changes: 15 additions & 8 deletions remote-curl.c
Original file line number Diff line number Diff line change
Expand Up @@ -362,16 +362,17 @@ static size_t rpc_in(char *ptr, size_t eltsize,

static int run_slot(struct active_request_slot *slot)
{
int err = 0;
int err;
struct slot_results results;

slot->results = &results;
slot->curl_result = curl_easy_perform(slot->curl);
finish_active_slot(slot);

if (results.curl_result != CURLE_OK) {
err |= error("RPC failed; result=%d, HTTP code = %ld",
results.curl_result, results.http_code);
err = handle_curl_result(slot);
if (err != HTTP_OK && err != HTTP_REAUTH) {
error("RPC failed; result=%d, HTTP code = %ld",
results.curl_result, results.http_code);
}

return err;
Expand Down Expand Up @@ -436,9 +437,11 @@ static int post_rpc(struct rpc_state *rpc)
}

if (large_request) {
err = probe_rpc(rpc);
if (err)
return err;
do {
err = probe_rpc(rpc);
} while (err == HTTP_REAUTH);
if (err != HTTP_OK)
return -1;
}

slot = get_active_slot();
Expand Down Expand Up @@ -525,7 +528,11 @@ static int post_rpc(struct rpc_state *rpc)
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);

err = run_slot(slot);
do {
err = run_slot(slot);
} while (err == HTTP_REAUTH && !large_request && !use_gzip);
if (err != HTTP_OK)
err = -1;

curl_slist_free_all(headers);
free(gzip_body);
Expand Down
39 changes: 39 additions & 0 deletions t/lib-httpd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,42 @@ test_http_push_nonff() {
test_i18ngrep "Updates were rejected because" output
'
}

setup_askpass_helper() {
test_expect_success 'setup askpass helper' '
write_script "$TRASH_DIRECTORY/askpass" <<-\EOF &&
echo >>"$TRASH_DIRECTORY/askpass-query" "askpass: $*" &&
cat "$TRASH_DIRECTORY/askpass-response"
EOF
GIT_ASKPASS="$TRASH_DIRECTORY/askpass" &&
export GIT_ASKPASS &&
export TRASH_DIRECTORY
'
}

set_askpass() {
>"$TRASH_DIRECTORY/askpass-query" &&
echo "$*" >"$TRASH_DIRECTORY/askpass-response"
}

expect_askpass() {
dest=$HTTPD_DEST
{
case "$1" in
none)
;;
pass)
echo "askpass: Password for 'http://$2@$dest': "
;;
both)
echo "askpass: Username for 'http://$dest': "
echo "askpass: Password for 'http://$2@$dest': "
;;
*)
false
;;
esac
} >"$TRASH_DIRECTORY/askpass-expect" &&
test_cmp "$TRASH_DIRECTORY/askpass-expect" \
"$TRASH_DIRECTORY/askpass-query"
}
25 changes: 15 additions & 10 deletions t/lib-httpd/apache.conf
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,22 @@ ErrorLog error.log
</IfVersion>

Alias /dumb/ www/
Alias /auth/ www/auth/
Alias /auth/dumb/ www/auth/dumb/

<Location /smart/>
<LocationMatch /smart/>
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
SetEnv GIT_HTTP_EXPORT_ALL
</Location>
<Location /smart_noexport/>
</LocationMatch>
<LocationMatch /smart_noexport/>
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
</Location>
<Location /smart_custom_env/>
</LocationMatch>
<LocationMatch /smart_custom_env/>
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
SetEnv GIT_HTTP_EXPORT_ALL
SetEnv GIT_COMMITTER_NAME "Custom User"
SetEnv GIT_COMMITTER_EMAIL custom@example.com
</Location>
ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/
ScriptAlias /smart_noexport/ ${GIT_EXEC_PATH}/git-http-backend/
ScriptAlias /smart_custom_env/ ${GIT_EXEC_PATH}/git-http-backend/
</LocationMatch>
ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
<Directory ${GIT_EXEC_PATH}>
Options None
</Directory>
Expand Down Expand Up @@ -91,6 +89,13 @@ SSLEngine On
Require valid-user
</Location>

<LocationMatch "^/auth-push/.*/git-receive-pack$">
AuthType Basic
AuthName "git-auth"
AuthUserFile passwd
Require valid-user
</LocationMatch>

<IfDefine DAV>
LoadModule dav_module modules/mod_dav.so
LoadModule dav_fs_module modules/mod_dav_fs.so
Expand Down
17 changes: 8 additions & 9 deletions t/t5540-http-push.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,7 @@ test_expect_success 'create password-protected repository' '
"$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git"
'

test_expect_success 'setup askpass helper' '
cat >askpass <<-\EOF &&
#!/bin/sh
echo user@host
EOF
chmod +x askpass &&
GIT_ASKPASS="$PWD/askpass" &&
export GIT_ASKPASS
'
setup_askpass_helper

test_expect_success 'clone remote repository' '
cd "$ROOT_PATH" &&
Expand Down Expand Up @@ -162,16 +154,23 @@ test_http_push_nonff "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \

test_expect_success 'push to password-protected repository (user in URL)' '
test_commit pw-user &&
set_askpass user@host &&
git push "$HTTPD_URL_USER/auth/dumb/test_repo.git" HEAD &&
git rev-parse --verify HEAD >expect &&
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" \
rev-parse --verify HEAD >actual &&
test_cmp expect actual
'

test_expect_failure 'user was prompted only once for password' '
expect_askpass pass user@host
'

test_expect_failure 'push to password-protected repository (no user in URL)' '
test_commit pw-nouser &&
set_askpass user@host &&
git push "$HTTPD_URL/auth/dumb/test_repo.git" HEAD &&
expect_askpass both user@host
git rev-parse --verify HEAD >expect &&
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" \
rev-parse --verify HEAD >actual &&
Expand Down
26 changes: 26 additions & 0 deletions t/t5541-http-push.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ test_expect_success 'setup remote repository' '
mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
'

setup_askpass_helper

cat >exp <<EOF
GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200
Expand Down Expand Up @@ -266,5 +268,29 @@ test_expect_success 'http push respects GIT_COMMITTER_* in reflog' '
test_cmp expect actual
'

test_expect_success 'push over smart http with auth' '
cd "$ROOT_PATH/test_repo_clone" &&
echo push-auth-test >expect &&
test_commit push-auth-test &&
set_askpass user@host &&
git push "$HTTPD_URL"/auth/smart/test_repo.git &&
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
log -1 --format=%s >actual &&
expect_askpass both user@host &&
test_cmp expect actual
'

test_expect_success 'push to auth-only-for-push repo' '
cd "$ROOT_PATH/test_repo_clone" &&
echo push-half-auth >expect &&
test_commit push-half-auth &&
set_askpass user@host &&
git push "$HTTPD_URL"/auth-push/smart/test_repo.git &&
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
log -1 --format=%s >actual &&
expect_askpass both user@host &&
test_cmp expect actual
'

stop_httpd
test_done
Loading

0 comments on commit 7d9483c

Please sign in to comment.