mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-26 12:24:26 +03:00
test all the keys
This commit is contained in:
parent
346685cc64
commit
a1d8b8cb8c
@ -149,6 +149,7 @@ fn main() {
|
||||
commands::git_set_global_config,
|
||||
commands::git_get_global_config,
|
||||
commands::project_flush_and_push,
|
||||
commands::test_git_connection,
|
||||
zip::commands::get_logs_archive_path,
|
||||
zip::commands::get_project_archive_path,
|
||||
zip::commands::get_project_data_archive_path,
|
||||
|
@ -167,3 +167,59 @@ pub async fn project_flush_and_push(handle: tauri::AppHandle, id: &str) -> Resul
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command(async)]
|
||||
#[instrument(skip(handle))]
|
||||
pub async fn test_git_connection(
|
||||
handle: tauri::AppHandle,
|
||||
project_id: &str,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
let project_id = project_id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".into(),
|
||||
})?;
|
||||
|
||||
let users = handle.state::<users::Controller>().inner().clone();
|
||||
let projects = handle.state::<projects::Controller>().inner().clone();
|
||||
let local_data_dir = DataDir::try_from(&handle)?;
|
||||
|
||||
let project = projects.get(&project_id).context("failed to get project")?;
|
||||
let user = users.get_user()?;
|
||||
let project_repository = project_repository::Repository::open(&project)?;
|
||||
let gb_repo =
|
||||
gb_repository::Repository::open(&local_data_dir, &project_repository, user.as_ref())
|
||||
.context("failed to open repository")?;
|
||||
|
||||
let default_target = gb_repo
|
||||
.default_target()
|
||||
.context("failed to get default target")?
|
||||
.ok_or_else(|| Error::UserError {
|
||||
code: Code::Branches,
|
||||
message: "No default target set".into(),
|
||||
})?;
|
||||
|
||||
let remote_name = default_target.branch.remote();
|
||||
|
||||
let helper = git::credentials::Helper::from(&handle);
|
||||
|
||||
let mut results = HashMap::new();
|
||||
let refspec = &format!("+refs/heads/*:refs/remotes/{}/*", remote_name);
|
||||
for (mut remote, credentials) in helper.enumerate(&project_repository, remote_name)? {
|
||||
let remote_url = remote.url().context("failed to get remote url")?.unwrap();
|
||||
let results = results
|
||||
.entry(remote_url.to_string())
|
||||
.or_insert_with(Vec::new);
|
||||
for callback in credentials {
|
||||
let mut fetch_opts = git2::FetchOptions::new();
|
||||
fetch_opts.remote_callbacks(callback.clone().into());
|
||||
fetch_opts.prune(git2::FetchPrune::On);
|
||||
|
||||
results.push(serde_json::json!({
|
||||
"callback": format!("{:?}", callback),
|
||||
"error": remote.fetch(&[refspec], Some(&mut fetch_opts)).err().map(|e| e.to_string()),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(serde_json::json!(results))
|
||||
}
|
||||
|
@ -4,18 +4,23 @@ use tauri::AppHandle;
|
||||
|
||||
use crate::{keys, paths, project_repository, projects, users};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SshCredential {
|
||||
Keyfile {
|
||||
key_path: path::PathBuf,
|
||||
passphrase: Option<String>,
|
||||
},
|
||||
MemoryKey(Box<keys::PrivateKey>),
|
||||
GitButlerKey(Box<keys::PrivateKey>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HttpsCredential {
|
||||
UsernamePassword { username: String, password: String },
|
||||
CredentialHelper { username: String, password: String },
|
||||
GitHubToken(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Credential {
|
||||
Noop,
|
||||
Ssh(SshCredential),
|
||||
@ -40,7 +45,7 @@ impl From<Credential> for git2::RemoteCallbacks<'_> {
|
||||
git2::Cred::ssh_key("git", None, &key_path, passphrase.as_deref())
|
||||
});
|
||||
}
|
||||
Credential::Ssh(SshCredential::MemoryKey(key)) => {
|
||||
Credential::Ssh(SshCredential::GitButlerKey(key)) => {
|
||||
remote_callbacks.credentials(move |url, _username_from_url, _allowed_types| {
|
||||
tracing::info!("authenticating with {} using gitbutler's key", url);
|
||||
git2::Cred::ssh_key_from_memory("git", None, &key.to_string(), None)
|
||||
@ -52,6 +57,18 @@ impl From<Credential> for git2::RemoteCallbacks<'_> {
|
||||
git2::Cred::userpass_plaintext(&username, &password)
|
||||
});
|
||||
}
|
||||
Credential::Https(HttpsCredential::CredentialHelper { username, password }) => {
|
||||
remote_callbacks.credentials(move |url, _username_from_url, _allowed_types| {
|
||||
tracing::info!("authenticating with {url} as '{username}' with password using credential helper");
|
||||
git2::Cred::userpass_plaintext(&username, &password)
|
||||
});
|
||||
}
|
||||
Credential::Https(HttpsCredential::GitHubToken(token)) => {
|
||||
remote_callbacks.credentials(move |url, _username_from_url, _allowed_types| {
|
||||
tracing::info!("authenticating with {url} using github token");
|
||||
git2::Cred::userpass_plaintext("git", &token)
|
||||
});
|
||||
}
|
||||
};
|
||||
remote_callbacks
|
||||
}
|
||||
@ -117,6 +134,89 @@ impl From<HelpError> for crate::error::Error {
|
||||
}
|
||||
|
||||
impl Helper {
|
||||
/// returns all possible credentials for a remote, without trying to be smart.
|
||||
pub fn enumerate<'a>(
|
||||
&'a self,
|
||||
project_repository: &'a project_repository::Repository,
|
||||
remote: &str,
|
||||
) -> Result<Vec<(super::Remote, Vec<Credential>)>, HelpError> {
|
||||
let remote = project_repository.git_repository.find_remote(remote)?;
|
||||
let remote_url = remote.url()?.ok_or(HelpError::NoUrlSet)?;
|
||||
|
||||
let mut flow = vec![];
|
||||
|
||||
if let projects::AuthKey::Local {
|
||||
private_key_path,
|
||||
passphrase,
|
||||
} = &project_repository.project().preferred_key
|
||||
{
|
||||
let ssh_remote = if remote_url.scheme == super::Scheme::Ssh {
|
||||
project_repository
|
||||
.git_repository
|
||||
.remote_anonymous(&remote_url)
|
||||
} else {
|
||||
let ssh_url = remote_url.as_ssh()?;
|
||||
project_repository.git_repository.remote_anonymous(&ssh_url)
|
||||
}?;
|
||||
flow.push((
|
||||
ssh_remote,
|
||||
vec![Credential::Ssh(SshCredential::Keyfile {
|
||||
key_path: private_key_path
|
||||
.canonicalize()
|
||||
.unwrap_or(private_key_path.clone()),
|
||||
passphrase: passphrase.clone(),
|
||||
})],
|
||||
));
|
||||
}
|
||||
|
||||
// is github is authenticated, only try github.
|
||||
if remote_url.is_github() {
|
||||
if let Some(github_access_token) = self
|
||||
.users
|
||||
.get_user()?
|
||||
.and_then(|user| user.github_access_token)
|
||||
{
|
||||
let https_remote = if remote_url.scheme == super::Scheme::Https {
|
||||
project_repository
|
||||
.git_repository
|
||||
.remote_anonymous(&remote_url)
|
||||
} else {
|
||||
let ssh_url = remote_url.as_ssh()?;
|
||||
project_repository.git_repository.remote_anonymous(&ssh_url)
|
||||
}?;
|
||||
flow.push((
|
||||
https_remote,
|
||||
vec![Credential::Https(HttpsCredential::GitHubToken(
|
||||
github_access_token,
|
||||
))],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(https_url) = remote_url.as_https() {
|
||||
flow.push((
|
||||
project_repository
|
||||
.git_repository
|
||||
.remote_anonymous(&https_url)?,
|
||||
Self::https_flow(project_repository, &https_url)?
|
||||
.into_iter()
|
||||
.map(Credential::Https)
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Ok(ssh_url) = remote_url.as_ssh() {
|
||||
flow.push((
|
||||
project_repository
|
||||
.git_repository
|
||||
.remote_anonymous(&ssh_url)?,
|
||||
self.ssh_flow()?.into_iter().map(Credential::Ssh).collect(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(flow)
|
||||
}
|
||||
|
||||
pub fn help<'a>(
|
||||
&'a self,
|
||||
project_repository: &'a project_repository::Repository,
|
||||
@ -142,11 +242,12 @@ impl Helper {
|
||||
let ssh_url = remote_url.as_ssh()?;
|
||||
project_repository.git_repository.remote_anonymous(&ssh_url)
|
||||
}?;
|
||||
|
||||
return Ok(vec![(
|
||||
ssh_remote,
|
||||
vec![Credential::Ssh(SshCredential::Keyfile {
|
||||
key_path: private_key_path
|
||||
.read_link()
|
||||
.canonicalize()
|
||||
.unwrap_or(private_key_path.clone()),
|
||||
passphrase: passphrase.clone(),
|
||||
})],
|
||||
@ -163,10 +264,8 @@ impl Helper {
|
||||
let https_remote = if remote_url.scheme == super::Scheme::Https {
|
||||
Ok(remote)
|
||||
} else {
|
||||
let https_url = remote_url.as_ssh()?;
|
||||
project_repository
|
||||
.git_repository
|
||||
.remote_anonymous(&https_url)
|
||||
let ssh_url = remote_url.as_ssh()?;
|
||||
project_repository.git_repository.remote_anonymous(&ssh_url)
|
||||
}?;
|
||||
return Ok(vec![(
|
||||
https_remote,
|
||||
@ -205,7 +304,7 @@ impl Helper {
|
||||
self.ssh_flow()?.into_iter().map(Credential::Ssh).collect(),
|
||||
)];
|
||||
|
||||
if let Ok(https_url) = remote_url.as_ssh() {
|
||||
if let Ok(https_url) = remote_url.as_https() {
|
||||
flow.push((
|
||||
project_repository
|
||||
.git_repository
|
||||
@ -222,7 +321,7 @@ impl Helper {
|
||||
_ => {
|
||||
let mut flow = vec![];
|
||||
|
||||
if let Ok(https_url) = remote_url.as_ssh() {
|
||||
if let Ok(https_url) = remote_url.as_https() {
|
||||
flow.push((
|
||||
project_repository
|
||||
.git_repository
|
||||
@ -258,7 +357,7 @@ impl Helper {
|
||||
let config = project_repository.git_repository.config()?;
|
||||
helper.config(&git2::Config::from(config));
|
||||
if let Some((username, password)) = helper.execute() {
|
||||
flow.push(HttpsCredential::UsernamePassword { username, password });
|
||||
flow.push(HttpsCredential::CredentialHelper { username, password });
|
||||
}
|
||||
|
||||
Ok(flow)
|
||||
@ -270,7 +369,7 @@ impl Helper {
|
||||
let home_path = std::path::Path::new(&home_path);
|
||||
|
||||
let id_rsa_path = home_path.join(".ssh").join("id_rsa");
|
||||
let id_rsa_path = id_rsa_path.read_link().unwrap_or(id_rsa_path);
|
||||
let id_rsa_path = id_rsa_path.canonicalize().unwrap_or(id_rsa_path);
|
||||
if id_rsa_path.exists() {
|
||||
flow.push(SshCredential::Keyfile {
|
||||
key_path: id_rsa_path.clone(),
|
||||
@ -279,7 +378,7 @@ impl Helper {
|
||||
}
|
||||
|
||||
let id_ed25519_path = home_path.join(".ssh").join("id_ed25519");
|
||||
let id_ed25519_path = id_ed25519_path.read_link().unwrap_or(id_ed25519_path);
|
||||
let id_ed25519_path = id_ed25519_path.canonicalize().unwrap_or(id_ed25519_path);
|
||||
if id_ed25519_path.exists() {
|
||||
flow.push(SshCredential::Keyfile {
|
||||
key_path: id_ed25519_path.clone(),
|
||||
@ -288,7 +387,7 @@ impl Helper {
|
||||
}
|
||||
|
||||
let id_ecdsa_path = home_path.join(".ssh").join("id_ecdsa");
|
||||
let id_ecdsa_path = id_ecdsa_path.read_link().unwrap_or(id_ecdsa_path);
|
||||
let id_ecdsa_path = id_ecdsa_path.canonicalize().unwrap_or(id_ecdsa_path);
|
||||
if id_ecdsa_path.exists() {
|
||||
flow.push(SshCredential::Keyfile {
|
||||
key_path: id_ecdsa_path.clone(),
|
||||
@ -298,7 +397,7 @@ impl Helper {
|
||||
}
|
||||
|
||||
let key = self.keys.get_or_create()?;
|
||||
flow.push(SshCredential::MemoryKey(Box::new(key)));
|
||||
flow.push(SshCredential::GitButlerKey(Box::new(key)));
|
||||
Ok(flow)
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ pub use convert::ConvertError;
|
||||
pub use parse::Error as ParseError;
|
||||
pub use scheme::Scheme;
|
||||
|
||||
#[derive(Default, Clone, Debug, thiserror::Error)]
|
||||
#[derive(Default, Clone, Hash, PartialEq, Eq, Debug, thiserror::Error)]
|
||||
pub struct Url {
|
||||
/// The URL scheme.
|
||||
pub scheme: Scheme,
|
||||
|
@ -60,7 +60,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn to_https_url_test() {
|
||||
vec![
|
||||
for (input, expected) in [
|
||||
(
|
||||
"https://github.com/gitbutlerapp/gitbutler-client.git",
|
||||
"https://github.com/gitbutlerapp/gitbutler-client.git",
|
||||
@ -77,19 +77,24 @@ mod tests {
|
||||
"ssh://git@github.com/gitbutlerapp/gitbutler-client.git",
|
||||
"https://github.com/gitbutlerapp/gitbutler-client.git",
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, (input, expected))| {
|
||||
(
|
||||
"git@bitbucket.org:gitbutler-nikita/test.git",
|
||||
"https://bitbucket.org/gitbutler-nikita/test.git",
|
||||
),
|
||||
(
|
||||
"https://bitbucket.org/gitbutler-nikita/test.git",
|
||||
"https://bitbucket.org/gitbutler-nikita/test.git",
|
||||
),
|
||||
] {
|
||||
let url = input.parse().unwrap();
|
||||
let https_url = to_https_url(&url).unwrap();
|
||||
assert_eq!(https_url.to_string(), expected, "test case {}", i);
|
||||
});
|
||||
assert_eq!(https_url.to_string(), expected, "test case {}", url);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_ssh_url_test() {
|
||||
vec![
|
||||
for (input, expected) in [
|
||||
(
|
||||
"git@github.com:gitbutlerapp/gitbutler-client.git",
|
||||
"git@github.com:gitbutlerapp/gitbutler-client.git",
|
||||
@ -106,13 +111,18 @@ mod tests {
|
||||
"ssh://git@github.com/gitbutlerapp/gitbutler-client.git",
|
||||
"ssh://git@github.com/gitbutlerapp/gitbutler-client.git",
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, (input, expected))| {
|
||||
(
|
||||
"https://bitbucket.org/gitbutler-nikita/test.git",
|
||||
"git@bitbucket.org:gitbutler-nikita/test.git",
|
||||
),
|
||||
(
|
||||
"git@bitbucket.org:gitbutler-nikita/test.git",
|
||||
"git@bitbucket.org:gitbutler-nikita/test.git",
|
||||
),
|
||||
] {
|
||||
let url = input.parse().unwrap();
|
||||
let ssh_url = to_ssh_url(&url).unwrap();
|
||||
assert_eq!(ssh_url.to_string(), expected, "test case {}", i);
|
||||
});
|
||||
assert_eq!(ssh_url.to_string(), expected, "test case {}", url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ssh_key;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PrivateKey(ssh_key::PrivateKey);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
Loading…
Reference in New Issue
Block a user