Implement audited findings
Some checks failed
Dependency security audit / rustsec (push) Failing after 3s
Some checks failed
Dependency security audit / rustsec (push) Failing after 3s
This commit is contained in:
@@ -6,7 +6,7 @@ use std::{
|
||||
error::Error,
|
||||
fmt, fs,
|
||||
fs::OpenOptions,
|
||||
io::Write,
|
||||
io::{self, Write},
|
||||
path::{Component, Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
@@ -638,6 +638,13 @@ impl Config {
|
||||
}
|
||||
|
||||
pub(crate) fn persist(&self) -> Result<(), ConfigError> {
|
||||
self.persist_with_directory_sync(sync_config_directory)
|
||||
}
|
||||
|
||||
fn persist_with_directory_sync(
|
||||
&self,
|
||||
sync_parent: impl FnOnce(&Dir) -> io::Result<()>,
|
||||
) -> Result<(), ConfigError> {
|
||||
let parent = self.source.parent().ok_or_else(|| ConfigError::Write {
|
||||
path: self.source.clone(),
|
||||
})?;
|
||||
@@ -668,10 +675,20 @@ impl Config {
|
||||
.and_then(|()| temporary.replace(name))
|
||||
.map_err(|_| ConfigError::Write {
|
||||
path: self.source.clone(),
|
||||
})
|
||||
})?;
|
||||
sync_parent(&directory).map_err(|_| ConfigError::DurabilityUncertain {
|
||||
path: self.source.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn persist_new(&self) -> Result<(), ConfigError> {
|
||||
self.persist_new_with_directory_sync(sync_config_directory)
|
||||
}
|
||||
|
||||
fn persist_new_with_directory_sync(
|
||||
&self,
|
||||
sync_parent: impl FnOnce(&Dir) -> io::Result<()>,
|
||||
) -> Result<(), ConfigError> {
|
||||
let parent = self.source.parent().ok_or_else(|| ConfigError::Write {
|
||||
path: self.source.clone(),
|
||||
})?;
|
||||
@@ -689,6 +706,10 @@ impl Config {
|
||||
set_private_directory(parent).map_err(|_| ConfigError::Write {
|
||||
path: self.source.clone(),
|
||||
})?;
|
||||
let directory =
|
||||
Dir::open_ambient_dir(parent, ambient_authority()).map_err(|_| ConfigError::Write {
|
||||
path: self.source.clone(),
|
||||
})?;
|
||||
let contents = toml::to_string_pretty(&self.document).map_err(|_| ConfigError::Write {
|
||||
path: self.source.clone(),
|
||||
})?;
|
||||
@@ -725,7 +746,9 @@ impl Config {
|
||||
drop(temporary_file);
|
||||
let _ = fs::remove_file(&temporary_path);
|
||||
match installed {
|
||||
Ok(()) => Ok(()),
|
||||
Ok(()) => sync_parent(&directory).map_err(|_| ConfigError::DurabilityUncertain {
|
||||
path: self.source.clone(),
|
||||
}),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {
|
||||
Err(ConfigError::AlreadyConfigured {
|
||||
path: self.source.clone(),
|
||||
@@ -738,6 +761,10 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_config_directory(directory: &Dir) -> io::Result<()> {
|
||||
directory.open(".").and_then(|file| file.sync_all())
|
||||
}
|
||||
|
||||
fn git_remote_document(remote: &GitRemote) -> Result<toml::Table, ConfigError> {
|
||||
let mut configured = toml::Table::new();
|
||||
configured.insert(
|
||||
@@ -1412,6 +1439,7 @@ pub enum ConfigError {
|
||||
VaultUnavailable { path: PathBuf },
|
||||
VaultIsNotDirectory { path: PathBuf },
|
||||
Write { path: PathBuf },
|
||||
DurabilityUncertain { path: PathBuf },
|
||||
AlreadyConfigured { path: PathBuf },
|
||||
KeyMaterialNotFound { path: PathBuf },
|
||||
InvalidKeyMaterial { path: PathBuf },
|
||||
@@ -1483,6 +1511,11 @@ impl fmt::Display for ConfigError {
|
||||
path.display()
|
||||
)
|
||||
}
|
||||
Self::DurabilityUncertain { path } => write!(
|
||||
formatter,
|
||||
"configuration replacement completed but its directory sync failed: {}",
|
||||
path.display()
|
||||
),
|
||||
Self::AlreadyConfigured { path } => write!(
|
||||
formatter,
|
||||
"configuration already exists and was not replaced: {}",
|
||||
@@ -2315,7 +2348,7 @@ fn native_config_directory() -> Option<PathBuf> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs;
|
||||
use std::{fs, io};
|
||||
|
||||
use crate::mobile::MobileTab;
|
||||
|
||||
@@ -2401,4 +2434,48 @@ key_material = "keys"
|
||||
assert!(contents.contains("vault = \"vault\""));
|
||||
assert!(!contents.contains("OLD-CONTAINER"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn post_install_directory_sync_failure_is_distinct_and_keeps_complete_config() {
|
||||
let temporary = tempfile::tempdir().expect("temporary directory");
|
||||
let vault = temporary.path().join("vault");
|
||||
let keys = temporary.path().join("keys");
|
||||
let source = temporary.path().join("config.toml");
|
||||
fs::create_dir(&vault).expect("vault");
|
||||
fs::create_dir(&keys).expect("keys");
|
||||
let remote = GitRemote::https(
|
||||
"origin",
|
||||
"https://example.test/team/passwords.git",
|
||||
"server-example",
|
||||
"repository-example",
|
||||
)
|
||||
.expect("remote");
|
||||
let config = Config::create_mobile_clone(source.clone(), &vault, &keys, "ALICE", &remote)
|
||||
.expect("initial config");
|
||||
|
||||
let error = config
|
||||
.persist_with_directory_sync(|_| Err(io::Error::other("simulated sync failure")))
|
||||
.expect_err("replacement directory sync must fail");
|
||||
assert_eq!(
|
||||
error,
|
||||
ConfigError::DurabilityUncertain {
|
||||
path: source.clone()
|
||||
}
|
||||
);
|
||||
Config::load(Some(&source)).expect("replacement is complete");
|
||||
|
||||
let created = temporary.path().join("created.toml");
|
||||
let mut new_config = config;
|
||||
new_config.source = created.clone();
|
||||
let error = new_config
|
||||
.persist_new_with_directory_sync(|_| Err(io::Error::other("simulated sync failure")))
|
||||
.expect_err("creation directory sync must fail");
|
||||
assert_eq!(
|
||||
error,
|
||||
ConfigError::DurabilityUncertain {
|
||||
path: created.clone()
|
||||
}
|
||||
);
|
||||
Config::load(Some(&created)).expect("creation is complete");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user