From 4ef3be18cce6b0645a4bb8d00c4976f00c6f3af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= <cmn@dwim.me> Date: Sat, 12 Apr 2014 17:13:15 +0200 Subject: [PATCH] Remote: support credentials via CFFI --- pygit2/decl.h | 18 ++++++++++++++++++ pygit2/remote.py | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/pygit2/decl.h b/pygit2/decl.h index e0ad826..9c4e052 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -60,6 +60,13 @@ typedef enum { GIT_DIRECTION_PUSH = 1 } git_direction; +typedef enum { + GIT_CREDTYPE_USERPASS_PLAINTEXT = ..., + GIT_CREDTYPE_SSH_KEY = ..., + GIT_CREDTYPE_SSH_CUSTOM = ..., + GIT_CREDTYPE_DEFAULT = ..., +} git_credtype_t; + typedef struct git_remote_callbacks { unsigned int version; int (*progress)(const char *str, int len, void *data); @@ -125,3 +132,14 @@ int git_refspec_dst_matches(const git_refspec *refspec, const char *refname); int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name); int git_refspec_rtransform(char *out, size_t outlen, const git_refspec *spec, const char *name); + +int git_cred_userpass_plaintext_new( + git_cred **out, + const char *username, + const char *password); +int git_cred_ssh_key_new( + git_cred **out, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase); diff --git a/pygit2/remote.py b/pygit2/remote.py index 28a4860..eca3dfc 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -57,11 +57,13 @@ class Remote(object): self._repo = repo self._remote = ptr + self._stored_exception = None # Build the callback structure callbacks = ffi.new('git_remote_callbacks *') callbacks.version = 1 callbacks.transfer_progress = self._transfer_progress_cb + callbacks.credentials = self._credentials_cb # We need to make sure that this handle stays alive self._self_handle = ffi.new_handle(self) callbacks.payload = self._self_handle @@ -103,8 +105,9 @@ class Remote(object): check_error(err) def fetch(self): + self._stored_exception = None err = C.git_remote_fetch(self._remote) - if err == C.GIT_EUSER: + if self._stored_exception: raise self._stored_exception check_error(err) @@ -205,3 +208,46 @@ class Remote(object): return C.GIT_EUSER return 0 + + @ffi.callback('int (*credentials)(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data)') + def _credentials_cb(cred_out, url, username, allowed, data): + self = ffi.from_handle(data) + + if not hasattr(self, 'credentials'): + return 0 + + try: + url_str = maybe_string(url) + username_str = maybe_string(username) + + creds = self.credentials(url_str, username_str, allowed) + + if not hasattr(creds, 'credential_type') or not hasattr(creds, 'credential_tuple'): + raise TypeError("credential does not implement interface") + + cred_type = creds.credential_type + + if not (allowed & cred_type): + raise TypeError("invalid credential type") + + ccred = ffi.new('git_cred **') + if cred_type == C.GIT_CREDTYPE_USERPASS_PLAINTEXT: + name, passwd = creds.credential_tuple + err = C.git_cred_userpass_plaintext_new(ccred, to_str(name), to_str(passwd)) + + elif cred_type == C.GIT_CREDTYPE_SSH_KEY: + name, pubkey, privkey, passphrase = creds.credential_tuple + err = C.git_cred_ssh_key_new(ccred, to_str(name),to_str(pubkey), + to_str(privkey), to_str(passphrase)) + + else: + raise TypeError("unsupported credential type") + + check_error(err) + cred_out[0] = ccred[0] + + except Exception, e: + self._stored_exception = e + return C.GIT_EUSER + + return 0