Skip to content

Commit 294cbb9

Browse files
committed
Use GIT_CREDENTIAL_CALLBACK=0 to disable use of credential callback
1 parent 760ca88 commit 294cbb9

9 files changed

Lines changed: 71 additions & 18 deletions

File tree

docs/env_vars.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ time.
1919
`GIT_COMMITTER_NAME`
2020
: The human-readable name for the "committer" field.
2121

22+
`GIT_CREDENTIAL_CALLBACK`
23+
: By default, `git2cpp` will prompt the user to enter a username and password if they are required
24+
for remote authentication, using a
25+
[libgit2 credential callback](https://libgit2.org/docs/reference/main/credential/git_credential_acquire_cb.html).
26+
To disable the callback use `export GIT_CREDENTIAL_CALLBACK=0`.
27+
2228

2329
## In WebAssembly build only
2430

src/subcommand/clone_subcommand.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ void clone_subcommand::run()
5353
checkout_opts.progress_cb = checkout_progress;
5454
checkout_opts.progress_payload = &pd;
5555
clone_opts.checkout_opts = checkout_opts;
56-
clone_opts.fetch_opts.callbacks.credentials = user_credentials;
56+
if (want_user_credentials())
57+
{
58+
clone_opts.fetch_opts.callbacks.credentials = user_credentials;
59+
}
5760
clone_opts.fetch_opts.callbacks.sideband_progress = sideband_progress;
5861
clone_opts.fetch_opts.callbacks.transfer_progress = fetch_progress;
5962
clone_opts.fetch_opts.callbacks.payload = &pd;

src/subcommand/fetch_subcommand.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ void fetch_subcommand::run()
4444

4545
git_indexer_progress pd = {0};
4646
git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
47-
fetch_opts.callbacks.credentials = user_credentials;
47+
if (want_user_credentials())
48+
{
49+
fetch_opts.callbacks.credentials = user_credentials;
50+
}
4851
fetch_opts.callbacks.sideband_progress = sideband_progress;
4952
fetch_opts.callbacks.transfer_progress = fetch_progress;
5053
fetch_opts.callbacks.payload = &pd;

src/subcommand/push_subcommand.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,10 @@ void push_subcommand::run()
172172
auto remote = repo.find_remote(remote_name);
173173

174174
git_push_options push_opts = GIT_PUSH_OPTIONS_INIT;
175-
push_opts.callbacks.credentials = user_credentials;
175+
if (want_user_credentials())
176+
{
177+
push_opts.callbacks.credentials = user_credentials;
178+
}
176179
push_opts.callbacks.push_transfer_progress = push_transfer_progress;
177180
push_opts.callbacks.push_update_reference = push_update_reference;
178181

src/utils/credentials.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,9 @@ int user_credentials(
4747
giterr_set_str(GIT_ERROR_HTTP, "Unexpected credentials request");
4848
return GIT_ERROR;
4949
}
50+
51+
bool want_user_credentials()
52+
{
53+
const char* env_var = std::getenv("GIT_CREDENTIAL_CALLBACK");
54+
return env_var == nullptr || std::string_view(env_var) != "0";
55+
}

src/utils/credentials.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ int user_credentials(
1111
unsigned int allowed_types,
1212
void* payload
1313
);
14+
15+
bool want_user_credentials();

src/wasm/stream.cpp

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# include <emscripten.h>
99

1010
# include "../utils/common.hpp"
11+
# include "../utils/credentials.hpp"
1112
# include "constants.hpp"
1213
# include "read_buffer.hpp"
1314
# include "response.hpp"
@@ -464,28 +465,38 @@ static int create_credential(wasm_http_stream* stream)
464465
}
465466
subtransport->m_authorization_header = "";
466467

467-
// Check that response headers show support for 'www-authenticate: Basic'.
468-
if (!stream->m_response.has_header_starts_with("www-authenticate", "Basic"))
468+
if (!want_user_credentials())
469469
{
470-
git_error_set(
471-
GIT_ERROR_HTTP,
472-
"remote host for request %s does not support Basic authentication",
473-
stream->m_unconverted_url.c_str()
474-
);
475-
return -1;
470+
// Check that response headers show support for 'www-authenticate: Basic'.
471+
if (!stream->m_response.has_header_starts_with("www-authenticate", "Basic"))
472+
{
473+
git_error_set(
474+
GIT_ERROR_HTTP,
475+
"remote host for request %s does not support Basic authentication",
476+
stream->m_unconverted_url.c_str()
477+
);
478+
return -1;
479+
}
476480
}
477481

478482
// Get credentials from user via libgit2 registered callback.
479-
if (git_transport_smart_credentials(
480-
&subtransport->m_credential,
481-
subtransport->m_owner,
482-
nullptr,
483-
GIT_CREDENTIAL_USERPASS_PLAINTEXT
484-
)
483+
int err;
484+
if ((err = git_transport_smart_credentials(
485+
&subtransport->m_credential,
486+
subtransport->m_owner,
487+
nullptr,
488+
GIT_CREDENTIAL_USERPASS_PLAINTEXT
489+
))
485490
< 0)
486491
{
492+
if (err == GIT_PASSTHROUGH)
493+
{
494+
// Use same error message as libgit2
495+
git_error_set(GIT_ERROR_HTTP, "remote authentication required but no callback set");
496+
}
497+
487498
// credentials_callback will have set git error.
488-
return -1;
499+
return err;
489500
}
490501

491502
if (subtransport->m_credential->credtype != GIT_CREDENTIAL_USERPASS_PLAINTEXT)

test/conftest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ def commit_env_config(monkeypatch):
5151
monkeypatch.setenv(key, value)
5252

5353

54+
@pytest.fixture
55+
def disable_credential_callback(monkeypatch):
56+
if GIT2CPP_TEST_WASM:
57+
subprocess.run(["export", "GIT_CREDENTIAL_CALLBACK=0"], check=True)
58+
else:
59+
monkeypatch.setenv("GIT_CREDENTIAL_CALLBACK", "0")
60+
61+
5462
@pytest.fixture
5563
def repo_init_with_commit(commit_env_config, git2cpp_path, tmp_path):
5664
cmd_init = [git2cpp_path, "init", ".", "-b", "main"]

test/test_clone.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,17 @@ def test_clone_private_repo_fails_on_no_password(
128128
assert p_clone.stdout.count("Password:") == 1
129129

130130

131+
def test_clone_private_repo_fails_with_no_credential_callback(
132+
git2cpp_path, tmp_path, run_in_tmp_path, disable_credential_callback
133+
):
134+
clone_cmd = [git2cpp_path, "clone", "https://github.com/QuantStack/git2cpp-test-private"]
135+
p_clone = subprocess.run(clone_cmd, capture_output=True, text=True)
136+
137+
assert p_clone.returncode != 0
138+
assert "Cloning into 'git2cpp-test-private'..." in p_clone.stdout
139+
assert "error: remote authentication required but no callback set" in p_clone.stderr
140+
141+
131142
@pytest.mark.parametrize("protocol", ["http", "https"])
132143
def test_clone_gitlab(git2cpp_path, tmp_path, run_in_tmp_path, protocol):
133144
repo_url = f"{protocol}://gitlab.quantstack.net/ianthomas23_group/cockle-playground"

0 commit comments

Comments
 (0)