mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
SSH passwords and key passphrases, git HTTPS credentials, the HTTP proxy password and data source passwords were kept in plaintext JSON files in the app data directory. They now live in the OS credential store via keyring 4 (macOS Keychain, Windows Credential Manager, Secret Service on Linux); each file keeps only an index of keys under a per-directory id. Files from older versions are moved key by key on first use, with a .migrating sidecar so a failed or interrupted move retries without duplicating credentials. Without a credential store (headless Linux) the old file is still read but nothing new is written to disk; the secret lasts for the session. New data sources no longer save their password or SSH tunnel secret unless asked. Also keeps the index path in SecretStore for global data sources and runs their test against the in-memory store. Fixes #740 (credentials part) Co-authored-by: LittleGuava <52166187+LittleGuava@users.noreply.github.com>
77 lines
3.1 KiB
Rust
77 lines
3.1 KiB
Rust
//! System Settings › HTTP Proxy: applied on write, the password kept out of settings.toml.
|
||
|
||
use ide_core::NullSink;
|
||
use ide_core::proxy_settings::{ProxyMode, ProxyRoute, proxy};
|
||
use ide_host::{Host, HostConfig, ops};
|
||
use ide_workspace::SettingsScope;
|
||
use std::sync::Arc;
|
||
|
||
fn host(base: &std::path::Path) -> Arc<Host> {
|
||
Arc::new(Host::new(HostConfig {
|
||
config_dir: base.join("config"),
|
||
data_dir: base.join("data"),
|
||
local_data_dir: base.join("data"),
|
||
languages: Arc::new(ide_language::LanguageRegistry::load(None).unwrap()),
|
||
syntax: Arc::default(),
|
||
sink_for: Box::new(|_| Arc::new(NullSink)),
|
||
owner: |w| w,
|
||
runtime: tokio::runtime::Handle::current(),
|
||
project_environment: false,
|
||
}))
|
||
}
|
||
|
||
fn set(host: &Host, key: &str, value: &str) -> ide_core::settings::Settings {
|
||
ops::project::set_settings(host, "main", SettingsScope::Global, key, value).unwrap().settings
|
||
}
|
||
|
||
fn settings_files(base: &std::path::Path) -> String {
|
||
let mut text = String::new();
|
||
for entry in walk(&base.join("config")) {
|
||
text.push_str(&std::fs::read_to_string(entry).unwrap_or_default());
|
||
}
|
||
text
|
||
}
|
||
|
||
fn walk(dir: &std::path::Path) -> Vec<std::path::PathBuf> {
|
||
let mut out = Vec::new();
|
||
for entry in std::fs::read_dir(dir).into_iter().flatten().flatten() {
|
||
let path = entry.path();
|
||
if path.is_dir() { out.extend(walk(&path)) } else { out.push(path) }
|
||
}
|
||
out
|
||
}
|
||
|
||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||
async fn manual_proxy_applies_and_the_password_is_saved_apart() {
|
||
ide_ssh::secrets::testing::use_memory_store();
|
||
let tmp = tempfile::tempdir().unwrap();
|
||
let host = host(tmp.path());
|
||
assert_eq!(proxy().mode, ProxyMode::Auto);
|
||
|
||
set(&host, "proxy.mode", "\"manual\"");
|
||
set(&host, "proxy.host", "\"proxy.corp\"");
|
||
set(&host, "proxy.port", "3128");
|
||
set(&host, "proxy.authenticate", "true");
|
||
set(&host, "proxy.login", "\"me\"");
|
||
let settings = set(&host, "proxy.password", "\"s3cret\"");
|
||
assert_eq!(settings.proxy.password, "s3cret", "the dialog sees the saved password");
|
||
assert_eq!(
|
||
proxy().route_for_url("https://example.com/"),
|
||
ProxyRoute::Via("http://me:s3cret@proxy.corp:3128".into())
|
||
);
|
||
|
||
assert!(!settings_files(tmp.path()).contains("s3cret"), "no password in settings.toml");
|
||
let saved = std::fs::read_to_string(tmp.path().join("data/proxy-password.json")).unwrap();
|
||
assert!(!saved.contains("s3cret"), "the password is in the credential store, not on disk: {saved}");
|
||
assert_eq!(ops::project::settings(&host, "main").proxy.password, "s3cret");
|
||
|
||
// A whole-table write keeps the password out of the file too; an empty one forgets it.
|
||
let settings = set(&host, "proxy", r#"{"mode":"none","host":"proxy.corp","password":""}"#);
|
||
assert_eq!(settings.proxy.password, "");
|
||
assert!(!settings_files(tmp.path()).contains("password"));
|
||
assert_eq!(proxy().route_for_url("https://example.com/"), ProxyRoute::Direct);
|
||
|
||
set(&host, "proxy", r#"{"mode":"auto"}"#);
|
||
assert_eq!(proxy().mode, ProxyMode::Auto);
|
||
}
|