Merge pull request #8 from TheAwiteb/import-command

`import` command
This commit is contained in:
Mohammed Alotaibi 2023-12-29 11:41:37 +03:00 committed by GitHub
commit e8d9941fee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 153 additions and 31 deletions

View file

@ -38,6 +38,7 @@ Commands:
edit Edit the password content edit Edit the password content
gen Generate password gen Generate password
export Export the passwords export Export the passwords
import Import passwords
help Print this message or the help of the given subcommand(s) help Print this message or the help of the given subcommand(s)
Options: Options:

View file

@ -14,50 +14,53 @@
// 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>.
use std::{fs, path::PathBuf}; use std::{fs, io::Error as IoError, io::ErrorKind as IoErrorKind, path::PathBuf};
use clap::{Args, ValueEnum}; use clap::Args;
use crate::{ use crate::{
password::{BitWardenPasswords, Passwords}, password::{BitWardenPasswords, Format, Passwords},
LprsError, LprsResult, RunCommand, LprsError, LprsResult, RunCommand,
}; };
#[derive(Clone, Debug, ValueEnum)]
pub enum ExportFormat {
Lprs,
BitWarden,
}
#[derive(Debug, Args)] #[derive(Debug, Args)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
pub struct Export { pub struct Export {
/// The path to export to /// The path to export to
path: PathBuf, path: PathBuf,
/// Format to export passwords in /// Format to export passwords in
#[arg(short, long, value_name = "FORMAT", default_value_t= ExportFormat::Lprs)] #[arg(short, long, value_name = "FORMAT", default_value_t= Format::Lprs)]
format: ExportFormat, format: Format,
}
impl ToString for ExportFormat {
fn to_string(&self) -> String {
self.to_possible_value()
.expect("There is no skiped values")
.get_name()
.to_owned()
}
} }
impl RunCommand for Export { impl RunCommand for Export {
fn run(&self, password_manager: Passwords) -> LprsResult<()> { fn run(&self, password_manager: Passwords) -> LprsResult<()> {
let exported_data = match self.format { if self
ExportFormat::Lprs => serde_json::to_string(&password_manager.encrypt()?.passwords), .path
ExportFormat::BitWarden => { .extension()
serde_json::to_string(&BitWardenPasswords::from(password_manager)) .is_some_and(|e| e.to_string_lossy().eq_ignore_ascii_case("json"))
} {
} if !self.path.exists() {
.map_err(LprsError::from)?; let exported_data = match self.format {
Format::Lprs => serde_json::to_string(&password_manager.encrypt()?.passwords),
Format::BitWarden => {
serde_json::to_string(&BitWardenPasswords::from(password_manager))
}
}
.map_err(LprsError::from)?;
fs::write(&self.path, exported_data).map_err(LprsError::from) fs::write(&self.path, exported_data).map_err(LprsError::from)
} else {
Err(LprsError::Io(IoError::new(
IoErrorKind::AlreadyExists,
format!("file `{}` is already exists", self.path.display()),
)))
}
} else {
Err(LprsError::Io(IoError::new(
IoErrorKind::InvalidInput,
format!("file `{}` is not a json file", self.path.display()),
)))
}
} }
} }

88
src/cli/import_command.rs Normal file
View file

@ -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 <https://www.gnu.org/licenses/gpl-3.0.html>.
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()),
)))
}
}
}

View file

@ -28,6 +28,7 @@ pub mod clean_command;
pub mod edit_command; pub mod edit_command;
pub mod export_command; pub mod export_command;
pub mod gen_command; pub mod gen_command;
pub mod import_command;
pub mod list_command; pub mod list_command;
pub mod remove_command; pub mod remove_command;
@ -40,8 +41,7 @@ crate::create_commands!(
"Edit the password content", Edit => edit_command::Edit "Edit the password content", Edit => edit_command::Edit
"Generate password", Gen => gen_command::Gen "Generate password", Gen => gen_command::Gen
"Export the passwords", Export => export_command::Export "Export the passwords", Export => export_command::Export
// TODO: Export command "Import passwords", Import => import_command::Import
// TODO: Import command
); );
#[derive(Parser, Debug)] #[derive(Parser, Debug)]

View file

@ -39,6 +39,21 @@ pub struct BitWardenPasswords {
pub items: Vec<BitWardenPassword>, pub items: Vec<BitWardenPassword>,
} }
impl From<BitWardenPassword> 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<Password> for BitWardenPassword { impl From<Password> for BitWardenPassword {
fn from(value: Password) -> Self { fn from(value: Password) -> Self {
Self { Self {

View file

@ -16,7 +16,7 @@
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
use clap::Parser; use clap::{Parser, ValueEnum};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{LprsError, LprsResult}; use crate::{LprsError, LprsResult};
@ -29,6 +29,12 @@ mod validator;
pub use bitwarden::*; pub use bitwarden::*;
pub use validator::*; pub use validator::*;
#[derive(Clone, Debug, ValueEnum)]
pub enum Format {
Lprs,
BitWarden,
}
/// The password struct /// The password struct
#[serde_with_macros::skip_serializing_none] #[serde_with_macros::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize, Parser)] #[derive(Clone, Debug, Deserialize, Serialize, Parser)]
@ -145,3 +151,12 @@ impl Passwords {
self.passwords.push(password) self.passwords.push(password)
} }
} }
impl ToString for Format {
fn to_string(&self) -> String {
self.to_possible_value()
.expect("There is no skiped values")
.get_name()
.to_owned()
}
}