-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
/
callbacks.jl
269 lines (238 loc) · 11.5 KB
/
callbacks.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# This file is a part of Julia. License is MIT: http:https://julialang.org/license
"""Mirror callback function
Function sets `+refs/*:refs/*` refspecs and `mirror` flag for remote reference.
"""
function mirror_callback(remote::Ptr{Ptr{Void}}, repo_ptr::Ptr{Void},
name::Cstring, url::Cstring, payload::Ptr{Void})
# Create the remote with a mirroring url
fetch_spec = "+refs/*:refs/*"
err = ccall((:git_remote_create_with_fetchspec, :libgit2), Cint,
(Ptr{Ptr{Void}}, Ptr{Void}, Cstring, Cstring, Cstring),
remote, repo_ptr, name, url, fetch_spec)
err != 0 && return Cint(err)
# And set the configuration option to true for the push command
config = GitConfig(GitRepo(repo_ptr,false))
name_str = unsafe_string(name)
err= try set!(config, "remote.$name_str.mirror", true)
catch -1
finally close(config)
end
err != 0 && return Cint(err)
return Cint(0)
end
function authenticate_ssh(creds::SSHCredentials, libgit2credptr::Ptr{Ptr{Void}},
username_ptr, schema, host)
isusedcreds = checkused!(creds)
errcls, errmsg = Error.last_error()
if errcls != Error.None
# Check if we used ssh-agent
if creds.usesshagent == "U"
creds.usesshagent = "E" # reported ssh-agent error, disables ssh agent use for the future
end
end
# first try ssh-agent if credentials support its usage
if creds.usesshagent === nothing || creds.usesshagent == "Y" || creds.usesshagent == "U"
err = ccall((:git_cred_ssh_key_from_agent, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring), libgit2credptr, username_ptr)
creds.usesshagent = "U" # used ssh-agent only one time
err == 0 && return Cint(0)
end
if creds.prompt_if_incorrect
# if username is not provided, then prompt for it
username = if username_ptr == Cstring(C_NULL)
uname = creds.user # check if credentials were already used
!isusedcreds ? uname : prompt("Username for '$schema$host'", default=uname)
else
unsafe_string(username_ptr)
end
isempty(username) && return Cint(Error.EAUTH)
# For SSH we need a private key location
privatekey = if haskey(ENV,"SSH_KEY_PATH")
ENV["SSH_KEY_PATH"]
else
keydefpath = creds.prvkey # check if credentials were already used
keydefpath === nothing && (keydefpath = "")
if isempty(keydefpath) || isusedcreds
defaultkeydefpath = joinpath(homedir(),".ssh","id_rsa")
if isempty(keydefpath) && isfile(defaultkeydefpath)
keydefpath = defaultkeydefpath
else
keydefpath =
prompt("Private key location for '$schema$username@$host'", default=keydefpath)
end
end
keydefpath
end
# If the private key changed, invalidate the cached public key
(privatekey != creds.prvkey) &&
(creds.pubkey = "")
# For SSH we need a public key location, look for environment vars SSH_* as well
publickey = if haskey(ENV,"SSH_PUB_KEY_PATH")
ENV["SSH_PUB_KEY_PATH"]
else
keydefpath = creds.pubkey # check if credentials were already used
keydefpath === nothing && (keydefpath = "")
if isempty(keydefpath) || isusedcreds
if isempty(keydefpath)
keydefpath = privatekey*".pub"
end
if !isfile(keydefpath)
prompt("Public key location for '$schema$username@$host'", default=keydefpath)
end
end
keydefpath
end
passphrase_required = true
if !isfile(privatekey)
warn("Private key not found")
else
# In encrypted private keys, the second line is "Proc-Type: 4,ENCRYPTED"
open(privatekey) do f
passphrase_required = (readline(f); chomp(readline(f)) == "Proc-Type: 4,ENCRYPTED")
end
end
passphrase = if haskey(ENV,"SSH_KEY_PASS")
ENV["SSH_KEY_PASS"]
else
passdef = creds.pass # check if credentials were already used
passdef === nothing && (passdef = "")
if passphrase_required && (isempty(passdef) || isusedcreds)
if is_windows()
passdef = Base.winprompt(
"Your SSH Key requires a password, please enter it now:",
"Passphrase required", privatekey; prompt_username = false)
isnull(passdef) && return Cint(Error.EAUTH)
passdef = Base.get(passdef)[2]
else
passdef = prompt("Passphrase for $privatekey", password=true)
end
end
passdef
end
((creds.user != username) || (creds.pass != passphrase) ||
(creds.prvkey != privatekey) || (creds.pubkey != publickey)) && reset!(creds)
creds.user = username # save credentials
creds.prvkey = privatekey # save credentials
creds.pubkey = publickey # save credentials
creds.pass = passphrase
else
isusedcreds && return Cint(Error.EAUTH)
end
err = ccall((:git_cred_ssh_key_new, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring, Cstring, Cstring, Cstring),
libgit2credptr, creds.user, creds.pubkey, creds.prvkey, creds.pass)
return err
end
function authenticate_userpass(creds::UserPasswordCredentials, libgit2credptr::Ptr{Ptr{Void}},
schema, host, urlusername)
isusedcreds = checkused!(creds)
if creds.prompt_if_incorrect
username = creds.user
userpass = creds.pass
(username === nothing) && (username = "")
(userpass === nothing) && (userpass = "")
if is_windows()
if isempty(username) || isempty(userpass) || isusedcreds
res = Base.winprompt("Please enter your credentials for '$schema$host'", "Credentials required",
username === nothing || isempty(username) ?
urlusername : username; prompt_username = true)
isnull(res) && return Cint(Error.EAUTH)
username, userpass = Base.get(res)
end
elseif isusedcreds
username = prompt("Username for '$schema$host'", default = isempty(username) ?
urlusername : username)
userpass = prompt("Password for '$schema$username@$host'", password=true)
end
((creds.user != username) || (creds.pass != userpass)) && reset!(creds)
creds.user = username # save credentials
creds.pass = userpass # save credentials
isempty(username) && isempty(userpass) && return Cint(Error.EAUTH)
else
isusedcreds && return Cint(Error.EAUTH)
end
err = ccall((:git_cred_userpass_plaintext_new, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring, Cstring),
libgit2credptr, creds.user, creds.pass)
err == 0 && return Cint(0)
end
"""Credentials callback function
Function provides different credential acquisition functionality w.r.t. a connection protocol.
If a payload is provided then `payload_ptr` should contain a `LibGit2.AbstractCredentials` object.
For `LibGit2.Consts.CREDTYPE_USERPASS_PLAINTEXT` type, if the payload contains fields:
`user` & `pass`, they are used to create authentication credentials.
Empty `user` name and `pass`word trigger an authentication error.
For `LibGit2.Consts.CREDTYPE_SSH_KEY` type, if the payload contains fields:
`user`, `prvkey`, `pubkey` & `pass`, they are used to create authentication credentials.
Empty `user` name triggers an authentication error.
Credentials are checked in the following order (if supported):
- ssh key pair (`ssh-agent` if specified in payload's `usesshagent` field)
- plain text
**Note**: Due to the specifics of the `libgit2` authentication procedure, when
authentication fails, this function is called again without any indication whether
authentication was successful or not. To avoid an infinite loop from repeatedly
using the same faulty credentials, the `checkused!` function can be called. This
function returns `true` if the credentials were used.
Using credentials triggers a user prompt for (re)entering required information.
`UserPasswordCredentials` and `CachedCredentials` are implemented using a call
counting strategy that prevents repeated usage of faulty credentials.
"""
function credentials_callback(libgit2credptr::Ptr{Ptr{Void}}, url_ptr::Cstring,
username_ptr::Cstring,
allowed_types::Cuint, payload_ptr::Ptr{Void})
err = 0
url = unsafe_string(url_ptr)
# parse url for schema and host
urlparts = match(URL_REGEX, url)
schema = urlparts[:scheme] === nothing ? "" : urlparts[:scheme] * ":https://"
urlusername = urlparts[:user] === nothing ? "" : urlparts[:user]
host = urlparts[:host]
# get credentials object from payload pointer
@assert payload_ptr != C_NULL
creds = unsafe_pointer_to_objref(payload_ptr)
explicit = !isnull(creds[]) && !isa(Base.get(creds[]), CachedCredentials)
# use ssh key or ssh-agent
if isset(allowed_types, Cuint(Consts.CREDTYPE_SSH_KEY))
sshcreds = get_creds!(creds, "ssh:https://$host", reset!(SSHCredentials(true), -1))
if isa(sshcreds, SSHCredentials)
err = authenticate_ssh(sshcreds, libgit2credptr, username_ptr, schema, host)
err == 0 && return err
end
end
if isset(allowed_types, Cuint(Consts.CREDTYPE_USERPASS_PLAINTEXT))
defaultcreds = reset!(UserPasswordCredentials(true), -1)
credid = "$schema$host"
upcreds = get_creds!(creds, credid, defaultcreds)
# If there were stored SSH credentials, but we ended up here that must
# mean that something went wrong. Replace the SSH credentials by user/pass
# credentials
if !isa(upcreds, UserPasswordCredentials)
upcreds = defaultcreds
isa(Base.get(creds[]), CachedCredentials) && (Base.get(creds[]).creds[credid] = upcreds)
end
return authenticate_userpass(upcreds, libgit2credptr, schema, host, urlusername)
end
# No authentication method we support succeeded. The most likely cause is
# that explicit credentials were passed in, but said credentials are incompatible
# with the remote host.
if err == 0
if explicit
warn("The explicitly provided credentials were incompatible with " *
"the server's supported authentication methods")
end
err = Cint(Error.EAUTH)
end
return Cint(err)
end
function fetchhead_foreach_callback(ref_name::Cstring, remote_url::Cstring,
oid::Ptr{GitHash}, is_merge::Cuint, payload::Ptr{Void})
fhead_vec = unsafe_pointer_to_objref(payload)::Vector{FetchHead}
push!(fhead_vec, FetchHead(unsafe_string(ref_name), unsafe_string(remote_url), GitHash(oid), is_merge == 1))
return Cint(0)
end
"C function pointer for `mirror_callback`"
mirror_cb() = cfunction(mirror_callback, Cint, (Ptr{Ptr{Void}}, Ptr{Void}, Cstring, Cstring, Ptr{Void}))
"C function pointer for `credentials_callback`"
credentials_cb() = cfunction(credentials_callback, Cint, (Ptr{Ptr{Void}}, Cstring, Cstring, Cuint, Ptr{Void}))
"C function pointer for `fetchhead_foreach_callback`"
fetchhead_foreach_cb() = cfunction(fetchhead_foreach_callback, Cint, (Cstring, Cstring, Ptr{GitHash}, Cuint, Ptr{Void}))