Skip to content

Commit 26014f9

Browse files
committed
Remove authentication failure protection counter
State between multiple credential_callback calls is now being maintained within the `CredentialPayload`. This change allows credential types to just contain credential information.
1 parent 4c8e693 commit 26014f9

File tree

5 files changed

+55
-76
lines changed

5 files changed

+55
-76
lines changed

base/libgit2/callbacks.jl

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ end
5050

5151
function authenticate_ssh(libgit2credptr::Ptr{Ptr{Void}}, p::CredentialPayload, username_ptr)
5252
creds = Base.get(p.credential)::SSHCredentials
53-
isusedcreds = checkused!(creds)
53+
54+
# Reset password on sucessive calls
55+
if !p.first_pass
56+
creds.pass = ""
57+
end
5458

5559
# Note: The same SSHCredentials can be used to authenticate separate requests using the
5660
# same credential cache. e.g. using Pkg.update when there are two private packages.
@@ -74,15 +78,10 @@ function authenticate_ssh(libgit2credptr::Ptr{Ptr{Void}}, p::CredentialPayload,
7478
# if username is not provided or empty, then prompt for it
7579
username = username_ptr != Cstring(C_NULL) ? unsafe_string(username_ptr) : ""
7680
if isempty(username)
77-
uname = creds.user # check if credentials were already used
7881
prompt_url = git_url(scheme=p.scheme, host=p.host)
79-
if !isusedcreds
80-
username = uname
81-
else
82-
response = Base.prompt("Username for '$prompt_url'", default=uname)
83-
isnull(response) && return user_abort()
84-
username = unsafe_get(response)
85-
end
82+
response = Base.prompt("Username for '$prompt_url'", default=creds.user)
83+
isnull(response) && return user_abort()
84+
username = unsafe_get(response)
8685
end
8786

8887
prompt_url = git_url(scheme=p.scheme, host=p.host, username=username)
@@ -92,7 +91,7 @@ function authenticate_ssh(libgit2credptr::Ptr{Ptr{Void}}, p::CredentialPayload,
9291
ENV["SSH_KEY_PATH"]
9392
else
9493
keydefpath = creds.prvkey # check if credentials were already used
95-
if isempty(keydefpath) || isusedcreds
94+
if isempty(keydefpath)
9695
defaultkeydefpath = joinpath(homedir(),".ssh","id_rsa")
9796
if isempty(keydefpath) && isfile(defaultkeydefpath)
9897
keydefpath = defaultkeydefpath
@@ -117,7 +116,7 @@ function authenticate_ssh(libgit2credptr::Ptr{Ptr{Void}}, p::CredentialPayload,
117116
ENV["SSH_PUB_KEY_PATH"]
118117
else
119118
keydefpath = creds.pubkey # check if credentials were already used
120-
if isempty(keydefpath) || isusedcreds
119+
if isempty(keydefpath)
121120
if isempty(keydefpath)
122121
keydefpath = privatekey*".pub"
123122
end
@@ -135,7 +134,7 @@ function authenticate_ssh(libgit2credptr::Ptr{Ptr{Void}}, p::CredentialPayload,
135134
ENV["SSH_KEY_PASS"]
136135
else
137136
passdef = creds.pass # check if credentials were already used
138-
if (isempty(passdef) || isusedcreds) && is_passphrase_required(privatekey)
137+
if isempty(passdef) && is_passphrase_required(privatekey)
139138
if Sys.iswindows()
140139
response = Base.winprompt(
141140
"Your SSH Key requires a password, please enter it now:",
@@ -151,15 +150,13 @@ function authenticate_ssh(libgit2credptr::Ptr{Ptr{Void}}, p::CredentialPayload,
151150
end
152151
passdef
153152
end
154-
((creds.user != username) || (creds.pass != passphrase) ||
155-
(creds.prvkey != privatekey) || (creds.pubkey != publickey)) && reset!(creds)
156153

157154
creds.user = username # save credentials
158155
creds.prvkey = privatekey # save credentials
159156
creds.pubkey = publickey # save credentials
160157
creds.pass = passphrase
161-
else
162-
isusedcreds && return Cint(Error.EAUTH)
158+
elseif !p.first_pass
159+
return Cint(Error.EAUTH)
163160
end
164161

165162
return ccall((:git_cred_ssh_key_new, :libgit2), Cint,
@@ -169,37 +166,39 @@ end
169166

170167
function authenticate_userpass(libgit2credptr::Ptr{Ptr{Void}}, p::CredentialPayload)
171168
creds = Base.get(p.credential)::UserPasswordCredentials
172-
isusedcreds = checkused!(creds)
169+
170+
# Reset password on sucessive calls
171+
if !p.first_pass
172+
creds.pass = ""
173+
end
173174

174175
if creds.prompt_if_incorrect
175176
username = creds.user
176177
userpass = creds.pass
177-
prompt_url = git_url(scheme=p.scheme, host=p.host)
178-
if Sys.iswindows()
179-
if isempty(username) || isempty(userpass) || isusedcreds
178+
if isempty(username) || isempty(userpass)
179+
prompt_url = git_url(scheme=p.scheme, host=p.host)
180+
if Sys.iswindows()
180181
response = Base.winprompt("Please enter your credentials for '$prompt_url'", "Credentials required",
181182
isempty(username) ? p.username : username; prompt_username = true)
182183
isnull(response) && return user_abort()
183184
username, userpass = unsafe_get(response)
184-
end
185-
elseif isusedcreds
186-
response = Base.prompt("Username for '$prompt_url'",
187-
default=isempty(username) ? p.username : username)
188-
isnull(response) && return user_abort()
189-
username = unsafe_get(response)
185+
else
186+
response = Base.prompt("Username for '$prompt_url'",
187+
default=isempty(username) ? p.username : username)
188+
isnull(response) && return user_abort()
189+
username = unsafe_get(response)
190190

191-
prompt_url = git_url(scheme=p.scheme, host=p.host, username=username)
192-
response = Base.prompt("Password for '$prompt_url'", password=true)
193-
isnull(response) && return user_abort()
194-
userpass = unsafe_get(response)
195-
isempty(userpass) && return user_abort() # Ambiguous if EOF or newline
191+
prompt_url = git_url(scheme=p.scheme, host=p.host, username=username)
192+
response = Base.prompt("Password for '$prompt_url'", password=true)
193+
isnull(response) && return user_abort()
194+
userpass = unsafe_get(response)
195+
isempty(userpass) && return user_abort() # Ambiguous if EOF or newline
196+
end
196197
end
197-
198-
((creds.user != username) || (creds.pass != userpass)) && reset!(creds)
199198
creds.user = username # save credentials
200199
creds.pass = userpass # save credentials
201-
else
202-
isusedcreds && return Cint(Error.EAUTH)
200+
elseif !p.first_pass
201+
return Cint(Error.EAUTH)
203202
end
204203

205204
return ccall((:git_cred_userpass_plaintext_new, :libgit2), Cint,
@@ -228,11 +227,7 @@ Credentials are checked in the following order (if supported):
228227
**Note**: Due to the specifics of the `libgit2` authentication procedure, when
229228
authentication fails, this function is called again without any indication whether
230229
authentication was successful or not. To avoid an infinite loop from repeatedly
231-
using the same faulty credentials, the `checkused!` function can be called. This
232-
function returns `true` if the credentials were used.
233-
Using credentials triggers a user prompt for (re)entering required information.
234-
`UserPasswordCredentials` and `CachedCredentials` are implemented using a call
235-
counting strategy that prevents repeated usage of faulty credentials.
230+
using the same faulty credentials, we will keep track of state using the payload.
236231
"""
237232
function credentials_callback(libgit2credptr::Ptr{Ptr{Void}}, url_ptr::Cstring,
238233
username_ptr::Cstring,
@@ -269,12 +264,16 @@ function credentials_callback(libgit2credptr::Ptr{Ptr{Void}}, url_ptr::Cstring,
269264
allowed_types &= Cuint(0) # Unhandled credential type
270265
end
271266
end
267+
268+
p.first_pass = true
269+
else
270+
p.first_pass = false
272271
end
273272

274273
# use ssh key or ssh-agent
275274
if isset(allowed_types, Cuint(Consts.CREDTYPE_SSH_KEY))
276275
if isnull(p.credential) || !isa(unsafe_get(p.credential), SSHCredentials)
277-
creds = reset!(SSHCredentials(p.username, "", true), -1)
276+
creds = SSHCredentials(p.username, "", true)
278277
if !isnull(p.cache)
279278
credid = "ssh://$(p.host)"
280279
creds = get_creds!(unsafe_get(p.cache), credid, creds)
@@ -287,7 +286,7 @@ function credentials_callback(libgit2credptr::Ptr{Ptr{Void}}, url_ptr::Cstring,
287286

288287
if isset(allowed_types, Cuint(Consts.CREDTYPE_USERPASS_PLAINTEXT))
289288
if isnull(p.credential) || !isa(unsafe_get(p.credential), UserPasswordCredentials)
290-
creds = reset!(UserPasswordCredentials(p.username, "", true), -1)
289+
creds = UserPasswordCredentials(p.username, "", true)
291290
if !isnull(p.cache)
292291
credid = "$(isempty(p.scheme) ? "ssh" : p.scheme)://$(p.host)"
293292
creds = get_creds!(unsafe_get(p.cache), credid, creds)

base/libgit2/types.jl

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,9 +1076,8 @@ mutable struct UserPasswordCredentials <: AbstractCredentials
10761076
user::String
10771077
pass::String
10781078
prompt_if_incorrect::Bool # Whether to allow interactive prompting if the credentials are incorrect
1079-
count::Int # authentication failure protection count
10801079
function UserPasswordCredentials(u::AbstractString,p::AbstractString,prompt_if_incorrect::Bool=false)
1081-
c = new(u,p,prompt_if_incorrect,3)
1080+
c = new(u,p,prompt_if_incorrect)
10821081
finalizer(c, securezero!)
10831082
return c
10841083
end
@@ -1088,7 +1087,6 @@ end
10881087
function securezero!(cred::UserPasswordCredentials)
10891088
securezero!(cred.user)
10901089
securezero!(cred.pass)
1091-
cred.count = 0
10921090
return cred
10931091
end
10941092

@@ -1104,9 +1102,8 @@ mutable struct SSHCredentials <: AbstractCredentials
11041102
pubkey::String
11051103
usesshagent::String # used for ssh-agent authentication
11061104
prompt_if_incorrect::Bool # Whether to allow interactive prompting if the credentials are incorrect
1107-
count::Int
11081105
function SSHCredentials(u::AbstractString,p::AbstractString,prvkey::AbstractString,pubkey::AbstractString,prompt_if_incorrect::Bool=false)
1109-
c = new(u,p,prvkey,pubkey,"Y",prompt_if_incorrect,3)
1106+
c = new(u,p,prvkey,pubkey,"Y",prompt_if_incorrect)
11101107
finalizer(c, securezero!)
11111108
return c
11121109
end
@@ -1119,7 +1116,6 @@ function securezero!(cred::SSHCredentials)
11191116
securezero!(cred.pass)
11201117
securezero!(cred.prvkey)
11211118
securezero!(cred.pubkey)
1122-
cred.count = 0
11231119
return cred
11241120
end
11251121

@@ -1128,21 +1124,11 @@ function Base.:(==)(a::SSHCredentials, b::SSHCredentials)
11281124
end
11291125

11301126
"Credentials that support caching"
1131-
mutable struct CachedCredentials <: AbstractCredentials
1127+
struct CachedCredentials <: AbstractCredentials
11321128
cred::Dict{String,AbstractCredentials}
1133-
count::Int # authentication failure protection count
1134-
CachedCredentials() = new(Dict{String,AbstractCredentials}(),3)
1129+
CachedCredentials() = new(Dict{String,AbstractCredentials}())
11351130
end
11361131

1137-
"Checks if credentials were used or failed authentication, see `LibGit2.credentials_callback`"
1138-
function checkused!(p::Union{UserPasswordCredentials, SSHCredentials})
1139-
p.count <= 0 && return true
1140-
p.count -= 1
1141-
return false
1142-
end
1143-
reset!(p::Union{UserPasswordCredentials, SSHCredentials}, cnt::Int=3) = (p.count = cnt; p)
1144-
reset!(p::CachedCredentials) = (foreach(reset!, values(p.cred)); p)
1145-
11461132
"Obtain the cached credentials for the given host+protocol (credid), or return and store the default if not found"
11471133
get_creds!(collection::CachedCredentials, credid, default) = get!(collection.cred, credid, default)
11481134

@@ -1161,13 +1147,14 @@ instances will be used when the URL has changed.
11611147
mutable struct CredentialPayload <: Payload
11621148
credential::Nullable{AbstractCredentials}
11631149
cache::Nullable{CachedCredentials}
1150+
first_pass::Bool
11641151
scheme::String
11651152
username::String
11661153
host::String
11671154
path::String
11681155

11691156
function CredentialPayload(credential::Nullable{<:AbstractCredentials}, cache::Nullable{CachedCredentials})
1170-
new(credential, cache, "", "", "", "")
1157+
new(credential, cache, true, "", "", "", "")
11711158
end
11721159
end
11731160

base/pkg/entry.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,6 @@ function update(branch::AbstractString, upkgs::Set{String})
427427
success = true
428428
try
429429
LibGit2.fetch(repo, payload = Nullable(creds))
430-
LibGit2.reset!(creds)
431430
LibGit2.merge!(repo, fastforward=true)
432431
catch err
433432
cex = CapturedException(err, catch_backtrace())

test/libgit2-helpers.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ function credential_loop(
7171
cache = get(payload.cache)
7272

7373
m = match(LibGit2.URL_REGEX, url)
74-
default_cred = LibGit2.reset!(SSHCredentials(true), -1)
74+
default_cred = SSHCredentials(true)
7575
default_cred.usesshagent = "N"
7676
LibGit2.get_creds!(cache, "ssh://$(m[:host])", default_cred)
7777
end

test/libgit2.jl

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1588,16 +1588,10 @@ mktempdir() do dir
15881588
creds_user = "USER"
15891589
creds_pass = "PASS"
15901590
creds = LibGit2.UserPasswordCredentials(creds_user, creds_pass)
1591-
@test !LibGit2.checkused!(creds)
1592-
@test !LibGit2.checkused!(creds)
1593-
@test !LibGit2.checkused!(creds)
1594-
@test LibGit2.checkused!(creds)
15951591
@test creds.user == creds_user
15961592
@test creds.pass == creds_pass
15971593
creds2 = LibGit2.UserPasswordCredentials(creds_user, creds_pass)
15981594
@test creds == creds2
1599-
LibGit2.reset!(creds)
1600-
@test !LibGit2.checkused!(creds)
16011595
sshcreds = LibGit2.SSHCredentials(creds_user, creds_pass)
16021596
@test sshcreds.user == creds_user
16031597
@test sshcreds.pass == creds_pass
@@ -1687,7 +1681,7 @@ mktempdir() do dir
16871681
]
16881682
err, auth_attempts = challenge_prompt(ssh_p_ex, challenges)
16891683
@test err == git_ok
1690-
@test auth_attempts == 5
1684+
@test auth_attempts == 2
16911685

16921686
# User sends EOF in passphrase prompt which aborts the credential request
16931687
challenges = [
@@ -1737,7 +1731,7 @@ mktempdir() do dir
17371731
]
17381732
err, auth_attempts = challenge_prompt(ssh_u_ex, challenges)
17391733
@test err == abort_prompt
1740-
@test auth_attempts == 5 # Should ideally be <= 2
1734+
@test auth_attempts == 2
17411735

17421736
# User repeatedly chooses an invalid username
17431737
challenges = [
@@ -1747,7 +1741,7 @@ mktempdir() do dir
17471741
]
17481742
err, auth_attempts = challenge_prompt(ssh_u_ex, challenges)
17491743
@test err == abort_prompt
1750-
@test auth_attempts == 6
1744+
@test auth_attempts == 3
17511745

17521746
# Credential callback is given an empty string in the `username_ptr`
17531747
# instead of the typical C_NULL.
@@ -1904,7 +1898,7 @@ mktempdir() do dir
19041898
]
19051899
err, auth_attempts = challenge_prompt(https_ex, challenges)
19061900
@test err == git_ok
1907-
@test auth_attempts == 5
1901+
@test auth_attempts == 2
19081902
end
19091903

19101904
@testset "SSH explicit credentials" begin
@@ -1940,7 +1934,7 @@ mktempdir() do dir
19401934
# TODO: Unless the SSH agent is disabled we may get caught in an infinite loop
19411935
err, auth_attempts = challenge_prompt(invalid_ex, [])
19421936
@test err == eauth_error
1943-
@test auth_attempts == 4
1937+
@test auth_attempts == 2
19441938
end
19451939

19461940
@testset "HTTPS explicit credentials" begin
@@ -1974,7 +1968,7 @@ mktempdir() do dir
19741968
# Explicitly provided credential is incorrect
19751969
err, auth_attempts = challenge_prompt(invalid_ex, [])
19761970
@test err == eauth_error
1977-
@test auth_attempts == 4
1971+
@test auth_attempts == 2
19781972
end
19791973

19801974
@testset "Cached credentials" begin
@@ -2040,7 +2034,7 @@ mktempdir() do dir
20402034
expected_cred = LibGit2.UserPasswordCredentials(valid_username, valid_password)
20412035
err, auth_attempts, cache = challenge_prompt(replace_ex, challenges)
20422036
@test err == git_ok
2043-
@test auth_attempts == 4
2037+
@test auth_attempts == 2
20442038
@test typeof(cache) == LibGit2.CachedCredentials
20452039
@test cache.cred == Dict(cred_id => expected_cred)
20462040
end

0 commit comments

Comments
 (0)