Cache TOTP discovery and report progress (#74)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
//! Capability-scoped password-store repository discovery and atomic file access.
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
collections::BTreeMap,
|
||||
error::Error,
|
||||
ffi::{OsStr, OsString},
|
||||
fmt, fs,
|
||||
@@ -9,6 +9,9 @@ use std::{
|
||||
path::{Component, Path, PathBuf},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use cap_std::{ambient_authority, fs::Dir};
|
||||
use cap_tempfile::TempFile;
|
||||
use zeroize::Zeroize;
|
||||
@@ -262,7 +265,6 @@ pub struct RepositorySnapshot {
|
||||
recipients: BTreeMap<DirectoryPath, RecipientPolicy>,
|
||||
git_repositories: BTreeMap<DirectoryPath, GitRepository>,
|
||||
auxiliary_files: BTreeMap<PathBuf, AuxiliaryFile>,
|
||||
collisions: BTreeSet<PathBuf>,
|
||||
}
|
||||
|
||||
impl RepositorySnapshot {
|
||||
@@ -286,10 +288,6 @@ impl RepositorySnapshot {
|
||||
self.auxiliary_files.values()
|
||||
}
|
||||
|
||||
pub fn collisions(&self) -> impl ExactSizeIterator<Item = &PathBuf> {
|
||||
self.collisions.iter()
|
||||
}
|
||||
|
||||
/// Resolve an upstream-style display path. A trailing slash explicitly selects a directory.
|
||||
pub fn resolve(&self, input: &str) -> Result<ResolvedObject<'_>, RepositoryError> {
|
||||
let directory_only = input.ends_with('/') || (cfg!(windows) && input.ends_with('\\'));
|
||||
@@ -310,10 +308,7 @@ impl RepositorySnapshot {
|
||||
let entry = EntryPath::parse(trimmed)?;
|
||||
let entry_record = self.entries.get(&entry);
|
||||
match (entry_record, directory_record) {
|
||||
(Some(_), Some(_)) => Err(RepositoryError::AmbiguousPath {
|
||||
path: entry.0.clone(),
|
||||
}),
|
||||
(Some(entry), None) => Ok(ResolvedObject::Entry(entry)),
|
||||
(Some(entry), _) => Ok(ResolvedObject::Entry(entry)),
|
||||
(None, Some(directory)) => Ok(ResolvedObject::Directory(directory)),
|
||||
(None, None) => Err(RepositoryError::NotFound {
|
||||
path: entry.0.clone(),
|
||||
@@ -390,14 +385,6 @@ impl Repository {
|
||||
pub fn snapshot(&self) -> Result<RepositorySnapshot, RepositoryError> {
|
||||
let mut snapshot = RepositorySnapshot::default();
|
||||
scan_directory(&self.root, &DirectoryPath::root(), &mut snapshot)?;
|
||||
for entry in snapshot.entries.keys() {
|
||||
if snapshot
|
||||
.directories
|
||||
.contains_key(&DirectoryPath(entry.0.clone()))
|
||||
{
|
||||
snapshot.collisions.insert(entry.0.clone());
|
||||
}
|
||||
}
|
||||
Ok(snapshot)
|
||||
}
|
||||
|
||||
@@ -540,7 +527,7 @@ impl Repository {
|
||||
{
|
||||
let (parent, file_name, created) = self.create_entry_parent(path)?;
|
||||
let encrypted_path = path.encrypted_relative_path();
|
||||
if let Err(error) = validate_write_target(&parent, &file_name, &path.0, &encrypted_path) {
|
||||
if let Err(error) = validate_write_target(&parent, &file_name, &encrypted_path) {
|
||||
return self.rollback_created(created, error);
|
||||
}
|
||||
|
||||
@@ -598,7 +585,6 @@ impl Repository {
|
||||
.0
|
||||
.file_name()
|
||||
.expect("non-root directory has a file name");
|
||||
reject_entry_directory_collision(&parent_handle, name, &parent.0)?;
|
||||
let metadata = child_metadata(&parent_handle, name, &directory.0)?;
|
||||
let Some(metadata) = metadata else {
|
||||
current = directory.parent();
|
||||
@@ -651,7 +637,6 @@ impl Repository {
|
||||
.as_path()
|
||||
.file_name()
|
||||
.expect("non-root directory has a file name");
|
||||
reject_entry_directory_collision(&parent_handle, name, parent.as_path())?;
|
||||
let Some(metadata) = child_metadata(&parent_handle, name, directory.as_path())? else {
|
||||
return Ok(false);
|
||||
};
|
||||
@@ -691,7 +676,6 @@ impl Repository {
|
||||
file_name.push(".gpg");
|
||||
return Ok((directory, file_name));
|
||||
}
|
||||
reject_entry_directory_collision(&directory, name, &relative)?;
|
||||
relative.push(name);
|
||||
let metadata = child_metadata(&directory, name, &relative)?;
|
||||
let Some(metadata) = metadata else {
|
||||
@@ -725,9 +709,6 @@ impl Repository {
|
||||
file_name.push(".gpg");
|
||||
return Ok((directory, file_name, created));
|
||||
}
|
||||
if let Err(error) = reject_entry_directory_collision(&directory, name, &relative) {
|
||||
return self.rollback_created(created, error);
|
||||
}
|
||||
relative.push(name);
|
||||
match child_metadata(&directory, name, &relative) {
|
||||
Ok(Some(metadata)) => {
|
||||
@@ -773,9 +754,6 @@ impl Repository {
|
||||
let Component::Normal(name) = component else {
|
||||
unreachable!("DirectoryPath is validated")
|
||||
};
|
||||
if let Err(error) = reject_entry_directory_collision(&directory, name, &relative) {
|
||||
return self.rollback_created(created, error);
|
||||
}
|
||||
relative.push(name);
|
||||
match child_metadata(&directory, name, &relative) {
|
||||
Ok(Some(metadata)) => {
|
||||
@@ -845,7 +823,6 @@ impl Repository {
|
||||
let Component::Normal(name) = component else {
|
||||
unreachable!("normalized directory path has only normal components")
|
||||
};
|
||||
reject_entry_directory_collision(&directory, name, &relative)?;
|
||||
relative.push(name);
|
||||
let metadata = child_metadata(&directory, name, &relative)?.ok_or_else(|| {
|
||||
RepositoryError::NotFound {
|
||||
@@ -899,9 +876,6 @@ pub enum RepositoryError {
|
||||
Collision {
|
||||
path: PathBuf,
|
||||
},
|
||||
AmbiguousPath {
|
||||
path: PathBuf,
|
||||
},
|
||||
NotFound {
|
||||
path: PathBuf,
|
||||
},
|
||||
@@ -962,11 +936,6 @@ impl fmt::Display for RepositoryError {
|
||||
"password-store entry collides with a directory: {}",
|
||||
path.display()
|
||||
),
|
||||
Self::AmbiguousPath { path } => write!(
|
||||
formatter,
|
||||
"password-store path is both an entry and directory; add a trailing slash for the directory: {}",
|
||||
path.display()
|
||||
),
|
||||
Self::NotFound { path } => {
|
||||
write!(
|
||||
formatter,
|
||||
@@ -1198,38 +1167,11 @@ fn require_regular_file(
|
||||
}
|
||||
}
|
||||
|
||||
fn reject_entry_directory_collision(
|
||||
directory: &Dir,
|
||||
name: &OsStr,
|
||||
parent: &Path,
|
||||
) -> Result<(), RepositoryError> {
|
||||
let mut encrypted_name = name.to_os_string();
|
||||
encrypted_name.push(".gpg");
|
||||
let logical = parent.join(name);
|
||||
if child_metadata(directory, &encrypted_name, &logical)?.is_some() {
|
||||
return Err(RepositoryError::Collision { path: logical });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_write_target(
|
||||
parent: &Dir,
|
||||
file_name: &OsStr,
|
||||
logical: &Path,
|
||||
encrypted: &Path,
|
||||
) -> Result<(), RepositoryError> {
|
||||
if let Some(metadata) =
|
||||
child_metadata(parent, logical.file_name().unwrap_or_default(), logical)?
|
||||
{
|
||||
if metadata.is_dir() {
|
||||
return Err(RepositoryError::Collision {
|
||||
path: logical.to_owned(),
|
||||
});
|
||||
}
|
||||
return Err(RepositoryError::UnsupportedFileType {
|
||||
path: logical.to_owned(),
|
||||
});
|
||||
}
|
||||
if let Some(metadata) = child_metadata(parent, file_name, encrypted)? {
|
||||
require_regular_file(metadata, encrypted)?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user