Add additive KDBX importer

This commit is contained in:
2026-08-11 09:04:48 +02:00
parent f84f7247d2
commit bf75465642
23 changed files with 1959 additions and 82 deletions

View File

@@ -26,6 +26,7 @@ use ironstorage::{
crypto::KeyStore,
generate::{GeneratorConfig, PasswordGenerator},
git::{GitChangeKind, GitIdentity, GitRepository, PullOutcome},
kdbx::KdbxImporter,
mutation::{
MutationError, NoGitTreeCommitter, TreeCommit, TreeCommitError, TreeCommitter, TreeMutator,
},
@@ -177,6 +178,7 @@ fn needs_secret_store(request: &CommandRequest) -> bool {
| CommandRequest::Remove(_)
| CommandRequest::Move(_)
| CommandRequest::Copy(_)
| CommandRequest::ImportKdbx(_)
| CommandRequest::Otp(
OtpRequest::Code(_)
| OtpRequest::Insert(_)
@@ -651,6 +653,32 @@ fn execute_secure_with_services<
interaction,
stderr,
),
CommandRequest::ImportKdbx(request) => {
let password = match interaction.read_kdbx_password(request.source()) {
Ok(password) => password,
Err(error) => return operation_error(stderr, error),
};
match KdbxImporter::new(&repository, &keys).import(
request,
password,
secrets,
git_identity(),
) {
Ok(outcome) => {
writeln!(
stdout,
"KDBX import: {} added, {} updated, {} unchanged, {} skipped",
outcome.added(),
outcome.updated(),
outcome.unchanged(),
outcome.skipped()
)
.map_err(|_| ())?;
Ok(EXIT_SUCCESS)
}
Err(error) => operation_error(stderr, error),
}
}
CommandRequest::Otp(request) => execute_otp(
config,
request,
@@ -1026,6 +1054,8 @@ trait OtpInteraction {
}
trait CliInteraction: OtpInteraction {
fn read_kdbx_password(&mut self, source: &Path) -> Result<SecretBytes, CliInteractionError>;
fn read_insert(
&mut self,
plan: InputPlan,
@@ -1130,6 +1160,12 @@ impl OtpInteraction for NativeOtpInteraction {
}
impl CliInteraction for NativeOtpInteraction {
fn read_kdbx_password(&mut self, source: &Path) -> Result<SecretBytes, CliInteractionError> {
rpassword::prompt_password(format!("Enter password for {}: ", source.display()))
.map(|password| SecretBytes::new(password.into_bytes()))
.map_err(|_| CliInteractionError::Input)
}
fn read_insert(
&mut self,
plan: InputPlan,
@@ -1213,6 +1249,10 @@ impl OtpInteraction for UnavailableOtpInteraction {
#[cfg(test)]
impl CliInteraction for UnavailableOtpInteraction {
fn read_kdbx_password(&mut self, _source: &Path) -> Result<SecretBytes, CliInteractionError> {
Err(CliInteractionError::Input)
}
fn read_insert(
&mut self,
_plan: InputPlan,
@@ -1611,6 +1651,7 @@ mod tests {
inputs: VecDeque<OtpInput>,
insert_inputs: VecDeque<ironstorage::write::InsertContent>,
edit_replacements: VecDeque<SecretBytes>,
kdbx_passwords: VecDeque<SecretBytes>,
decisions: VecDeque<OverwriteDecision>,
plans: Vec<InputPlan>,
prompts: Vec<String>,
@@ -1644,6 +1685,15 @@ mod tests {
}
impl super::CliInteraction for MemoryOtpInteraction {
fn read_kdbx_password(
&mut self,
_source: &std::path::Path,
) -> Result<SecretBytes, super::CliInteractionError> {
self.kdbx_passwords
.pop_front()
.ok_or(super::CliInteractionError::Input)
}
fn read_insert(
&mut self,
_plan: InputPlan,