From f888e8352540ecdee394e6b8a706a46b78224d79 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Fri, 29 Dec 2023 11:37:53 +0300 Subject: [PATCH] Add `import` command --- src/cli/import_command.rs | 88 +++++++++++++++++++++++++++++++++++++++ src/cli/mod.rs | 4 +- src/password/bitwarden.rs | 15 +++++++ 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 src/cli/import_command.rs diff --git a/src/cli/import_command.rs b/src/cli/import_command.rs new file mode 100644 index 0000000..1315945 --- /dev/null +++ b/src/cli/import_command.rs @@ -0,0 +1,88 @@ +// Lprs - A local CLI password manager +// Copyright (C) 2024 Awiteb +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::{fs::File, io::Error as IoError, io::ErrorKind as IoErrorKind, path::PathBuf}; + +use clap::Args; + +use crate::{ + password::{BitWardenPasswords, Format, Password, Passwords}, + LprsError, LprsResult, RunCommand, +}; + +#[derive(Debug, Args)] +#[command(author, version, about, long_about = None)] +pub struct Import { + /// The file path to import from + path: PathBuf, + + /// The format to import from + #[arg(short, long, default_value_t = Format::Lprs)] + format: Format, +} + +impl RunCommand for Import { + fn run(&self, mut password_manager: Passwords) -> 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 passwords = Passwords::try_reload( + self.path.to_path_buf(), + password_manager.master_password.to_vec(), + )?; + let passwords_len = passwords.passwords.len(); + + password_manager.passwords.extend(passwords.passwords); + password_manager.try_export()?; + passwords_len + } + Format::BitWarden => { + let passwords: BitWardenPasswords = + serde_json::from_reader(File::open(&self.path)?)?; + let passwords_len = passwords.items.len(); + + password_manager + .passwords + .extend(passwords.items.into_iter().map(Password::from)); + password_manager.try_export()?; + passwords_len + } + }; + println!( + "{imported_passwords_len} password{s} were imported successfully", + s = if imported_passwords_len >= 2 { "s" } else { "" } + ); + + Ok(()) + } else { + Err(LprsError::Io(IoError::new( + IoErrorKind::InvalidInput, + format!("file `{}` is not a json file", self.path.display()), + ))) + } + } else { + Err(LprsError::Io(IoError::new( + IoErrorKind::NotFound, + format!("file `{}` not found", self.path.display()), + ))) + } + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index cff9531..8036cf6 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -28,6 +28,7 @@ pub mod clean_command; pub mod edit_command; pub mod export_command; pub mod gen_command; +pub mod import_command; pub mod list_command; pub mod remove_command; @@ -40,8 +41,7 @@ crate::create_commands!( "Edit the password content", Edit => edit_command::Edit "Generate password", Gen => gen_command::Gen "Export the passwords", Export => export_command::Export - // TODO: Export command - // TODO: Import command + "Import passwords", Import => import_command::Import ); #[derive(Parser, Debug)] diff --git a/src/password/bitwarden.rs b/src/password/bitwarden.rs index 5a89ce4..7b46ea4 100644 --- a/src/password/bitwarden.rs +++ b/src/password/bitwarden.rs @@ -39,6 +39,21 @@ pub struct BitWardenPasswords { pub items: Vec, } +impl From for Password { + fn from(value: BitWardenPassword) -> Self { + Self { + name: value.name, + username: value.login.username, + password: value.login.password, + service: value + .login + .uris + .and_then(|p| p.first().map(|u| u.uri.clone())), + note: value.notes, + } + } +} + impl From for BitWardenPassword { fn from(value: Password) -> Self { Self {