feat: protect profiles with explicit desktop wallet selection
This commit is contained in:
138
tests/credentials.rs
Normal file
138
tests/credentials.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
use hermes_grocy_mcp::{
|
||||
credentials::{
|
||||
CredentialStore, DesktopStore,
|
||||
preferences::PreferenceStore,
|
||||
protection::{ProtectionEvidence, inspect_header},
|
||||
},
|
||||
model::{ProfileSelection, ProviderIdentity},
|
||||
};
|
||||
use std::{
|
||||
io::{BufRead, BufReader},
|
||||
os::unix::fs::PermissionsExt,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
#[test]
|
||||
fn protected_preferences() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let selection = ProfileSelection {
|
||||
profile: "default".into(),
|
||||
provider: ProviderIdentity {
|
||||
bus_name: "org.freedesktop.secrets".into(),
|
||||
executable: "/usr/bin/fixture-wallet".into(),
|
||||
},
|
||||
};
|
||||
let prefs = PreferenceStore {
|
||||
directory: dir.path().join("prefs"),
|
||||
};
|
||||
prefs.save(&selection).unwrap();
|
||||
assert_eq!(prefs.load("default").unwrap(), selection);
|
||||
assert_eq!(
|
||||
std::fs::metadata(prefs.directory.join("default.json"))
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777,
|
||||
0o600
|
||||
);
|
||||
let header = dir.path().join("header");
|
||||
std::fs::write(&header, b"[keyring]\nplaintext").unwrap();
|
||||
assert_eq!(inspect_header(&header), ProtectionEvidence::Unprotected);
|
||||
std::fs::write(&header, b"GnomeKeyring\n\r\0\n\0\0\0\0").unwrap();
|
||||
assert_eq!(inspect_header(&header), ProtectionEvidence::Protected);
|
||||
std::fs::write(&header, b"KWALLET\n\r\0\r\n").unwrap();
|
||||
assert_eq!(inspect_header(&header), ProtectionEvidence::Unverified);
|
||||
let link = dir.path().join("link");
|
||||
std::os::unix::fs::symlink(&header, &link).unwrap();
|
||||
assert_eq!(inspect_header(&link), ProtectionEvidence::Unverified);
|
||||
assert!(prefs.load("../escape").is_err());
|
||||
}
|
||||
struct PlainService;
|
||||
#[zbus::interface(name = "org.freedesktop.Secret.Service")]
|
||||
impl PlainService {
|
||||
fn open_session(
|
||||
&self,
|
||||
algorithm: &str,
|
||||
_input: zbus::zvariant::OwnedValue,
|
||||
) -> zbus::fdo::Result<(zbus::zvariant::OwnedValue, zbus::zvariant::OwnedObjectPath)> {
|
||||
if algorithm != "plain" {
|
||||
return Err(zbus::fdo::Error::NotSupported("Only plain".into()));
|
||||
}
|
||||
Ok((
|
||||
zbus::zvariant::Value::from("").try_into().unwrap(),
|
||||
"/org/freedesktop/secrets/session/plain".try_into().unwrap(),
|
||||
))
|
||||
}
|
||||
#[zbus(property)]
|
||||
fn collections(&self) -> Vec<zbus::zvariant::OwnedObjectPath> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn selected_wallet_only() {
|
||||
if std::env::var_os("GROCY_WALLET_TEST_CHILD").is_some() {
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||
let _conn = zbus::connection::Builder::address(
|
||||
std::env::var("DBUS_SESSION_BUS_ADDRESS").unwrap().as_str(),
|
||||
)
|
||||
.unwrap()
|
||||
.name("org.freedesktop.secrets")
|
||||
.unwrap()
|
||||
.serve_at("/org/freedesktop/secrets", PlainService)
|
||||
.unwrap()
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
let selection = ProfileSelection {
|
||||
profile: "default".into(),
|
||||
provider: ProviderIdentity {
|
||||
bus_name: "org.freedesktop.secrets".into(),
|
||||
executable: std::fs::read_link("/proc/self/exe").unwrap(),
|
||||
},
|
||||
};
|
||||
assert_eq!(
|
||||
DesktopStore
|
||||
.load(selection.clone())
|
||||
.await
|
||||
.err()
|
||||
.unwrap()
|
||||
.code,
|
||||
"UNENCRYPTED_SECRET_SESSION"
|
||||
);
|
||||
let mut wrong = selection;
|
||||
wrong.provider.executable = "/not-the-selected-provider".into();
|
||||
assert_eq!(
|
||||
DesktopStore.load(wrong).await.err().unwrap().code,
|
||||
"WALLET_PROVIDER_CHANGED"
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
let mut daemon = Command::new("dbus-daemon")
|
||||
.args(["--session", "--nofork", "--print-address"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let mut address = String::new();
|
||||
BufReader::new(daemon.stdout.take().unwrap())
|
||||
.read_line(&mut address)
|
||||
.unwrap();
|
||||
let result = Command::new(std::env::current_exe().unwrap())
|
||||
.args([
|
||||
"--exact",
|
||||
"selected_wallet_only",
|
||||
"--nocapture",
|
||||
"--test-threads=1",
|
||||
])
|
||||
.env("GROCY_WALLET_TEST_CHILD", "1")
|
||||
.env("DBUS_SESSION_BUS_ADDRESS", address.trim())
|
||||
.output()
|
||||
.unwrap();
|
||||
let _ = daemon.kill();
|
||||
let _ = daemon.wait();
|
||||
assert!(
|
||||
result.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&result.stdout)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user