Skip to content

Commit e6dd70e

Browse files
committed
Merge branch 'jk/maint-http-half-auth-push'
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
2 parents 9192ece + b81401c commit e6dd70e

File tree

9 files changed

+173
-108
lines changed

9 files changed

+173
-108
lines changed

http.c

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,35 @@ char *get_remote_object_url(const char *url, const char *hex,
745745
return strbuf_detach(&buf, NULL);
746746
}
747747

748+
int handle_curl_result(struct active_request_slot *slot)
749+
{
750+
struct slot_results *results = slot->results;
751+
752+
if (results->curl_result == CURLE_OK) {
753+
credential_approve(&http_auth);
754+
return HTTP_OK;
755+
} else if (missing_target(results))
756+
return HTTP_MISSING_TARGET;
757+
else if (results->http_code == 401) {
758+
if (http_auth.username && http_auth.password) {
759+
credential_reject(&http_auth);
760+
return HTTP_NOAUTH;
761+
} else {
762+
credential_fill(&http_auth);
763+
init_curl_http_auth(slot->curl);
764+
return HTTP_REAUTH;
765+
}
766+
} else {
767+
#if LIBCURL_VERSION_NUM >= 0x070c00
768+
if (!curl_errorstr[0])
769+
strlcpy(curl_errorstr,
770+
curl_easy_strerror(results->curl_result),
771+
sizeof(curl_errorstr));
772+
#endif
773+
return HTTP_ERROR;
774+
}
775+
}
776+
748777
/* http_request() targets */
749778
#define HTTP_REQUEST_STRBUF 0
750779
#define HTTP_REQUEST_FILE 1
@@ -792,28 +821,7 @@ static int http_request(const char *url, void *result, int target, int options)
792821

793822
if (start_active_slot(slot)) {
794823
run_active_slot(slot);
795-
if (results.curl_result == CURLE_OK)
796-
ret = HTTP_OK;
797-
else if (missing_target(&results))
798-
ret = HTTP_MISSING_TARGET;
799-
else if (results.http_code == 401) {
800-
if (http_auth.username && http_auth.password) {
801-
credential_reject(&http_auth);
802-
ret = HTTP_NOAUTH;
803-
} else {
804-
credential_fill(&http_auth);
805-
init_curl_http_auth(slot->curl);
806-
ret = HTTP_REAUTH;
807-
}
808-
} else {
809-
#if LIBCURL_VERSION_NUM >= 0x070c00
810-
if (!curl_errorstr[0])
811-
strlcpy(curl_errorstr,
812-
curl_easy_strerror(results.curl_result),
813-
sizeof(curl_errorstr));
814-
#endif
815-
ret = HTTP_ERROR;
816-
}
824+
ret = handle_curl_result(slot);
817825
} else {
818826
error("Unable to start HTTP request for %s", url);
819827
ret = HTTP_START_FAILED;
@@ -822,9 +830,6 @@ static int http_request(const char *url, void *result, int target, int options)
822830
curl_slist_free_all(headers);
823831
strbuf_release(&buf);
824832

825-
if (ret == HTTP_OK)
826-
credential_approve(&http_auth);
827-
828833
return ret;
829834
}
830835

http.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ extern int start_active_slot(struct active_request_slot *slot);
7878
extern void run_active_slot(struct active_request_slot *slot);
7979
extern void finish_active_slot(struct active_request_slot *slot);
8080
extern void finish_all_active_slots(void);
81+
extern int handle_curl_result(struct active_request_slot *slot);
8182

8283
#ifdef USE_CURL_MULTI
8384
extern void fill_active_slots(void);

remote-curl.c

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -362,16 +362,17 @@ static size_t rpc_in(char *ptr, size_t eltsize,
362362

363363
static int run_slot(struct active_request_slot *slot)
364364
{
365-
int err = 0;
365+
int err;
366366
struct slot_results results;
367367

368368
slot->results = &results;
369369
slot->curl_result = curl_easy_perform(slot->curl);
370370
finish_active_slot(slot);
371371

372-
if (results.curl_result != CURLE_OK) {
373-
err |= error("RPC failed; result=%d, HTTP code = %ld",
374-
results.curl_result, results.http_code);
372+
err = handle_curl_result(slot);
373+
if (err != HTTP_OK && err != HTTP_REAUTH) {
374+
error("RPC failed; result=%d, HTTP code = %ld",
375+
results.curl_result, results.http_code);
375376
}
376377

377378
return err;
@@ -436,9 +437,11 @@ static int post_rpc(struct rpc_state *rpc)
436437
}
437438

438439
if (large_request) {
439-
err = probe_rpc(rpc);
440-
if (err)
441-
return err;
440+
do {
441+
err = probe_rpc(rpc);
442+
} while (err == HTTP_REAUTH);
443+
if (err != HTTP_OK)
444+
return -1;
442445
}
443446

444447
slot = get_active_slot();
@@ -525,7 +528,11 @@ static int post_rpc(struct rpc_state *rpc)
525528
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
526529
curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);
527530

528-
err = run_slot(slot);
531+
do {
532+
err = run_slot(slot);
533+
} while (err == HTTP_REAUTH && !large_request && !use_gzip);
534+
if (err != HTTP_OK)
535+
err = -1;
529536

530537
curl_slist_free_all(headers);
531538
free(gzip_body);

t/lib-httpd.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,42 @@ test_http_push_nonff() {
167167
test_i18ngrep "Updates were rejected because" output
168168
'
169169
}
170+
171+
setup_askpass_helper() {
172+
test_expect_success 'setup askpass helper' '
173+
write_script "$TRASH_DIRECTORY/askpass" <<-\EOF &&
174+
echo >>"$TRASH_DIRECTORY/askpass-query" "askpass: $*" &&
175+
cat "$TRASH_DIRECTORY/askpass-response"
176+
EOF
177+
GIT_ASKPASS="$TRASH_DIRECTORY/askpass" &&
178+
export GIT_ASKPASS &&
179+
export TRASH_DIRECTORY
180+
'
181+
}
182+
183+
set_askpass() {
184+
>"$TRASH_DIRECTORY/askpass-query" &&
185+
echo "$*" >"$TRASH_DIRECTORY/askpass-response"
186+
}
187+
188+
expect_askpass() {
189+
dest=$HTTPD_DEST
190+
{
191+
case "$1" in
192+
none)
193+
;;
194+
pass)
195+
echo "askpass: Password for 'http://$2@$dest': "
196+
;;
197+
both)
198+
echo "askpass: Username for 'http://$dest': "
199+
echo "askpass: Password for 'http://$2@$dest': "
200+
;;
201+
*)
202+
false
203+
;;
204+
esac
205+
} >"$TRASH_DIRECTORY/askpass-expect" &&
206+
test_cmp "$TRASH_DIRECTORY/askpass-expect" \
207+
"$TRASH_DIRECTORY/askpass-query"
208+
}

t/lib-httpd/apache.conf

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,22 @@ PassEnv GIT_VALGRIND
4646
PassEnv GIT_VALGRIND_OPTIONS
4747

4848
Alias /dumb/ www/
49-
Alias /auth/ www/auth/
49+
Alias /auth/dumb/ www/auth/dumb/
5050

51-
<Location /smart/>
51+
<LocationMatch /smart/>
5252
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
5353
SetEnv GIT_HTTP_EXPORT_ALL
54-
</Location>
55-
<Location /smart_noexport/>
54+
</LocationMatch>
55+
<LocationMatch /smart_noexport/>
5656
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
57-
</Location>
58-
<Location /smart_custom_env/>
57+
</LocationMatch>
58+
<LocationMatch /smart_custom_env/>
5959
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
6060
SetEnv GIT_HTTP_EXPORT_ALL
6161
SetEnv GIT_COMMITTER_NAME "Custom User"
6262
SetEnv GIT_COMMITTER_EMAIL [email protected]
63-
</Location>
64-
ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/
65-
ScriptAlias /smart_noexport/ ${GIT_EXEC_PATH}/git-http-backend/
66-
ScriptAlias /smart_custom_env/ ${GIT_EXEC_PATH}/git-http-backend/
63+
</LocationMatch>
64+
ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
6765
<Directory ${GIT_EXEC_PATH}>
6866
Options FollowSymlinks
6967
</Directory>
@@ -94,6 +92,13 @@ SSLEngine On
9492
Require valid-user
9593
</Location>
9694

95+
<LocationMatch "^/auth-push/.*/git-receive-pack$">
96+
AuthType Basic
97+
AuthName "git-auth"
98+
AuthUserFile passwd
99+
Require valid-user
100+
</LocationMatch>
101+
97102
<IfDefine DAV>
98103
LoadModule dav_module modules/mod_dav.so
99104
LoadModule dav_fs_module modules/mod_dav_fs.so

t/t5540-http-push.sh

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,7 @@ test_expect_success 'create password-protected repository' '
4646
"$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git"
4747
'
4848

49-
test_expect_success 'setup askpass helper' '
50-
cat >askpass <<-\EOF &&
51-
#!/bin/sh
52-
echo user@host
53-
EOF
54-
chmod +x askpass &&
55-
GIT_ASKPASS="$PWD/askpass" &&
56-
export GIT_ASKPASS
57-
'
49+
setup_askpass_helper
5850

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

163155
test_expect_success 'push to password-protected repository (user in URL)' '
164156
test_commit pw-user &&
157+
set_askpass user@host &&
165158
git push "$HTTPD_URL_USER/auth/dumb/test_repo.git" HEAD &&
166159
git rev-parse --verify HEAD >expect &&
167160
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" \
168161
rev-parse --verify HEAD >actual &&
169162
test_cmp expect actual
170163
'
171164

165+
test_expect_failure 'user was prompted only once for password' '
166+
expect_askpass pass user@host
167+
'
168+
172169
test_expect_failure 'push to password-protected repository (no user in URL)' '
173170
test_commit pw-nouser &&
171+
set_askpass user@host &&
174172
git push "$HTTPD_URL/auth/dumb/test_repo.git" HEAD &&
173+
expect_askpass both user@host
175174
git rev-parse --verify HEAD >expect &&
176175
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" \
177176
rev-parse --verify HEAD >actual &&

t/t5541-http-push.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ test_expect_success 'setup remote repository' '
3636
mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
3737
'
3838

39+
setup_askpass_helper
40+
3941
cat >exp <<EOF
4042
GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
4143
POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200
@@ -269,5 +271,29 @@ test_expect_success 'http push respects GIT_COMMITTER_* in reflog' '
269271
test_cmp expect actual
270272
'
271273

274+
test_expect_success 'push over smart http with auth' '
275+
cd "$ROOT_PATH/test_repo_clone" &&
276+
echo push-auth-test >expect &&
277+
test_commit push-auth-test &&
278+
set_askpass user@host &&
279+
git push "$HTTPD_URL"/auth/smart/test_repo.git &&
280+
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
281+
log -1 --format=%s >actual &&
282+
expect_askpass both user@host &&
283+
test_cmp expect actual
284+
'
285+
286+
test_expect_success 'push to auth-only-for-push repo' '
287+
cd "$ROOT_PATH/test_repo_clone" &&
288+
echo push-half-auth >expect &&
289+
test_commit push-half-auth &&
290+
set_askpass user@host &&
291+
git push "$HTTPD_URL"/auth-push/smart/test_repo.git &&
292+
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
293+
log -1 --format=%s >actual &&
294+
expect_askpass both user@host &&
295+
test_cmp expect actual
296+
'
297+
272298
stop_httpd
273299
test_done

0 commit comments

Comments
 (0)