Skip to content

Commit 7d9483c

Browse files
committed
Merge branch 'jk/maint-http-half-auth-push' into maint-1.7.11
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 92c830d + b81401c commit 7d9483c

File tree

9 files changed

+171
-106
lines changed

9 files changed

+171
-106
lines changed

http.c

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,33 @@ char *get_remote_object_url(const char *url, const char *hex,
744744
return strbuf_detach(&buf, NULL);
745745
}
746746

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

792819
if (start_active_slot(slot)) {
793820
run_active_slot(slot);
794-
if (results.curl_result == CURLE_OK)
795-
ret = HTTP_OK;
796-
else if (missing_target(&results))
797-
ret = HTTP_MISSING_TARGET;
798-
else if (results.http_code == 401) {
799-
if (http_auth.username && http_auth.password) {
800-
credential_reject(&http_auth);
801-
ret = HTTP_NOAUTH;
802-
} else {
803-
credential_fill(&http_auth);
804-
init_curl_http_auth(slot->curl);
805-
ret = HTTP_REAUTH;
806-
}
807-
} else {
808-
if (!curl_errorstr[0])
809-
strlcpy(curl_errorstr,
810-
curl_easy_strerror(results.curl_result),
811-
sizeof(curl_errorstr));
812-
ret = HTTP_ERROR;
813-
}
821+
ret = handle_curl_result(slot);
814822
} else {
815823
error("Unable to start HTTP request for %s", url);
816824
ret = HTTP_START_FAILED;
@@ -819,9 +827,6 @@ static int http_request(const char *url, void *result, int target, int options)
819827
curl_slist_free_all(headers);
820828
strbuf_release(&buf);
821829

822-
if (ret == HTTP_OK)
823-
credential_approve(&http_auth);
824-
825830
return ret;
826831
}
827832

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
@@ -163,3 +163,42 @@ test_http_push_nonff() {
163163
test_i18ngrep "Updates were rejected because" output
164164
'
165165
}
166+
167+
setup_askpass_helper() {
168+
test_expect_success 'setup askpass helper' '
169+
write_script "$TRASH_DIRECTORY/askpass" <<-\EOF &&
170+
echo >>"$TRASH_DIRECTORY/askpass-query" "askpass: $*" &&
171+
cat "$TRASH_DIRECTORY/askpass-response"
172+
EOF
173+
GIT_ASKPASS="$TRASH_DIRECTORY/askpass" &&
174+
export GIT_ASKPASS &&
175+
export TRASH_DIRECTORY
176+
'
177+
}
178+
179+
set_askpass() {
180+
>"$TRASH_DIRECTORY/askpass-query" &&
181+
echo "$*" >"$TRASH_DIRECTORY/askpass-response"
182+
}
183+
184+
expect_askpass() {
185+
dest=$HTTPD_DEST
186+
{
187+
case "$1" in
188+
none)
189+
;;
190+
pass)
191+
echo "askpass: Password for 'http://$2@$dest': "
192+
;;
193+
both)
194+
echo "askpass: Username for 'http://$dest': "
195+
echo "askpass: Password for 'http://$2@$dest': "
196+
;;
197+
*)
198+
false
199+
;;
200+
esac
201+
} >"$TRASH_DIRECTORY/askpass-expect" &&
202+
test_cmp "$TRASH_DIRECTORY/askpass-expect" \
203+
"$TRASH_DIRECTORY/askpass-query"
204+
}

t/lib-httpd/apache.conf

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,22 @@ ErrorLog error.log
4343
</IfVersion>
4444

4545
Alias /dumb/ www/
46-
Alias /auth/ www/auth/
46+
Alias /auth/dumb/ www/auth/dumb/
4747

48-
<Location /smart/>
48+
<LocationMatch /smart/>
4949
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
5050
SetEnv GIT_HTTP_EXPORT_ALL
51-
</Location>
52-
<Location /smart_noexport/>
51+
</LocationMatch>
52+
<LocationMatch /smart_noexport/>
5353
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
54-
</Location>
55-
<Location /smart_custom_env/>
54+
</LocationMatch>
55+
<LocationMatch /smart_custom_env/>
5656
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
5757
SetEnv GIT_HTTP_EXPORT_ALL
5858
SetEnv GIT_COMMITTER_NAME "Custom User"
5959
SetEnv GIT_COMMITTER_EMAIL [email protected]
60-
</Location>
61-
ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/
62-
ScriptAlias /smart_noexport/ ${GIT_EXEC_PATH}/git-http-backend/
63-
ScriptAlias /smart_custom_env/ ${GIT_EXEC_PATH}/git-http-backend/
60+
</LocationMatch>
61+
ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
6462
<Directory ${GIT_EXEC_PATH}>
6563
Options None
6664
</Directory>
@@ -91,6 +89,13 @@ SSLEngine On
9189
Require valid-user
9290
</Location>
9391

92+
<LocationMatch "^/auth-push/.*/git-receive-pack$">
93+
AuthType Basic
94+
AuthName "git-auth"
95+
AuthUserFile passwd
96+
Require valid-user
97+
</LocationMatch>
98+
9499
<IfDefine DAV>
95100
LoadModule dav_module modules/mod_dav.so
96101
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
@@ -266,5 +268,29 @@ test_expect_success 'http push respects GIT_COMMITTER_* in reflog' '
266268
test_cmp expect actual
267269
'
268270

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

0 commit comments

Comments
 (0)