forked from JuliaLang/julia
-
Notifications
You must be signed in to change notification settings - Fork 0
/
callbacks.jl
228 lines (201 loc) · 9.96 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
# 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))
name_str = unsafe_string(name)
err= try set!(config, "remote.$name_str.mirror", true)
catch -1
finally finalize(config)
end
err != 0 && return Cint(err)
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(cred::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(urlmatcher, url)
schema = urlparts.captures[1]
host = urlparts.captures[5]
schema = schema === nothing ? "" : schema*":https://"
# get credentials object from payload pointer
creds = nothing
creds_are_temp = true
if payload_ptr != C_NULL
tmpobj = unsafe_pointer_to_objref(payload_ptr)
if isa(tmpobj, AbstractCredentials)
creds = tmpobj
creds_are_temp = false
end
end
isusedcreds = checkused!(creds)
try
# use ssh key or ssh-agent
if isset(allowed_types, Cuint(Consts.CREDTYPE_SSH_KEY))
creds == nothing && (creds = SSHCredentials())
credid = "ssh:https://$host"
# first try ssh-agent if credentials support its usage
if creds[:usesshagent, credid] === nothing || creds[:usesshagent, credid] == "Y"
err = ccall((:git_cred_ssh_key_from_agent, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring), cred, username_ptr)
creds[:usesshagent, credid] = "U" # used ssh-agent only one time
err == 0 && return Cint(0)
end
errcls, errmsg = Error.last_error()
if errcls != Error.None
# Check if we used ssh-agent
if creds[:usesshagent, credid] == "U"
println("ERROR: $errmsg ssh-agent")
creds[:usesshagent, credid] = "E" # reported ssh-agent error
else
println("ERROR: $errmsg")
end
flush(STDOUT)
end
# if username is not provided, then prompt for it
username = if username_ptr == Cstring(C_NULL)
uname = creds[:user, credid] # check if credentials were already used
uname !== nothing && !isusedcreds ? uname : prompt("Username for '$schema$host'")
else
unsafe_string(username_ptr)
end
creds[:user, credid] = username # save credentials
# For SSH we need a private key location
privatekey = if haskey(ENV,"SSH_KEY_PATH")
ENV["SSH_KEY_PATH"]
else
keydefpath = creds[:prvkey, credid] # check if credentials were already used
if keydefpath !== nothing && !isusedcreds
keydefpath # use cached value
else
if keydefpath === nothing || isempty(keydefpath)
keydefpath = joinpath(homedir(),".ssh","id_rsa")
end
prompt("Private key location for '$schema$username@$host'", default=keydefpath)
end
end
creds[:prvkey, credid] = privatekey # save credentials
# 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, credid] # check if credentials were already used
if keydefpath !== nothing && !isusedcreds
keydefpath # use cached value
else
if keydefpath === nothing || isempty(keydefpath)
keydefpath = privatekey*".pub"
end
if isfile(keydefpath)
keydefpath
else
prompt("Public key location for '$schema$username@$host'", default=keydefpath)
end
end
end
creds[:pubkey, credid] = publickey # save credentials
passphrase = if haskey(ENV,"SSH_KEY_PASS")
ENV["SSH_KEY_PASS"]
else
passdef = creds[:pass, credid] # check if credentials were already used
if passdef === nothing || 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
end
creds[:pass, credid] = passphrase # save credentials
isempty(username) && return Cint(Error.EAUTH)
err = ccall((:git_cred_ssh_key_new, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring, Cstring, Cstring, Cstring),
cred, username, publickey, privatekey, passphrase)
err == 0 && return Cint(0)
end
if isset(allowed_types, Cuint(Consts.CREDTYPE_USERPASS_PLAINTEXT))
creds == nothing && (creds = UserPasswordCredentials())
credid = "$schema$host"
username = creds[:user, credid]
userpass = creds[:pass, credid]
if is_windows()
if username === nothing || userpass === nothing || isusedcreds
res = Base.winprompt("Please enter your credentials for '$schema$host'", "Credentials required",
username === nothing ? "" : username; prompt_username = true)
isnull(res) && return Cint(Error.EAUTH)
username, userpass = Base.get(res)
end
else
if username === nothing || isusedcreds
username = prompt("Username for '$schema$host'")
end
if userpass === nothing || isusedcreds
userpass = prompt("Password for '$schema$username@$host'", password=true)
end
end
creds[:user, credid] = username # save credentials
creds[:pass, credid] = userpass # save credentials
isempty(username) && isempty(userpass) && return Cint(Error.EAUTH)
err = ccall((:git_cred_userpass_plaintext_new, :libgit2), Cint,
(Ptr{Ptr{Void}}, Cstring, Cstring),
cred, username, userpass)
err == 0 && return Cint(0)
end
finally
# if credentials are not passed back to caller via payload,
# then zero any passwords immediately.
if creds_are_temp && creds !== nothing
securezero!(creds)
end
end
return Cint(err)
end
function fetchhead_foreach_callback(ref_name::Cstring, remote_url::Cstring,
oid::Ptr{Oid}, 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), Oid(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{Oid}, Cuint, Ptr{Void}))