use git2;
use failure::Error;
pub struct CredentialHandler {
usernames: Vec<String>,
ssh_agent_attempts_count: usize,
username_attempts_count: usize,
cred_helper_bad: Option<bool>,
cfg: git2::Config,
}
impl CredentialHandler {
pub fn new(cfg: git2::Config) -> Self {
let mut usernames = Vec::new();
usernames.push("".to_string()); usernames.push("git".to_string());
if let Ok(s) = std::env::var("USER").or_else(|_| std::env::var("USERNAME")) {
usernames.push(s);
}
CredentialHandler {
usernames,
ssh_agent_attempts_count: 0,
username_attempts_count: 0,
cred_helper_bad: None,
cfg,
}
}
pub fn try_next_credential(
&mut self,
url: &str,
username: Option<&str>,
allowed: git2::CredentialType,
) -> Result<git2::Cred, git2::Error> {
if allowed.contains(git2::CredentialType::USERNAME) {
let idx = self.username_attempts_count;
self.username_attempts_count += 1;
return match self.usernames.get(idx).map(|s| &s[..]) {
Some("") if username.is_none() => {
Err(git2::Error::from_str("gonna try usernames later"))
}
Some("") => git2::Cred::username(&username.unwrap_or("")),
Some(s) => git2::Cred::username(&s),
_ => Err(git2::Error::from_str("no more username to try")),
};
}
if allowed.contains(git2::CredentialType::SSH_KEY) {
let idx = self.ssh_agent_attempts_count;
self.ssh_agent_attempts_count += 1;
return match self.usernames.get(idx).map(|s| &s[..]) {
Some("") => git2::Cred::ssh_key_from_agent(&username.unwrap_or("")),
Some(s) => git2::Cred::ssh_key_from_agent(&s),
_ => Err(git2::Error::from_str("no more username to try")),
};
}
if allowed.contains(git2::CredentialType::USER_PASS_PLAINTEXT) && self.cred_helper_bad.is_none() {
let r = git2::Cred::credential_helper(&self.cfg, url, username);
self.cred_helper_bad = Some(r.is_err());
if r.is_err() {
match Self::ui_ask_user_password(username.unwrap_or("")) {
Ok((user, password)) => return git2::Cred::userpass_plaintext(&user, &password),
Err(_) => (), }
}
return r;
}
if allowed.contains(git2::CredentialType::DEFAULT) {
return git2::Cred::default();
}
Err(git2::Error::from_str("no valid authentication available"))
}
fn ui_ask_user_password(username: &str) -> Result<(String, String), Error> {
use dialoguer::Input;
use dialoguer::PasswordInput;
let user: String = Input::new()
.default(username.to_owned())
.with_prompt("username")
.interact()?;
let password: String = PasswordInput::new()
.with_prompt("password (hidden)")
.interact()?;
Ok((user.to_owned(), password.to_owned()))
}
}