Skip to content

Commit 3de8103

Browse files
Merge pull request #639 from libgit2/git_repo_with_self_signed_certs
Remote: Expose `certificate_check` callback
2 parents 04fe32f + 989d6ef commit 3de8103

File tree

4 files changed

+108
-20
lines changed

4 files changed

+108
-20
lines changed

ext/rugged/rugged.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ struct rugged_remote_cb_payload
159159
VALUE transfer_progress;
160160
VALUE update_tips;
161161
VALUE credentials;
162+
VALUE certificate_check;
162163
VALUE result;
163164
int exception;
164165
};

ext/rugged/rugged_remote.c

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ extern VALUE rb_cRuggedRepo;
2929
extern VALUE rb_eRuggedError;
3030
VALUE rb_cRuggedRemote;
3131

32-
#define RUGGED_REMOTE_CALLBACKS_INIT {1, progress_cb, NULL, credentials_cb, NULL, transfer_progress_cb, update_tips_cb, NULL, NULL, push_update_reference_cb, NULL}
33-
3432
static int progress_cb(const char *str, int len, void *data)
3533
{
3634
struct rugged_remote_cb_payload *payload = data;
@@ -96,6 +94,27 @@ static int update_tips_cb(const char *refname, const git_oid *src, const git_oid
9694
return payload->exception ? GIT_ERROR : GIT_OK;
9795
}
9896

97+
static int certificate_check_cb(git_cert *cert, int valid, const char *host, void *data)
98+
{
99+
struct rugged_remote_cb_payload *payload = data;
100+
VALUE args = rb_ary_new2(3);
101+
VALUE ret;
102+
103+
if (NIL_P(payload->certificate_check))
104+
return valid ? 0 : GIT_ECERTIFICATE;
105+
106+
rb_ary_push(args, payload->certificate_check);
107+
rb_ary_push(args, valid ? Qtrue : Qfalse);
108+
rb_ary_push(args, rb_str_new_utf8(host));
109+
110+
ret = rb_protect(rugged__block_yield_splat, args, &payload->exception);
111+
112+
if (payload->exception)
113+
return GIT_ERROR;
114+
115+
return rugged_parse_bool(ret) ? GIT_OK : GIT_ECERTIFICATE;
116+
}
117+
99118
struct extract_cred_args
100119
{
101120
VALUE rb_callback;
@@ -155,11 +174,9 @@ static int credentials_cb(
155174
return payload->exception ? GIT_ERROR : GIT_OK;
156175
}
157176

158-
#define CALLABLE_OR_RAISE(ret, rb_options, name) \
159-
do { \
160-
ret = rb_hash_aref(rb_options, CSTR2SYM(name)); \
161-
\
162-
if (!NIL_P(ret) && !rb_respond_to(ret, rb_intern("call"))) \
177+
#define CALLABLE_OR_RAISE(ret, name) \
178+
do { \
179+
if (!rb_respond_to(ret, rb_intern("call"))) \
163180
rb_raise(rb_eArgError, "Expected a Proc or an object that responds to #call (:" name " )."); \
164181
} while (0);
165182

@@ -168,16 +185,39 @@ void rugged_remote_init_callbacks_and_payload_from_options(
168185
git_remote_callbacks *callbacks,
169186
struct rugged_remote_cb_payload *payload)
170187
{
171-
git_remote_callbacks prefilled = RUGGED_REMOTE_CALLBACKS_INIT;
172-
173-
prefilled.payload = payload;
174-
memcpy(callbacks, &prefilled, sizeof(git_remote_callbacks));
188+
callbacks->payload = payload;
189+
callbacks->push_update_reference = push_update_reference_cb;
175190

176191
if (!NIL_P(rb_options)) {
177-
CALLABLE_OR_RAISE(payload->update_tips, rb_options, "update_tips");
178-
CALLABLE_OR_RAISE(payload->progress, rb_options, "progress");
179-
CALLABLE_OR_RAISE(payload->transfer_progress, rb_options, "transfer_progress");
180-
CALLABLE_OR_RAISE(payload->credentials, rb_options, "credentials");
192+
payload->progress = rb_hash_aref(rb_options, CSTR2SYM("progress"));
193+
if (!NIL_P(payload->progress)) {
194+
CALLABLE_OR_RAISE(payload->progress, "progress");
195+
callbacks->sideband_progress = progress_cb;
196+
}
197+
198+
payload->credentials = rb_hash_aref(rb_options, CSTR2SYM("credentials"));
199+
if (!NIL_P(payload->credentials)) {
200+
CALLABLE_OR_RAISE(payload->credentials, "credentials");
201+
callbacks->credentials = credentials_cb;
202+
}
203+
204+
payload->certificate_check = rb_hash_aref(rb_options, CSTR2SYM("certificate_check"));
205+
if (!NIL_P(payload->certificate_check)) {
206+
CALLABLE_OR_RAISE(payload->certificate_check, "certificate_check");
207+
callbacks->certificate_check = certificate_check_cb;
208+
}
209+
210+
payload->transfer_progress = rb_hash_aref(rb_options, CSTR2SYM("transfer_progress"));
211+
if (!NIL_P(payload->transfer_progress)) {
212+
CALLABLE_OR_RAISE(payload->transfer_progress, "transfer_progress");
213+
callbacks->transfer_progress = transfer_progress_cb;
214+
}
215+
216+
payload->update_tips = rb_hash_aref(rb_options, CSTR2SYM("update_tips"));
217+
if (!NIL_P(payload->update_tips)) {
218+
CALLABLE_OR_RAISE(payload->update_tips, "update_tips");
219+
callbacks->update_tips = update_tips_cb;
220+
}
181221
}
182222
}
183223

@@ -274,7 +314,7 @@ static VALUE rb_git_remote_ls(int argc, VALUE *argv, VALUE self)
274314
git_strarray custom_headers = {0};
275315
const git_remote_head **heads;
276316

277-
struct rugged_remote_cb_payload payload = { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil, 0 };
317+
struct rugged_remote_cb_payload payload = { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil, Qnil, 0 };
278318

279319
VALUE rb_options;
280320

@@ -471,7 +511,7 @@ static VALUE rb_git_remote_check_connection(int argc, VALUE *argv, VALUE self)
471511
git_remote *remote;
472512
git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
473513
git_strarray custom_headers = {0};
474-
struct rugged_remote_cb_payload payload = { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil, 0 };
514+
struct rugged_remote_cb_payload payload = { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil, Qnil, 0 };
475515
VALUE rb_direction, rb_options;
476516
ID id_direction;
477517
int error, direction;
@@ -537,6 +577,11 @@ static VALUE rb_git_remote_check_connection(int argc, VALUE *argv, VALUE self)
537577
* A callback that will be executed each time a reference is updated locally. It will be
538578
* passed the +refname+, +old_oid+ and +new_oid+.
539579
*
580+
* :certificate_check ::
581+
* A callback that will be executed each time we validate a certificate using https. It
582+
* will be passed the +valid+, +host_name+ and the callback should return a true/false to
583+
* indicate if the certificate has been validated.
584+
*
540585
* :message ::
541586
* The message to insert into the reflogs. Defaults to "fetch".
542587
*
@@ -559,7 +604,7 @@ static VALUE rb_git_remote_fetch(int argc, VALUE *argv, VALUE self)
559604
git_strarray refspecs;
560605
git_fetch_options opts = GIT_FETCH_OPTIONS_INIT;
561606
const git_transfer_progress *stats;
562-
struct rugged_remote_cb_payload payload = { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil, 0 };
607+
struct rugged_remote_cb_payload payload = { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil, Qnil, 0 };
563608

564609
char *log_message = NULL;
565610
int error;
@@ -650,7 +695,7 @@ static VALUE rb_git_remote_push(int argc, VALUE *argv, VALUE self)
650695

651696
int error = 0;
652697

653-
struct rugged_remote_cb_payload payload = { Qnil, Qnil, Qnil, Qnil, Qnil, rb_hash_new(), 0 };
698+
struct rugged_remote_cb_payload payload = { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil, rb_hash_new(), 0 };
654699

655700
rb_scan_args(argc, argv, "01:", &rb_refspecs, &rb_options);
656701

ext/rugged/rugged_repo.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ static VALUE rb_git_repo_clone_at(int argc, VALUE *argv, VALUE klass)
485485
{
486486
VALUE url, local_path, rb_options_hash;
487487
git_clone_options options = GIT_CLONE_OPTIONS_INIT;
488-
struct rugged_remote_cb_payload remote_payload = { Qnil, Qnil, Qnil, Qnil, 0 };
488+
struct rugged_remote_cb_payload remote_payload = { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil, Qnil, 0 };
489489
git_repository *repo;
490490
int error;
491491

test/online/fetch_test.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,48 @@ def test_fetch_over_https
2828
"refs/tags/commit_tree"
2929
], @repo.refs.map(&:name).sort
3030
end
31+
32+
def test_fetch_over_https_with_certificate_callback
33+
@repo.remotes.create("origin", "https://github.com/libgit2/TestGitRepository.git")
34+
35+
args = {}
36+
@repo.fetch("origin", {
37+
certificate_check: lambda { |valid, host|
38+
args[:valid] = valid
39+
args[:host] = host
40+
41+
true
42+
}
43+
})
44+
45+
assert_equal({ valid: true, host: "github.com" }, args)
46+
end
47+
48+
def test_fetch_over_https_with_certificate_callback_fail
49+
@repo.remotes.create("origin", "https://github.com/libgit2/TestGitRepository.git")
50+
51+
exception = assert_raises Rugged::NetworkError do
52+
@repo.fetch("origin", {
53+
certificate_check: lambda { |valid, host| false }
54+
})
55+
end
56+
57+
assert_equal "user cancelled certificate check", exception.message
58+
end
59+
60+
def test_fetch_over_https_with_certificate_callback_exception
61+
@repo.remotes.create("origin", "https://github.com/libgit2/TestGitRepository.git")
62+
63+
exception = assert_raises RuntimeError do
64+
@repo.fetch("origin", {
65+
certificate_check: lambda { |valid, host|
66+
raise "Exception from callback"
67+
}
68+
})
69+
end
70+
71+
assert_equal "Exception from callback", exception.message
72+
end
3173
end
3274

3375
if Rugged.features.include?(:ssh) && ssh_creds?

0 commit comments

Comments
 (0)