mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-07 16:36:58 +00:00
Git no longer asks for the HTTPS user name and password on every fetch, pull and push. The password prompt offers "Remember for this host"; the credential is saved in the app's secret store (next to the saved SSH passwords, never in git's global configuration) only after the git command that used it succeeds, and a stored credential the server rejects is forgotten so the next command asks again. Settings › Version Control › Git lists saved credentials and forgets them. Each git child gets its own id, passed to the askpass helper, so concurrent commands never save or forget each other's credentials. ide-vcs reports command completion through an observer installed at start-up instead of depending on ide-ssh. Credentials are keyed by scheme, host and port, and only git's authentication errors (Authentication failed, HTTP 401/403 responses, invalid username or password) forget a stored credential. Fixes #272 Co-authored-by: a-rookie-of-C-language <[email protected]>
94 lines
3.1 KiB
Rust
94 lines
3.1 KiB
Rust
//! Saved passwords (SSH passwords / key passphrases by configuration id, the HTTP
|
|
//! proxy's), in the app's data directory rather than the OS credential store (no
|
|
//! system prompts). One JSON object, key → secret; owner-only on Unix.
|
|
|
|
use ide_core::{IdeError, IdeResult};
|
|
use parking_lot::Mutex;
|
|
use std::collections::BTreeMap;
|
|
use std::path::PathBuf;
|
|
|
|
pub struct SecretFile {
|
|
path: PathBuf,
|
|
lock: Mutex<()>,
|
|
}
|
|
|
|
impl SecretFile {
|
|
pub fn new(path: PathBuf) -> Self {
|
|
Self { path, lock: Mutex::new(()) }
|
|
}
|
|
|
|
pub fn get(&self, key: &str) -> Option<String> {
|
|
let _g = self.lock.lock();
|
|
self.read().remove(key)
|
|
}
|
|
|
|
pub fn set(&self, key: &str, secret: &str) -> IdeResult<()> {
|
|
let _g = self.lock.lock();
|
|
let mut all = self.read();
|
|
all.insert(key.to_owned(), secret.to_owned());
|
|
self.write(&all)
|
|
}
|
|
|
|
pub fn delete(&self, key: &str) {
|
|
let _g = self.lock.lock();
|
|
let mut all = self.read();
|
|
if all.remove(key).is_some()
|
|
&& let Err(e) = self.write(&all)
|
|
{
|
|
tracing::warn!("{}", e.message);
|
|
}
|
|
}
|
|
|
|
/// Every stored key, sorted (the secrets themselves stay private).
|
|
pub fn keys(&self) -> Vec<String> {
|
|
let _g = self.lock.lock();
|
|
self.read().into_keys().collect()
|
|
}
|
|
|
|
fn read(&self) -> BTreeMap<String, String> {
|
|
match std::fs::read(&self.path) {
|
|
Ok(bytes) => serde_json::from_slice(&bytes).unwrap_or_else(|e| {
|
|
tracing::warn!("saved passwords: {}: {e}", self.path.display());
|
|
BTreeMap::new()
|
|
}),
|
|
Err(_) => BTreeMap::new(),
|
|
}
|
|
}
|
|
|
|
fn write(&self, all: &BTreeMap<String, String>) -> IdeResult<()> {
|
|
let err = |e: std::io::Error| IdeError::unavailable(format!("saved passwords: {}: {e}", self.path.display()));
|
|
if let Some(dir) = self.path.parent() {
|
|
std::fs::create_dir_all(dir).map_err(err)?;
|
|
}
|
|
let tmp = self.path.with_extension("json.tmp");
|
|
let mut options = std::fs::OpenOptions::new();
|
|
options.write(true).create(true).truncate(true);
|
|
#[cfg(unix)]
|
|
std::os::unix::fs::OpenOptionsExt::mode(&mut options, 0o600);
|
|
let bytes = serde_json::to_vec_pretty(all).map_err(|e| IdeError::internal(e.to_string()))?;
|
|
std::io::Write::write_all(&mut options.open(&tmp).map_err(err)?, &bytes).map_err(err)?;
|
|
std::fs::rename(&tmp, &self.path).map_err(err)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn stores_and_forgets_secrets() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let file = SecretFile::new(tmp.path().join("ssh-passwords.json"));
|
|
file.set("a", "pw").unwrap();
|
|
assert_eq!(file.get("a").as_deref(), Some("pw"));
|
|
file.delete("a");
|
|
assert_eq!(file.get("a"), None);
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::PermissionsExt;
|
|
let mode = std::fs::metadata(tmp.path().join("ssh-passwords.json")).unwrap().permissions().mode();
|
|
assert_eq!(mode & 0o777, 0o600);
|
|
}
|
|
}
|
|
}
|