CI / docs-check (push) Canceled after 0s
CI / backend-test (push) Canceled after 0s
CI / service-test (push) Canceled after 0s
CI / frontend-test (push) Canceled after 0s
CI / rust-core (push) Canceled after 0s
CI / docs-check (pull_request) Canceled after 0s
CI / backend-test (pull_request) Canceled after 0s
CI / service-test (pull_request) Canceled after 0s
CI / frontend-test (pull_request) Canceled after 0s
CI / rust-core (pull_request) Canceled after 0s
115 lines
3.9 KiB
Rust
115 lines
3.9 KiB
Rust
use notesagent_host::credentials::{CredentialBroker, CredentialId, Scope};
|
|
use zeroize::Zeroizing;
|
|
fn password() -> Zeroizing<Vec<u8>> {
|
|
Zeroizing::new(b"isolated-ownership-fixture".to_vec())
|
|
}
|
|
#[test]
|
|
fn stale_writer_is_rejected_then_handoff_preserves_both_commits() {
|
|
let temp = tempfile::tempdir().unwrap();
|
|
let path = temp.path().join("vault");
|
|
let a = CredentialId::legacy("first");
|
|
let b = CredentialId::legacy("second");
|
|
let mut one = CredentialBroker::new(path.clone());
|
|
one.unlock(password()).unwrap();
|
|
let mut two = CredentialBroker::new(path.clone());
|
|
assert_eq!(two.unlock(password()).unwrap_err(), "CREDENTIALS_BUSY");
|
|
assert!(two.is_locked());
|
|
one.put(&a, Zeroizing::new(b"fixture-a".to_vec())).unwrap();
|
|
assert_eq!(
|
|
two.put(&b, Zeroizing::new(b"fixture-b".to_vec()))
|
|
.unwrap_err(),
|
|
"CREDENTIALS_LOCKED"
|
|
);
|
|
one.lock();
|
|
two.unlock(password()).unwrap();
|
|
assert!(two.resolve(&Scope::Provider, &a).unwrap().is_some());
|
|
two.put(&b, Zeroizing::new(b"fixture-b".to_vec())).unwrap();
|
|
two.lock();
|
|
// 失败的密码尝试也必须释放其所有权。
|
|
assert!(one
|
|
.unlock(Zeroizing::new(b"wrong-fixture-password".to_vec()))
|
|
.is_err());
|
|
two.unlock(password()).unwrap();
|
|
assert!(two.resolve(&Scope::Provider, &a).unwrap().is_some());
|
|
assert!(two.resolve(&Scope::Provider, &b).unwrap().is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn process_lock_is_exclusive_and_released_on_crash() {
|
|
use std::io::{BufRead, Read, Write};
|
|
use std::process::{Command, Stdio};
|
|
if let Ok(role) = std::env::var("OPENNEXUS_OWNERSHIP_TEST_ROLE") {
|
|
let path =
|
|
std::path::PathBuf::from(std::env::var_os("OPENNEXUS_OWNERSHIP_TEST_PATH").unwrap());
|
|
let mut broker = CredentialBroker::new(path);
|
|
if role == "blocked" {
|
|
assert_eq!(broker.unlock(password()).unwrap_err(), "CREDENTIALS_BUSY");
|
|
return;
|
|
}
|
|
broker.unlock(password()).unwrap();
|
|
broker
|
|
.put(
|
|
&CredentialId::legacy("child"),
|
|
Zeroizing::new(b"child-fixture".to_vec()),
|
|
)
|
|
.unwrap();
|
|
println!("OWNERSHIP_READY");
|
|
std::io::stdout().flush().unwrap();
|
|
let _ = std::io::stdin().read_exact(&mut [0]);
|
|
return;
|
|
}
|
|
let temp = tempfile::tempdir().unwrap();
|
|
let path = temp.path().join("vault");
|
|
let mut broker = CredentialBroker::new(path.clone());
|
|
broker.unlock(password()).unwrap();
|
|
let command = |role: &str| {
|
|
let mut c = Command::new(std::env::current_exe().unwrap());
|
|
c.args([
|
|
"--exact",
|
|
"process_lock_is_exclusive_and_released_on_crash",
|
|
"--nocapture",
|
|
])
|
|
.env("OPENNEXUS_OWNERSHIP_TEST_ROLE", role)
|
|
.env("OPENNEXUS_OWNERSHIP_TEST_PATH", &path);
|
|
c
|
|
};
|
|
assert!(command("blocked")
|
|
.stdout(Stdio::null())
|
|
.status()
|
|
.unwrap()
|
|
.success());
|
|
broker.lock();
|
|
let mut child = command("owner")
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.spawn()
|
|
.unwrap();
|
|
let output = child.stdout.take().unwrap();
|
|
let (sender, receiver) = std::sync::mpsc::channel();
|
|
std::thread::spawn(move || {
|
|
for line in std::io::BufReader::new(output)
|
|
.lines()
|
|
.map_while(Result::ok)
|
|
{
|
|
if line == "OWNERSHIP_READY" {
|
|
let _ = sender.send(());
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
let ready = receiver
|
|
.recv_timeout(std::time::Duration::from_secs(30))
|
|
.is_ok();
|
|
if ready {
|
|
assert_eq!(broker.unlock(password()).unwrap_err(), "CREDENTIALS_BUSY");
|
|
}
|
|
child.kill().unwrap();
|
|
child.wait().unwrap();
|
|
assert!(ready, "child never acquired ownership");
|
|
broker.unlock(password()).unwrap();
|
|
assert!(broker
|
|
.resolve(&Scope::Provider, &CredentialId::legacy("child"))
|
|
.unwrap()
|
|
.is_some());
|
|
}
|