mod ssh_config;
#[cfg(feature = "ui4dialoguer")]
pub mod ui4dialoguer;
use failure::Error;
use git2;
pub struct CredentialHandler {
usernames: Vec<String>,
ssh_attempts_count: usize,
username_attempts_count: usize,
cred_helper_bad: Option<bool>,
cfg: git2::Config,
ui: Box<dyn CredentialUI>,
}
impl CredentialHandler
{
#[cfg(feature = "ui4dialoguer")]
pub fn new(cfg: git2::Config) -> Self {
use ui4dialoguer::CredentialUI4Dialoguer;
Self::new_with_ui(cfg, Box::new(CredentialUI4Dialoguer{}))
}
pub fn new_with_ui(cfg: git2::Config, ui: Box<dyn CredentialUI>) -> 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_attempts_count: 2,
username_attempts_count: 0,
cred_helper_bad: None,
cfg,
ui,
}
}
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) {
self.ssh_attempts_count = (self.ssh_attempts_count + 1) % 3;
let u = username.unwrap_or("git");
return match self.ssh_attempts_count {
0 => git2::Cred::ssh_key_from_agent(&u),
1 => self.cred_from_ssh_config(&u),
_ => Err(git2::Error::from_str("try with an other username")),
};
}
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 cred_from_ssh_config(&self, username: &str) -> Result<git2::Cred, git2::Error> {
let (key, passphrase) = ssh_config::get_ssh_key_and_passphrase(self.ui.as_ref());
match key {
Some(k) => {
git2::Cred::ssh_key(username, None, &k, passphrase.as_ref().map(String::as_str))
}
None => Err(git2::Error::from_str("failed authentication for repository")),
}
}
}
pub trait CredentialUI {
fn ask_user_password(&self, username: &str) -> Result<(String, String), Error>;
fn ask_ssh_passphrase(&self, passphrase_prompt: &str) -> Result<String, Error>;
}