From 4e103f67ca8699a7c32ccd6e325e360490970ebb Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Tue, 10 Dec 2024 11:06:01 +0100 Subject: [PATCH 1/3] feat(commands): Add missing key subcommands --- src/commands/key.rs | 178 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 153 insertions(+), 25 deletions(-) diff --git a/src/commands/key.rs b/src/commands/key.rs index 47eda0ef1..dc943c357 100644 --- a/src/commands/key.rs +++ b/src/commands/key.rs @@ -1,15 +1,17 @@ //! `key` subcommand -use crate::{repository::CliOpenRepo, status_err, Application, RUSTIC_APP}; +use crate::{ + helpers::table_with_titles, repository::CliOpenRepo, status_err, Application, RUSTIC_APP, +}; use std::path::PathBuf; use abscissa_core::{Command, Runnable, Shutdown}; use anyhow::Result; use dialoguer::Password; -use log::info; +use log::{info, warn}; -use rustic_core::{CommandInput, KeyOptions, RepositoryOptions}; +use rustic_core::{repofile::KeyFile, CommandInput, KeyOptions, RepositoryOptions}; /// `key` subcommand #[derive(clap::Parser, Command, Debug)] @@ -19,14 +21,26 @@ pub(super) struct KeyCmd { cmd: KeySubCmd, } +impl Runnable for KeyCmd { + fn run(&self) { + self.cmd.run(); + } +} + #[derive(clap::Subcommand, Debug, Runnable)] enum KeySubCmd { /// Add a new key to the repository Add(AddCmd), + /// List all keys in the repository + List(ListCmd), + /// Remove a key from the repository + Remove(RemoveCmd), + /// Change the password of a key + Password(PasswordCmd), } #[derive(clap::Parser, Debug)] -pub(crate) struct AddCmd { +pub(crate) struct NewPasswordOptions { /// New password #[clap(long)] pub(crate) new_password: Option, @@ -38,19 +52,69 @@ pub(crate) struct AddCmd { /// Command to get the new password from #[clap(long)] pub(crate) new_password_command: Option, +} + +impl NewPasswordOptions { + fn pass(&self, text: &str) -> Result { + // create new Repository options which just contain password information + let mut pass_opts = RepositoryOptions::default(); + pass_opts.password = self.new_password.clone(); + pass_opts.password_file = self.new_password_file.clone(); + pass_opts.password_command = self.new_password_command.clone(); + + let pass = pass_opts + .evaluate_password() + .map_err(Into::into) + .transpose() + .unwrap_or_else(|| -> Result<_> { + Ok(Password::new() + .with_prompt(text) + .allow_empty_password(true) + .with_confirmation("confirm password", "passwords do not match") + .interact()?) + })?; + Ok(pass) + } +} + +#[derive(clap::Parser, Debug)] +pub(crate) struct AddCmd { + /// New password options + #[clap(flatten)] + pub(crate) pass_opts: NewPasswordOptions, /// Key options #[clap(flatten)] pub(crate) key_opts: KeyOptions, } -impl Runnable for KeyCmd { +impl Runnable for AddCmd { fn run(&self) { - self.cmd.run(); + if let Err(err) = RUSTIC_APP + .config() + .repository + .run_open(|repo| self.inner_run(repo)) + { + status_err!("{}", err); + RUSTIC_APP.shutdown(Shutdown::Crash); + }; } } -impl Runnable for AddCmd { +impl AddCmd { + fn inner_run(&self, repo: CliOpenRepo) -> Result<()> { + let pass = self.pass_opts.pass("enter password for new key")?; + let id = repo.add_key(&pass, &self.key_opts)?; + info!("key {id} successfully added."); + + Ok(()) + } +} + +#[derive(clap::Parser, Debug)] +pub(crate) struct ListCmd; + +impl Runnable for ListCmd { fn run(&self) { if let Err(err) = RUSTIC_APP .config() @@ -63,29 +127,93 @@ impl Runnable for AddCmd { } } -impl AddCmd { +impl ListCmd { fn inner_run(&self, repo: CliOpenRepo) -> Result<()> { - // create new Repository options which just contain password information - let mut pass_opts = RepositoryOptions::default(); - pass_opts.password = self.new_password.clone(); - pass_opts.password_file = self.new_password_file.clone(); - pass_opts.password_command = self.new_password_command.clone(); + let used_key = repo.key_id(); + let keys = repo + .stream_files()? + .inspect(|f| { + if let Err(err) = f { + warn!("{err:?}"); + } + }) + .filter_map(Result::ok); + + let mut table = table_with_titles(["ID", "User", "Host", "Created"]); + _ = table.add_rows(keys.map(|key: (_, KeyFile)| { + [ + format!("{}{}", if used_key == &key.0 { "*" } else { "" }, key.0), + key.1.username.unwrap_or_default(), + key.1.hostname.unwrap_or_default(), + key.1 + .created + .map_or(String::new(), |time| format!("{time}")), + ] + })); + println!("{table}"); + Ok(()) + } +} - let pass = pass_opts - .evaluate_password() - .map_err(Into::into) - .transpose() - .unwrap_or_else(|| -> Result<_> { - Ok(Password::new() - .with_prompt("enter password for new key") - .allow_empty_password(true) - .with_confirmation("confirm password", "passwords do not match") - .interact()?) - })?; +#[derive(clap::Parser, Debug)] +pub(crate) struct RemoveCmd { + /// The key is to remove + id: String, +} - let id = repo.add_key(&pass, &self.key_opts)?; +impl Runnable for RemoveCmd { + fn run(&self) { + if let Err(err) = RUSTIC_APP + .config() + .repository + .run_open(|repo| self.inner_run(repo)) + { + status_err!("{}", err); + RUSTIC_APP.shutdown(Shutdown::Crash); + }; + } +} + +impl RemoveCmd { + fn inner_run(&self, repo: CliOpenRepo) -> Result<()> { + repo.delete_key(&self.id)?; + info!("key {} successfully removed.", self.id); + Ok(()) + } +} +#[derive(clap::Parser, Debug)] +pub(crate) struct PasswordCmd { + /// New password options + #[clap(flatten)] + pub(crate) pass_opts: NewPasswordOptions, +} + +impl Runnable for PasswordCmd { + fn run(&self) { + if let Err(err) = RUSTIC_APP + .config() + .repository + .run_open(|repo| self.inner_run(repo)) + { + status_err!("{}", err); + RUSTIC_APP.shutdown(Shutdown::Crash); + }; + } +} + +impl PasswordCmd { + fn inner_run(&self, repo: CliOpenRepo) -> Result<()> { + let pass = self.pass_opts.pass("enter new password")?; + let key_opts = KeyOptions::default(); + let id = repo.add_key(&pass, &key_opts)?; info!("key {id} successfully added."); + let old_key = *repo.key_id(); + // re-open repository using new password + let repo = repo.open_with_password(&pass)?; + repo.delete_key(&old_key.to_string())?; + info!("key {old_key} successfully removed."); + Ok(()) } } From 101b6a187ac0f939b0dd6fa50079fb150b49ade1 Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Tue, 10 Dec 2024 14:46:26 +0100 Subject: [PATCH 2/3] add key information when using key password --- src/commands/key.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/commands/key.rs b/src/commands/key.rs index dc943c357..eb25e8fe5 100644 --- a/src/commands/key.rs +++ b/src/commands/key.rs @@ -204,7 +204,11 @@ impl Runnable for PasswordCmd { impl PasswordCmd { fn inner_run(&self, repo: CliOpenRepo) -> Result<()> { let pass = self.pass_opts.pass("enter new password")?; - let key_opts = KeyOptions::default(); + let old_key: KeyFile = repo.get_file(repo.key_id())?; + let key_opts = KeyOptions::default() + .hostname(old_key.hostname) + .username(old_key.username) + .with_created(old_key.created.is_some()); let id = repo.add_key(&pass, &key_opts)?; info!("key {id} successfully added."); From 7d10b929600785988aed5b99f04ad838ee36db09 Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Tue, 10 Dec 2024 11:16:38 +0100 Subject: [PATCH 3/3] branch dependency --- Cargo.lock | 62 +++++++++++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 4 ++-- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0fb45770..9954bf2fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3931,7 +3931,7 @@ dependencies = [ "rhai", "rstest", "rustic_backend", - "rustic_core", + "rustic_core 0.7.3", "rustic_testing", "scopeguard", "self_update", @@ -3953,8 +3953,7 @@ dependencies = [ [[package]] name = "rustic_backend" version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e9176f9b6c75a2073776726110de99d10a0db1fdef9c9fb7a810ccb54cfbf5" +source = "git+https://github.com/rustic-rs/rustic_core.git?branch=more-key-control#d456270e865020971a10ef696cc625b2b102349a" dependencies = [ "aho-corasick", "backon", @@ -3972,7 +3971,7 @@ dependencies = [ "rand", "rayon", "reqwest 0.12.9", - "rustic_core", + "rustic_core 0.7.3", "semver", "serde", "strum", @@ -3995,6 +3994,59 @@ name = "rustic_core" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8a9c8d085732c1831d3742708b07f9a38516b6418cd43476227b98fa3b2d0d2" +dependencies = [ + "aes256ctr_poly1305aes", + "binrw", + "bytes", + "bytesize", + "cached", + "cachedir", + "chrono", + "crossbeam-channel", + "derive_more", + "derive_setters", + "dirs", + "displaydoc", + "dunce", + "ecow", + "enum-map", + "enum-map-derive", + "enumset", + "filetime", + "gethostname", + "hex", + "humantime", + "ignore", + "integer-sqrt", + "itertools", + "log", + "nix", + "pariter", + "path-dedot", + "quick_cache", + "rand", + "rayon", + "runtime-format", + "rustic_cdc", + "scrypt", + "serde", + "serde-aux", + "serde_derive", + "serde_json", + "serde_with", + "sha2", + "shell-words", + "strum", + "thiserror 2.0.3", + "walkdir", + "xattr", + "zstd", +] + +[[package]] +name = "rustic_core" +version = "0.7.3" +source = "git+https://github.com/rustic-rs/rustic_core.git?branch=more-key-control#d456270e865020971a10ef696cc625b2b102349a" dependencies = [ "aes256ctr_poly1305aes", "binrw", @@ -4056,7 +4108,7 @@ dependencies = [ "anyhow", "bytes", "enum-map", - "rustic_core", + "rustic_core 0.7.2", "tempfile", ] diff --git a/Cargo.toml b/Cargo.toml index 75dda4bae..5f520ee15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,8 +57,8 @@ rustdoc-args = ["--document-private-items", "--generate-link-to-definition"] [dependencies] abscissa_core = { version = "0.8.1", default-features = false, features = ["application"] } -rustic_backend = { version = "0.5.2", features = ["cli"] } -rustic_core = { version = "0.7.2", features = ["cli"] } +rustic_backend = { git = "https://github.com/rustic-rs/rustic_core.git", branch = "more-key-control", features = ["cli"] } +rustic_core = { git = "https://github.com/rustic-rs/rustic_core.git", branch = "more-key-control", features = ["cli"] } # allocators jemallocator-global = { version = "0.3.2", optional = true }