From 5df67e772caec28122a4e23371579f48eedac9ab Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Fri, 29 Dec 2023 08:18:57 +0300 Subject: [PATCH] Add `export` command --- src/cli/export_command.rs | 63 ++++++++++++++++++++++++++++++++++ src/cli/mod.rs | 2 ++ src/password/bitwarden.rs | 71 +++++++++++++++++++++++++++++++++++++++ src/password/mod.rs | 29 ++++++++-------- 4 files changed, 151 insertions(+), 14 deletions(-) create mode 100644 src/cli/export_command.rs create mode 100644 src/password/bitwarden.rs diff --git a/src/cli/export_command.rs b/src/cli/export_command.rs new file mode 100644 index 0000000..d332b91 --- /dev/null +++ b/src/cli/export_command.rs @@ -0,0 +1,63 @@ +// 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, path::PathBuf}; + +use clap::{Args, ValueEnum}; + +use crate::{ + password::{BitWardenPasswords, Passwords}, + LprsError, LprsResult, RunCommand, +}; + +#[derive(Clone, Debug, ValueEnum)] +pub enum ExportFormat { + Lprs, + BitWarden, +} + +#[derive(Debug, Args)] +#[command(author, version, about, long_about = None)] +pub struct Export { + /// The path to export to + path: PathBuf, + /// Format to export passwords in + #[arg(short, long, value_name = "FORMAT", default_value_t= ExportFormat::Lprs)] + format: ExportFormat, +} + +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 { + fn run(&self, password_manager: Passwords) -> LprsResult<()> { + let exported_data = match self.format { + ExportFormat::Lprs => serde_json::to_string(&password_manager.encrypt()?.passwords), + ExportFormat::BitWarden => { + serde_json::to_string(&BitWardenPasswords::from(password_manager)) + } + } + .map_err(LprsError::from)?; + + fs::write(&self.path, exported_data).map_err(LprsError::from) + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index d9aa12e..f36890f 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -26,6 +26,7 @@ use crate::{ pub mod add_command; pub mod clean_command; pub mod edit_command; +pub mod export_command; pub mod gen_command; pub mod list_command; pub mod remove_command; @@ -38,6 +39,7 @@ crate::create_commands!( "Clean the password file", Clean => clean_command::Clean "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 ); diff --git a/src/password/bitwarden.rs b/src/password/bitwarden.rs new file mode 100644 index 0000000..5a89ce4 --- /dev/null +++ b/src/password/bitwarden.rs @@ -0,0 +1,71 @@ +use serde::{Deserialize, Serialize}; + +use super::{Password, Passwords}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct BitWardenLoginData { + pub username: String, + pub password: String, + pub uris: Option>, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct BitWardenUri { + #[serde(rename = "match")] + pub mt: Option, + pub uri: String, +} + +#[derive(Default, Deserialize, Serialize)] +pub struct BitWardenFolder { + pub id: String, + pub name: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct BitWardenPassword { + #[serde(rename = "type")] + pub ty: i32, + pub name: String, + pub login: BitWardenLoginData, + pub notes: Option, +} + +/// The bitwarden password struct +#[derive(Default, Deserialize, Serialize)] +pub struct BitWardenPasswords { + pub encrypted: bool, + pub folders: Vec, + pub items: Vec, +} + +impl From for BitWardenPassword { + fn from(value: Password) -> Self { + Self { + ty: 1, + name: value.name, + login: BitWardenLoginData { + username: value.username, + password: value.password, + uris: value + .service + .map(|s| vec![BitWardenUri { mt: None, uri: s }]), + }, + notes: value.note, + } + } +} + +impl From for BitWardenPasswords { + fn from(value: Passwords) -> Self { + Self { + encrypted: false, + folders: Vec::new(), + items: value + .passwords + .into_iter() + .map(BitWardenPassword::from) + .collect(), + } + } +} diff --git a/src/password/mod.rs b/src/password/mod.rs index f50873b..fbbaeb3 100644 --- a/src/password/mod.rs +++ b/src/password/mod.rs @@ -22,23 +22,13 @@ use serde::{Deserialize, Serialize}; use crate::{LprsError, LprsResult}; pub mod cipher; + +mod bitwarden; mod validator; +pub use bitwarden::*; pub use validator::*; -/// The passwords manager -#[derive(Default, Deserialize, Serialize)] -pub struct Passwords { - /// Hash of the master password - #[serde(skip)] - pub master_password: Vec, - /// The json passwords file - #[serde(skip)] - pub passwords_file: PathBuf, - /// The passwords - pub passwords: Vec, -} - /// The password struct #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Deserialize, Serialize, Parser)] @@ -60,6 +50,17 @@ pub struct Password { pub note: Option, } +/// The passwords manager +#[derive(Default)] +pub struct Passwords { + /// Hash of the master password + pub master_password: Vec, + /// The json passwords file + pub passwords_file: PathBuf, + /// The passwords + pub passwords: Vec, +} + impl Password { /// Encrypt the password data pub fn encrypt(self, master_password: &[u8]) -> LprsResult { @@ -111,7 +112,7 @@ impl Passwords { } /// Encrypt the passwords - fn encrypt(self) -> LprsResult { + pub fn encrypt(self) -> LprsResult { Ok(Self { passwords: self .passwords