feat: Validate args before ask for the master password (#17)
All checks were successful
Write changelog / write-changelog (push) Successful in 3s
Rust CI / Rust CI (push) Successful in 1m17s

Reviewed-on: #17
Fixes: #13
Co-authored-by: Awiteb <a@4rs.nl>
Co-committed-by: Awiteb <a@4rs.nl>
This commit is contained in:
Awiteb 2024-04-29 13:39:36 +02:00 committed by awiteb
parent 4af6639e21
commit b4bcaa92ca
11 changed files with 124 additions and 96 deletions

View file

@ -18,7 +18,7 @@ use clap::Args;
use crate::{ use crate::{
vault::{vault_state::*, Vault, Vaults}, vault::{vault_state::*, Vault, Vaults},
LprsError, LprsResult, RunCommand, LprsCommand, LprsError, LprsResult,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
@ -31,16 +31,8 @@ pub struct Add {
password: Option<Option<String>>, password: Option<Option<String>>,
} }
impl RunCommand for Add { impl LprsCommand for Add {
fn run(mut self, mut vault_manager: Vaults<Plain>) -> LprsResult<()> { fn run(mut self, mut vault_manager: Vaults<Plain>) -> 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 { match self.password {
Some(Some(password)) => self.vault_info.password = Some(password), Some(Some(password)) => self.vault_info.password = Some(password),
Some(None) => { Some(None) => {
@ -58,4 +50,15 @@ impl RunCommand for Add {
vault_manager.add_vault(self.vault_info); vault_manager.add_vault(self.vault_info);
vault_manager.try_export() 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(())
}
} }

View file

@ -20,14 +20,14 @@ use clap::Args;
use crate::{ use crate::{
vault::{vault_state::*, Vaults}, vault::{vault_state::*, Vaults},
LprsError, LprsResult, RunCommand, LprsCommand, LprsError, LprsResult,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
pub struct Clean {} pub struct Clean {}
impl RunCommand for Clean { impl LprsCommand for Clean {
fn run(self, vault_manager: Vaults<Plain>) -> LprsResult<()> { fn run(self, vault_manager: Vaults<Plain>) -> LprsResult<()> {
fs::write(vault_manager.vaults_file, "[]").map_err(LprsError::Io) fs::write(vault_manager.vaults_file, "[]").map_err(LprsError::Io)
} }

View file

@ -20,7 +20,7 @@ use clap::Args;
use crate::{ use crate::{
vault::{vault_state::*, Vault, Vaults}, vault::{vault_state::*, Vault, Vaults},
LprsError, LprsResult, RunCommand, LprsCommand, LprsError, LprsResult,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
@ -46,7 +46,7 @@ pub struct Edit {
note: Option<String>, note: Option<String>,
} }
impl RunCommand for Edit { impl LprsCommand for Edit {
fn run(self, mut vault_manager: Vaults<Plain>) -> LprsResult<()> { fn run(self, mut vault_manager: Vaults<Plain>) -> LprsResult<()> {
let index = self.index.get() as usize; 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 // Get the password from stdin or from its value if provided
let password = match self.password { let password = match self.password {
Some(Some(password)) => Some(password), Some(Some(password)) => Some(password),
@ -91,4 +80,18 @@ impl RunCommand for Edit {
); );
vault_manager.try_export() 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(())
}
} }

View file

@ -20,7 +20,7 @@ use clap::Args;
use crate::{ use crate::{
vault::{vault_state::*, BitWardenPasswords, Format, Vault, Vaults}, vault::{vault_state::*, BitWardenPasswords, Format, Vault, Vaults},
LprsError, LprsResult, RunCommand, LprsCommand, LprsError, LprsResult,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
@ -33,24 +33,26 @@ pub struct Export {
format: Format, format: Format,
} }
impl RunCommand for Export { impl LprsCommand for Export {
fn run(self, vault_manager: Vaults<Plain>) -> LprsResult<()> { fn run(self, vault_manager: Vaults<Plain>) -> LprsResult<()> {
let exported_data = match self.format {
Format::Lprs => {
serde_json::to_string::<Vec<Vault<Encrypted>>>(&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 if self
.path .path
.extension() .extension()
.is_some_and(|e| e.to_string_lossy().eq_ignore_ascii_case("json")) .is_some_and(|e| e.to_string_lossy().eq_ignore_ascii_case("json"))
{ {
if !self.path.exists() { if !self.path.exists() {
let exported_data = match self.format { Ok(())
Format::Lprs => serde_json::to_string::<Vec<Vault<Encrypted>>>(
&vault_manager.encrypt_vaults()?,
),
Format::BitWarden => {
serde_json::to_string(&BitWardenPasswords::from(vault_manager))
}
}?;
fs::write(&self.path, exported_data).map_err(LprsError::from)
} else { } else {
Err(LprsError::Io(IoError::new( Err(LprsError::Io(IoError::new(
IoErrorKind::AlreadyExists, IoErrorKind::AlreadyExists,

View file

@ -20,7 +20,7 @@ use clap::Args;
use crate::{ use crate::{
vault::{vault_state::*, Vaults}, vault::{vault_state::*, Vaults},
LprsError, LprsResult, RunCommand, LprsCommand, LprsError, LprsResult,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
@ -44,26 +44,29 @@ pub struct Gen {
symbols: bool, symbols: bool,
} }
impl RunCommand for Gen { impl LprsCommand for Gen {
fn run(self, _vault_manager: Vaults<Plain>) -> LprsResult<()> { fn run(self, _vault_manager: Vaults<Plain>) -> LprsResult<()> {
if self.uppercase || self.lowercase || self.numbers || self.symbols { println!(
println!( "{}",
"{}", passwords::PasswordGenerator::new()
passwords::PasswordGenerator::new() .length(self.length.get() as usize)
.length(self.length.get() as usize) .uppercase_letters(self.uppercase)
.uppercase_letters(self.uppercase) .lowercase_letters(self.lowercase)
.lowercase_letters(self.lowercase) .numbers(self.numbers)
.numbers(self.numbers) .symbols(self.symbols)
.symbols(self.symbols) .strict(true)
.strict(true) .generate_one()
.generate_one() .expect("The length cannot be zero")
.expect("The length cannot be zero") );
); Ok(())
Ok(()) }
} else {
Err(LprsError::Other( 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(), "You need to enable at least one kind of characters".to_owned(),
)) ));
} }
Ok(())
} }
} }

View file

@ -20,7 +20,7 @@ use clap::Args;
use crate::{ use crate::{
vault::{vault_state::*, BitWardenPasswords, Format, Vault, Vaults}, vault::{vault_state::*, BitWardenPasswords, Format, Vault, Vaults},
LprsError, LprsResult, RunCommand, LprsCommand, LprsError, LprsResult,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
@ -34,41 +34,42 @@ pub struct Import {
format: Format, format: Format,
} }
impl RunCommand for Import { impl LprsCommand for Import {
fn run(self, mut vault_manager: Vaults<Plain>) -> LprsResult<()> { fn run(self, mut vault_manager: Vaults<Plain>) -> 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.exists() {
if self if self
.path .path
.extension() .extension()
.is_some_and(|e| e.to_string_lossy().eq_ignore_ascii_case("json")) .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(()) Ok(())
} else { } else {
Err(LprsError::Io(IoError::new( Err(LprsError::Io(IoError::new(

View file

@ -20,7 +20,7 @@ use clap::Args;
use crate::{ use crate::{
vault::{vault_state::*, Vaults}, vault::{vault_state::*, Vaults},
LprsError, LprsResult, RunCommand, LprsCommand, LprsError, LprsResult,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
@ -49,7 +49,7 @@ pub struct List {
regex: bool, regex: bool,
} }
impl RunCommand for List { impl LprsCommand for List {
fn run(self, vault_manager: Vaults<Plain>) -> LprsResult<()> { fn run(self, vault_manager: Vaults<Plain>) -> LprsResult<()> {
if vault_manager.vaults.is_empty() { if vault_manager.vaults.is_empty() {
return Err(LprsError::Other( return Err(LprsError::Other(

View file

@ -18,7 +18,7 @@ use std::path::PathBuf;
use clap::Parser; use clap::Parser;
use crate::{utils, vault::Vaults, LprsResult, RunCommand}; use crate::{utils, vault::Vaults, LprsCommand, LprsResult};
pub mod add_command; pub mod add_command;
pub mod clean_command; pub mod clean_command;
@ -63,6 +63,8 @@ impl Cli {
}; };
log::debug!("Getting the vaults file: {}", vaults_file.to_string_lossy()); 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(..)) { let vault_manager = if matches!(self.command, Commands::Clean(..) | Commands::Gen(..)) {
// Returns empty vault manager for those commands don't need it // Returns empty vault manager for those commands don't need it
Vaults { Vaults {

View file

@ -20,7 +20,7 @@ use clap::Args;
use crate::{ use crate::{
vault::{vault_state::*, Vaults}, vault::{vault_state::*, Vaults},
LprsError, LprsResult, RunCommand, LprsCommand, LprsError, LprsResult,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
@ -34,7 +34,7 @@ pub struct Remove {
force: bool, force: bool,
} }
impl RunCommand for Remove { impl LprsCommand for Remove {
fn run(self, mut vault_manager: Vaults<Plain>) -> LprsResult<()> { fn run(self, mut vault_manager: Vaults<Plain>) -> LprsResult<()> {
let index = (self.index.get() - 1) as usize; let index = (self.index.get() - 1) as usize;
if index > vault_manager.vaults.len() { if index > vault_manager.vaults.len() {

View file

@ -14,10 +14,10 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>. // along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
/// 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: /// ### Notes:
/// The `$command` must impl `RunCommand` trait /// The `$command` must impl `LprsCommand` trait
/// ///
/// ### Example: /// ### Example:
/// ```rust /// ```rust
@ -37,7 +37,7 @@
/// Some(SomeArgs), /// Some(SomeArgs),
/// } /// }
/// ///
/// impl crate::RunCommand for TestCommands { /// impl crate::LprsCommand for TestCommands {
/// fn run( /// fn run(
/// &self, /// &self,
/// vault_manager: crate::vault::Vaults, /// vault_manager: crate::vault::Vaults,
@ -62,7 +62,7 @@ macro_rules! create_commands {
} }
#[automatically_derived] #[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<()> { fn run(self, vault_manager: $crate::vault::Vaults<$crate::vault::vault_state::Plain>) -> $crate::LprsResult<()> {
match self { match self {
$( $(
@ -70,6 +70,14 @@ macro_rules! create_commands {
)+ )+
} }
} }
fn validate_args(&self) -> LprsResult<()> {
match self {
$(
Self::$varint(command) => command.validate_args(),
)+
}
}
} }
}; };
} }

View file

@ -19,7 +19,13 @@ use crate::{
LprsResult, LprsResult,
}; };
/// Trait to run the command /// Trait to work with the commands
pub trait RunCommand { pub trait LprsCommand {
/// Run the command, should do all the logic, even the export
fn run(self, vault_manager: Vaults<Plain>) -> LprsResult<()>; fn run(self, vault_manager: Vaults<Plain>) -> LprsResult<()>;
/// Validate the gaiven args from the user.
fn validate_args(&self) -> LprsResult<()> {
Ok(())
}
} }