Add export command

This commit is contained in:
TheAwiteb 2023-12-29 08:18:57 +03:00
parent 7b58e9e9a6
commit 5df67e772c
No known key found for this signature in database
GPG key ID: ABF818BD15DC2D34
4 changed files with 151 additions and 14 deletions

63
src/cli/export_command.rs Normal file
View file

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

View file

@ -26,6 +26,7 @@ use crate::{
pub mod add_command; pub mod add_command;
pub mod clean_command; pub mod clean_command;
pub mod edit_command; pub mod edit_command;
pub mod export_command;
pub mod gen_command; pub mod gen_command;
pub mod list_command; pub mod list_command;
pub mod remove_command; pub mod remove_command;
@ -38,6 +39,7 @@ crate::create_commands!(
"Clean the password file", Clean => clean_command::Clean "Clean the password file", Clean => clean_command::Clean
"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
// TODO: Export command // TODO: Export command
// TODO: Import command // TODO: Import command
); );

71
src/password/bitwarden.rs Normal file
View file

@ -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<Vec<BitWardenUri>>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct BitWardenUri {
#[serde(rename = "match")]
pub mt: Option<i32>,
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<String>,
}
/// The bitwarden password struct
#[derive(Default, Deserialize, Serialize)]
pub struct BitWardenPasswords {
pub encrypted: bool,
pub folders: Vec<BitWardenFolder>,
pub items: Vec<BitWardenPassword>,
}
impl From<Password> 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<Passwords> for BitWardenPasswords {
fn from(value: Passwords) -> Self {
Self {
encrypted: false,
folders: Vec::new(),
items: value
.passwords
.into_iter()
.map(BitWardenPassword::from)
.collect(),
}
}
}

View file

@ -22,23 +22,13 @@ use serde::{Deserialize, Serialize};
use crate::{LprsError, LprsResult}; use crate::{LprsError, LprsResult};
pub mod cipher; pub mod cipher;
mod bitwarden;
mod validator; mod validator;
pub use bitwarden::*;
pub use validator::*; pub use validator::*;
/// The passwords manager
#[derive(Default, Deserialize, Serialize)]
pub struct Passwords {
/// Hash of the master password
#[serde(skip)]
pub master_password: Vec<u8>,
/// The json passwords file
#[serde(skip)]
pub passwords_file: PathBuf,
/// The passwords
pub passwords: Vec<Password>,
}
/// 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)]
@ -60,6 +50,17 @@ pub struct Password {
pub note: Option<String>, pub note: Option<String>,
} }
/// The passwords manager
#[derive(Default)]
pub struct Passwords {
/// Hash of the master password
pub master_password: Vec<u8>,
/// The json passwords file
pub passwords_file: PathBuf,
/// The passwords
pub passwords: Vec<Password>,
}
impl Password { impl Password {
/// Encrypt the password data /// Encrypt the password data
pub fn encrypt(self, master_password: &[u8]) -> LprsResult<Self> { pub fn encrypt(self, master_password: &[u8]) -> LprsResult<Self> {
@ -111,7 +112,7 @@ impl Passwords {
} }
/// Encrypt the passwords /// Encrypt the passwords
fn encrypt(self) -> LprsResult<Self> { pub fn encrypt(self) -> LprsResult<Self> {
Ok(Self { Ok(Self {
passwords: self passwords: self
.passwords .passwords