Skip to content

Commit

Permalink
Merge pull request #23092 from JuliaLang/cv/prompt-eof
Browse files Browse the repository at this point in the history
Support using EOF char (^D) to abort credential prompt
  • Loading branch information
omus authored Aug 9, 2017
2 parents 44c53f4 + f26ee56 commit 3678816
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 51 deletions.
10 changes: 10 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1632,6 +1632,16 @@ function SymTridiagonal(dv::AbstractVector{T}, ev::AbstractVector{S}) where {T,S
SymTridiagonal(convert(Vector{R}, dv), convert(Vector{R}, ev))
end

# PR #23092
@eval LibGit2 begin
function prompt(msg::AbstractString; default::AbstractString="", password::Bool=false)
Base.depwarn(string(
"`LibGit2.prompt(msg::AbstractString; default::AbstractString=\"\", password::Bool=false)` is deprecated, use ",
"`get(Base.prompt(msg, default=default, password=password), \"\")` instead."), :prompt)
Base.get(Base.prompt(msg, default=default, password=password), "")
end
end

# END 0.7 deprecations

# BEGIN 1.0 deprecations
Expand Down
72 changes: 49 additions & 23 deletions base/libgit2/callbacks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ function is_passphrase_required(private_key::AbstractString)
end
end

function user_abort()
# Note: Potentially it could be better to just throw a Julia error.
ccall((:giterr_set_str, :libgit2), Void,
(Cint, Cstring),
Cint(Error.Callback), "Aborting, user cancelled credential request.")

return Cint(Error.EAUTH)
end

function authenticate_ssh(creds::SSHCredentials, libgit2credptr::Ptr{Ptr{Void}},
username_ptr, schema, host)
isusedcreds = checkused!(creds)
Expand All @@ -62,15 +71,19 @@ function authenticate_ssh(creds::SSHCredentials, libgit2credptr::Ptr{Ptr{Void}},
end

if creds.prompt_if_incorrect
# if username is not provided, then prompt for it
username = if username_ptr == Cstring(C_NULL)
# if username is not provided or empty, then prompt for it
username = username_ptr != Cstring(C_NULL) ? unsafe_string(username_ptr) : ""
if isempty(username)
uname = creds.user # check if credentials were already used
prompt_url = git_url(scheme=schema, host=host)
!isusedcreds ? uname : prompt("Username for '$prompt_url'", default=uname)
else
unsafe_string(username_ptr)
if !isusedcreds
username = uname
else
response = Base.prompt("Username for '$prompt_url'", default=uname)
isnull(response) && return user_abort()
username = unsafe_get(response)
end
end
isempty(username) && return Cint(Error.EAUTH)

prompt_url = git_url(scheme=schema, host=host, username=username)

Expand All @@ -84,8 +97,10 @@ function authenticate_ssh(creds::SSHCredentials, libgit2credptr::Ptr{Ptr{Void}},
if isempty(keydefpath) && isfile(defaultkeydefpath)
keydefpath = defaultkeydefpath
else
keydefpath =
prompt("Private key location for '$prompt_url'", default=keydefpath)
response = Base.prompt("Private key location for '$prompt_url'",
default=keydefpath)
isnull(response) && return user_abort()
keydefpath = unsafe_get(response)
end
end
keydefpath
Expand All @@ -107,7 +122,10 @@ function authenticate_ssh(creds::SSHCredentials, libgit2credptr::Ptr{Ptr{Void}},
keydefpath = privatekey*".pub"
end
if !isfile(keydefpath)
prompt("Public key location for '$prompt_url'", default=keydefpath)
response = Base.prompt("Public key location for '$prompt_url'",
default=keydefpath)
isnull(response) && return user_abort()
keydefpath = unsafe_get(response)
end
end
keydefpath
Expand All @@ -119,13 +137,16 @@ function authenticate_ssh(creds::SSHCredentials, libgit2credptr::Ptr{Ptr{Void}},
passdef = creds.pass # check if credentials were already used
if (isempty(passdef) || isusedcreds) && is_passphrase_required(privatekey)
if Sys.iswindows()
passdef = Base.winprompt(
response = 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]
isnull(response) && return user_abort()
passdef = unsafe_get(response)[2]
else
passdef = prompt("Passphrase for $privatekey", password=true)
response = Base.prompt("Passphrase for $privatekey", password=true)
isnull(response) && return user_abort()
passdef = unsafe_get(response)
isempty(passdef) && return user_abort() # Ambiguous if EOF or newline
end
end
passdef
Expand Down Expand Up @@ -156,22 +177,27 @@ function authenticate_userpass(creds::UserPasswordCredentials, libgit2credptr::P
prompt_url = git_url(scheme=schema, host=host)
if Sys.iswindows()
if isempty(username) || isempty(userpass) || isusedcreds
res = Base.winprompt("Please enter your credentials for '$prompt_url'", "Credentials required",
isempty(username) ? urlusername : username; prompt_username = true)
isnull(res) && return Cint(Error.EAUTH)
username, userpass = Base.get(res)
response = Base.winprompt("Please enter your credentials for '$prompt_url'", "Credentials required",
isempty(username) ? urlusername : username; prompt_username = true)
isnull(response) && return user_abort()
username, userpass = unsafe_get(response)
end
elseif isusedcreds
username = prompt("Username for '$prompt_url'",
response = Base.prompt("Username for '$prompt_url'",
default=isempty(username) ? urlusername : username)
isnull(response) && return user_abort()
username = unsafe_get(response)

prompt_url = git_url(scheme=schema, host=host, username=username)
userpass = prompt("Password for '$prompt_url'", password=true)
response = Base.prompt("Password for '$prompt_url'", password=true)
isnull(response) && return user_abort()
userpass = unsafe_get(response)
isempty(userpass) && return user_abort() # Ambiguous if EOF or newline
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
Expand All @@ -189,11 +215,11 @@ If a payload is provided then `payload_ptr` should contain a `LibGit2.AbstractCr
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.
Typing `^D` (control key together with the `d` key) will abort the credential prompt.
Credentials are checked in the following order (if supported):
- ssh key pair (`ssh-agent` if specified in payload's `usesshagent` field)
Expand Down
14 changes: 0 additions & 14 deletions base/libgit2/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,6 @@ isset(val::Integer, flag::Integer) = (val & flag == flag)
reset(val::Integer, flag::Integer) = (val &= ~flag)
toggle(val::Integer, flag::Integer) = (val |= flag)

function prompt(msg::AbstractString; default::AbstractString="", password::Bool=false)
if Sys.iswindows() && password
error("Command line prompt not supported for password entry on windows. Use winprompt instead")
end
msg = !isempty(default) ? msg*" [$default]:" : msg*":"
uinput = if password
Base.getpass(msg)
else
print(msg)
readline()
end
isempty(uinput) ? default : uinput
end

function features()
feat = ccall((:git_libgit2_features, :libgit2), Cint, ())
res = Consts.GIT_FEATURE[]
Expand Down
26 changes: 26 additions & 0 deletions base/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,32 @@ else
getpass(prompt::AbstractString) = unsafe_string(ccall(:getpass, Cstring, (Cstring,), prompt))
end

"""
prompt(message; default="", password=false) -> Nullable{String}
Displays the `message` then waits for user input. Input is terminated when a newline (\\n)
is encountered or EOF (^D) character is entered on a blank line. If a `default` is provided
then the user can enter just a newline character to select the `default`. Alternatively,
when the `password` keyword is `true` the characters entered by the user will not be
displayed.
"""
function prompt(message::AbstractString; default::AbstractString="", password::Bool=false)
if Sys.iswindows() && password
error("Command line prompt not supported for password entry on windows. Use `Base.winprompt` instead")
end
msg = !isempty(default) ? "$message [$default]:" : "$message:"
if password
# `getpass` automatically chomps. We cannot tell an EOF from a '\n'.
uinput = getpass(msg)
else
print(msg)
uinput = readline(chomp=false)
isempty(uinput) && return Nullable{String}() # Encountered an EOF
uinput = chomp(uinput)
end
Nullable{String}(isempty(uinput) ? default : uinput)
end

# Windows authentication prompt
if Sys.iswindows()
struct CREDUI_INFO
Expand Down
23 changes: 19 additions & 4 deletions test/libgit2-helpers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ without having to authenticate against a real server.
function credential_loop(
valid_credential::AbstractCredentials,
url::AbstractString,
user::AbstractString,
user::Nullable{<:AbstractString},
allowed_types::UInt32,
cache::CachedCredentials=CachedCredentials())
cb = Base.LibGit2.credentials_cb()
Expand All @@ -25,7 +25,7 @@ function credential_loop(
err = Cint(0)
while err == 0
err = ccall(cb, Cint, (Ptr{Ptr{Void}}, Cstring, Cstring, Cuint, Ptr{Void}),
libgitcred_ptr_ptr, url, isempty(user) ? C_NULL : user, allowed_types, pointer_from_objref(payload_ptr))
libgitcred_ptr_ptr, url, get(user, C_NULL), allowed_types, pointer_from_objref(payload_ptr))
num_authentications += 1

# Check if the callback provided us with valid credentials
Expand All @@ -44,14 +44,14 @@ end
function credential_loop(
valid_credential::UserPasswordCredentials,
url::AbstractString,
user::AbstractString="")
user::Nullable{<:AbstractString}=Nullable{String}())
credential_loop(valid_credential, url, user, 0x000001)
end

function credential_loop(
valid_credential::SSHCredentials,
url::AbstractString,
user::AbstractString="";
user::Nullable{<:AbstractString}=Nullable{String}();
use_ssh_agent::Bool=false)
cache = CachedCredentials()

Expand All @@ -64,3 +64,18 @@ function credential_loop(

credential_loop(valid_credential, url, user, 0x000046, cache)
end

function credential_loop(
valid_credential::UserPasswordCredentials,
url::AbstractString,
user::AbstractString)
credential_loop(valid_credential, url, Nullable(user))
end

function credential_loop(
valid_credential::SSHCredentials,
url::AbstractString,
user::AbstractString;
use_ssh_agent::Bool=false)
credential_loop(valid_credential, url, Nullable(user), use_ssh_agent=use_ssh_agent)
end
Loading

0 comments on commit 3678816

Please sign in to comment.