From b4bcaa92ca63b7c71ea5c28d5e9a6af3ecb88a91 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Mon, 29 Apr 2024 13:39:36 +0200 Subject: [PATCH] feat: Validate args before ask for the master password (#17) Reviewed-on: https://git.4rs.nl/awiteb/lprs/pulls/17 Fixes: https://git.4rs.nl/awiteb/lprs/issues/13 Co-authored-by: Awiteb Co-committed-by: Awiteb --- src/cli/add_command.rs | 23 ++++++++------- src/cli/clean_command.rs | 4 +-- src/cli/edit_command.rs | 29 ++++++++++--------- src/cli/export_command.rs | 26 +++++++++-------- src/cli/gen_command.rs | 41 ++++++++++++++------------- src/cli/import_command.rs | 59 ++++++++++++++++++++------------------- src/cli/list_command.rs | 4 +-- src/cli/mod.rs | 4 ++- src/cli/remove_command.rs | 4 +-- src/macros.rs | 16 ++++++++--- src/traits.rs | 10 +++++-- 11 files changed, 124 insertions(+), 96 deletions(-) diff --git a/src/cli/add_command.rs b/src/cli/add_command.rs index ce3c8df..dc7f5ef 100644 --- a/src/cli/add_command.rs +++ b/src/cli/add_command.rs @@ -18,7 +18,7 @@ use clap::Args; use crate::{ vault::{vault_state::*, Vault, Vaults}, - LprsError, LprsResult, RunCommand, + LprsCommand, LprsError, LprsResult, }; #[derive(Debug, Args)] @@ -31,16 +31,8 @@ pub struct Add { password: Option>, } -impl RunCommand for Add { +impl LprsCommand for Add { fn run(mut self, mut vault_manager: Vaults) -> LprsResult<()> { - if self.vault_info.username.is_none() - && self.password.is_none() - && self.vault_info.service.is_none() - && self.vault_info.note.is_none() - { - return Err(LprsError::Other("You can't add empty vault".to_owned())); - } - match self.password { Some(Some(password)) => self.vault_info.password = Some(password), Some(None) => { @@ -58,4 +50,15 @@ impl RunCommand for Add { vault_manager.add_vault(self.vault_info); vault_manager.try_export() } + + fn validate_args(&self) -> LprsResult<()> { + if self.vault_info.username.is_none() + && self.password.is_none() + && self.vault_info.service.is_none() + && self.vault_info.note.is_none() + { + return Err(LprsError::Other("You can't add empty vault".to_owned())); + } + Ok(()) + } } diff --git a/src/cli/clean_command.rs b/src/cli/clean_command.rs index 11ae7ec..989bf1d 100644 --- a/src/cli/clean_command.rs +++ b/src/cli/clean_command.rs @@ -20,14 +20,14 @@ use clap::Args; use crate::{ vault::{vault_state::*, Vaults}, - LprsError, LprsResult, RunCommand, + LprsCommand, LprsError, LprsResult, }; #[derive(Debug, Args)] #[command(author, version, about, long_about = None)] pub struct Clean {} -impl RunCommand for Clean { +impl LprsCommand for Clean { fn run(self, vault_manager: Vaults) -> LprsResult<()> { fs::write(vault_manager.vaults_file, "[]").map_err(LprsError::Io) } diff --git a/src/cli/edit_command.rs b/src/cli/edit_command.rs index 7c594f9..9fe9b17 100644 --- a/src/cli/edit_command.rs +++ b/src/cli/edit_command.rs @@ -20,7 +20,7 @@ use clap::Args; use crate::{ vault::{vault_state::*, Vault, Vaults}, - LprsError, LprsResult, RunCommand, + LprsCommand, LprsError, LprsResult, }; #[derive(Debug, Args)] @@ -46,7 +46,7 @@ pub struct Edit { note: Option, } -impl RunCommand for Edit { +impl LprsCommand for Edit { fn run(self, mut vault_manager: Vaults) -> LprsResult<()> { let index = self.index.get() as usize; @@ -58,17 +58,6 @@ impl RunCommand for Edit { ))); }; - if self.name.is_none() - && self.username.is_none() - && self.password.is_none() - && self.service.is_none() - && self.note.is_none() - { - return Err(LprsError::Other( - "You must edit one option at least".to_owned(), - )); - } - // Get the password from stdin or from its value if provided let password = match self.password { Some(Some(password)) => Some(password), @@ -91,4 +80,18 @@ impl RunCommand for Edit { ); vault_manager.try_export() } + + fn validate_args(&self) -> LprsResult<()> { + if self.name.is_none() + && self.username.is_none() + && self.password.is_none() + && self.service.is_none() + && self.note.is_none() + { + return Err(LprsError::Other( + "You must edit one option at least".to_owned(), + )); + } + Ok(()) + } } diff --git a/src/cli/export_command.rs b/src/cli/export_command.rs index 34b7abf..56f18f5 100644 --- a/src/cli/export_command.rs +++ b/src/cli/export_command.rs @@ -20,7 +20,7 @@ use clap::Args; use crate::{ vault::{vault_state::*, BitWardenPasswords, Format, Vault, Vaults}, - LprsError, LprsResult, RunCommand, + LprsCommand, LprsError, LprsResult, }; #[derive(Debug, Args)] @@ -33,24 +33,26 @@ pub struct Export { format: Format, } -impl RunCommand for Export { +impl LprsCommand for Export { fn run(self, vault_manager: Vaults) -> LprsResult<()> { + let exported_data = match self.format { + Format::Lprs => { + serde_json::to_string::>>(&vault_manager.encrypt_vaults()?) + } + Format::BitWarden => serde_json::to_string(&BitWardenPasswords::from(vault_manager)), + }?; + + fs::write(&self.path, exported_data).map_err(LprsError::from) + } + + fn validate_args(&self) -> LprsResult<()> { if self .path .extension() .is_some_and(|e| e.to_string_lossy().eq_ignore_ascii_case("json")) { if !self.path.exists() { - let exported_data = match self.format { - Format::Lprs => serde_json::to_string::>>( - &vault_manager.encrypt_vaults()?, - ), - Format::BitWarden => { - serde_json::to_string(&BitWardenPasswords::from(vault_manager)) - } - }?; - - fs::write(&self.path, exported_data).map_err(LprsError::from) + Ok(()) } else { Err(LprsError::Io(IoError::new( IoErrorKind::AlreadyExists, diff --git a/src/cli/gen_command.rs b/src/cli/gen_command.rs index f35f92f..92dfae3 100644 --- a/src/cli/gen_command.rs +++ b/src/cli/gen_command.rs @@ -20,7 +20,7 @@ use clap::Args; use crate::{ vault::{vault_state::*, Vaults}, - LprsError, LprsResult, RunCommand, + LprsCommand, LprsError, LprsResult, }; #[derive(Debug, Args)] @@ -44,26 +44,29 @@ pub struct Gen { symbols: bool, } -impl RunCommand for Gen { +impl LprsCommand for Gen { fn run(self, _vault_manager: Vaults) -> LprsResult<()> { - if self.uppercase || self.lowercase || self.numbers || self.symbols { - println!( - "{}", - passwords::PasswordGenerator::new() - .length(self.length.get() as usize) - .uppercase_letters(self.uppercase) - .lowercase_letters(self.lowercase) - .numbers(self.numbers) - .symbols(self.symbols) - .strict(true) - .generate_one() - .expect("The length cannot be zero") - ); - Ok(()) - } else { - Err(LprsError::Other( + println!( + "{}", + passwords::PasswordGenerator::new() + .length(self.length.get() as usize) + .uppercase_letters(self.uppercase) + .lowercase_letters(self.lowercase) + .numbers(self.numbers) + .symbols(self.symbols) + .strict(true) + .generate_one() + .expect("The length cannot be zero") + ); + Ok(()) + } + + fn validate_args(&self) -> LprsResult<()> { + if !(self.uppercase || self.lowercase || self.numbers || self.symbols) { + return Err(LprsError::Other( "You need to enable at least one kind of characters".to_owned(), - )) + )); } + Ok(()) } } diff --git a/src/cli/import_command.rs b/src/cli/import_command.rs index f9de214..6c46762 100644 --- a/src/cli/import_command.rs +++ b/src/cli/import_command.rs @@ -20,7 +20,7 @@ use clap::Args; use crate::{ vault::{vault_state::*, BitWardenPasswords, Format, Vault, Vaults}, - LprsError, LprsResult, RunCommand, + LprsCommand, LprsError, LprsResult, }; #[derive(Debug, Args)] @@ -34,41 +34,42 @@ pub struct Import { format: Format, } -impl RunCommand for Import { +impl LprsCommand for Import { fn run(self, mut vault_manager: Vaults) -> LprsResult<()> { + let imported_passwords_len = match self.format { + Format::Lprs => { + let vaults = Vaults::try_reload(self.path, vault_manager.master_password.to_vec())?; + let vaults_len = vaults.vaults.len(); + + vault_manager.vaults.extend(vaults.vaults); + vault_manager.try_export()?; + vaults_len + } + Format::BitWarden => { + let vaults: BitWardenPasswords = serde_json::from_reader(File::open(&self.path)?)?; + let vaults_len = vaults.items.len(); + + vault_manager + .vaults + .extend(vaults.items.into_iter().map(Vault::from)); + vault_manager.try_export()?; + vaults_len + } + }; + println!( + "{imported_passwords_len} vault{s} were imported successfully", + s = if imported_passwords_len >= 2 { "s" } else { "" } + ); + Ok(()) + } + + fn validate_args(&self) -> LprsResult<()> { if self.path.exists() { if self .path .extension() .is_some_and(|e| e.to_string_lossy().eq_ignore_ascii_case("json")) { - let imported_passwords_len = match self.format { - Format::Lprs => { - let vaults = - Vaults::try_reload(self.path, vault_manager.master_password.to_vec())?; - let vaults_len = vaults.vaults.len(); - - vault_manager.vaults.extend(vaults.vaults); - vault_manager.try_export()?; - vaults_len - } - Format::BitWarden => { - let vaults: BitWardenPasswords = - serde_json::from_reader(File::open(&self.path)?)?; - let vaults_len = vaults.items.len(); - - vault_manager - .vaults - .extend(vaults.items.into_iter().map(Vault::from)); - vault_manager.try_export()?; - vaults_len - } - }; - println!( - "{imported_passwords_len} vault{s} were imported successfully", - s = if imported_passwords_len >= 2 { "s" } else { "" } - ); - Ok(()) } else { Err(LprsError::Io(IoError::new( diff --git a/src/cli/list_command.rs b/src/cli/list_command.rs index 16cfc63..d0f0cc1 100644 --- a/src/cli/list_command.rs +++ b/src/cli/list_command.rs @@ -20,7 +20,7 @@ use clap::Args; use crate::{ vault::{vault_state::*, Vaults}, - LprsError, LprsResult, RunCommand, + LprsCommand, LprsError, LprsResult, }; #[derive(Debug, Args)] @@ -49,7 +49,7 @@ pub struct List { regex: bool, } -impl RunCommand for List { +impl LprsCommand for List { fn run(self, vault_manager: Vaults) -> LprsResult<()> { if vault_manager.vaults.is_empty() { return Err(LprsError::Other( diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 65298e2..a5c2424 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -18,7 +18,7 @@ use std::path::PathBuf; use clap::Parser; -use crate::{utils, vault::Vaults, LprsResult, RunCommand}; +use crate::{utils, vault::Vaults, LprsCommand, LprsResult}; pub mod add_command; pub mod clean_command; @@ -63,6 +63,8 @@ impl Cli { }; log::debug!("Getting the vaults file: {}", vaults_file.to_string_lossy()); + self.command.validate_args()?; + let vault_manager = if matches!(self.command, Commands::Clean(..) | Commands::Gen(..)) { // Returns empty vault manager for those commands don't need it Vaults { diff --git a/src/cli/remove_command.rs b/src/cli/remove_command.rs index 36e8ebb..af0ef12 100644 --- a/src/cli/remove_command.rs +++ b/src/cli/remove_command.rs @@ -20,7 +20,7 @@ use clap::Args; use crate::{ vault::{vault_state::*, Vaults}, - LprsError, LprsResult, RunCommand, + LprsCommand, LprsError, LprsResult, }; #[derive(Debug, Args)] @@ -34,7 +34,7 @@ pub struct Remove { force: bool, } -impl RunCommand for Remove { +impl LprsCommand for Remove { fn run(self, mut vault_manager: Vaults) -> LprsResult<()> { let index = (self.index.get() - 1) as usize; if index > vault_manager.vaults.len() { diff --git a/src/macros.rs b/src/macros.rs index a753c97..69f2777 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -/// Creates commands macro, to create the `Commands` enum and impl `RunCommand` to it. +/// Creates commands macro, to create the `Commands` enum and impl `LprsCommand` to it. /// /// ### Notes: -/// The `$command` must impl `RunCommand` trait +/// The `$command` must impl `LprsCommand` trait /// /// ### Example: /// ```rust @@ -37,7 +37,7 @@ /// Some(SomeArgs), /// } /// -/// impl crate::RunCommand for TestCommands { +/// impl crate::LprsCommand for TestCommands { /// fn run( /// &self, /// vault_manager: crate::vault::Vaults, @@ -62,7 +62,7 @@ macro_rules! create_commands { } #[automatically_derived] - impl $crate::RunCommand for $enum_name{ + impl $crate::LprsCommand for $enum_name{ fn run(self, vault_manager: $crate::vault::Vaults<$crate::vault::vault_state::Plain>) -> $crate::LprsResult<()> { match self { $( @@ -70,6 +70,14 @@ macro_rules! create_commands { )+ } } + + fn validate_args(&self) -> LprsResult<()> { + match self { + $( + Self::$varint(command) => command.validate_args(), + )+ + } + } } }; } diff --git a/src/traits.rs b/src/traits.rs index cb0882c..708829a 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -19,7 +19,13 @@ use crate::{ LprsResult, }; -/// Trait to run the command -pub trait RunCommand { +/// Trait to work with the commands +pub trait LprsCommand { + /// Run the command, should do all the logic, even the export fn run(self, vault_manager: Vaults) -> LprsResult<()>; + + /// Validate the gaiven args from the user. + fn validate_args(&self) -> LprsResult<()> { + Ok(()) + } }