From ee93433562f73b92ff629b01d3e904de0a42b020 Mon Sep 17 00:00:00 2001 From: DuckySmacky Date: Sat, 11 Jan 2025 03:44:44 +0300 Subject: [PATCH] Switch to object-driven approach for profiles --- Cargo.lock | 9 +- src/core/data/auth.rs | 7 +- src/core/data/io.rs | 4 +- src/core/data/keys.rs | 34 ++---- src/core/data/mod.rs | 4 +- src/core/data/profile.rs | 229 ++++++++++++++++++++++++++++++++++++ src/core/data/profiles.rs | 232 ------------------------------------- src/core/encryption/mod.rs | 16 +-- src/core/error.rs | 9 ++ src/lib.rs | 67 ++++++----- tests/cli_tests.rs | 1 + tests/common/mod.rs | 24 ++-- 12 files changed, 323 insertions(+), 313 deletions(-) create mode 100644 src/core/data/profile.rs delete mode 100644 src/core/data/profiles.rs diff --git a/Cargo.lock b/Cargo.lock index 3bea9b2..c4da867 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,9 +278,9 @@ checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "lazy_static" @@ -427,11 +427,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.118" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] diff --git a/src/core/data/auth.rs b/src/core/data/auth.rs index 7829132..fbeef6c 100644 --- a/src/core/data/auth.rs +++ b/src/core/data/auth.rs @@ -3,7 +3,6 @@ use argon2::{self, Config}; use rand::random; use crate::{Result, Error}; -use crate::core::data::profiles::Profile; /// Hashes the given password. Returns hashed password and salt pub fn hash_password(password: &str) -> Result<(String, [u8; 16])> { @@ -14,8 +13,8 @@ pub fn hash_password(password: &str) -> Result<(String, [u8; 16])> { Ok((hashed_password, salt)) } -/// Verifies password for the given profile -pub fn verify_password(input_password: &str, profile: Profile) -> bool { - argon2::verify_encoded(&profile.password_hash, input_password.as_bytes()) +/// Verifies password by comparing it to the password hash +pub fn verify_password(password_hash: &str, password: &str) -> bool { + argon2::verify_encoded(password_hash, password.as_bytes()) .unwrap_or(false) } \ No newline at end of file diff --git a/src/core/data/io.rs b/src/core/data/io.rs index d2a7e04..8777d15 100644 --- a/src/core/data/io.rs +++ b/src/core/data/io.rs @@ -49,7 +49,7 @@ pub fn write_bytes(path: &Path, bytes: &[u8], truncate: bool) -> Result<()> { /// Writes string to the specified file. Creates a new one if already doesn't exist pub fn write_file(path: &Path, contents: &str, truncate: bool) -> Result<()> { - log_debug!("Writing file to \"{}\"", path.display()); + log_debug!("Writing to \"{}\"", path.display()); let mut file = File::options() .write(true) .create(true) @@ -59,6 +59,6 @@ pub fn write_file(path: &Path, contents: &str, truncate: bool) -> Result<()> { file.write_all(contents.as_bytes())?; file.flush()?; - log_debug!("Wrote {} to file", contents.len()); + log_debug!("Wrote {} to file", contents); Ok(()) } \ No newline at end of file diff --git a/src/core/data/keys.rs b/src/core/data/keys.rs index 7765a9c..dd22cc2 100644 --- a/src/core/data/keys.rs +++ b/src/core/data/keys.rs @@ -1,36 +1,22 @@ -//! Contains functions related to key manipulation +//! Contains wrapper functions above profiles to get and set current profile's key -use crate::{Error, Result, Key}; +use super::profile; use crate::log_debug; -use crate::core::encryption::cipher; -use super::profiles; +use crate::{Key, Result}; /// Gets the key for the current profile pub fn get_key() -> Result { log_debug!("Getting encryption key from current profile"); - - let profile_data = profiles::get_current_profile()?; - Ok(profile_data.key) + let mut profiles = profile::get_profiles(); + let profile = profiles.get_current_profile()?; + Ok(profile.key) } /// Sets the key for the current profile pub fn set_key(new_key: Key) -> Result<()> { log_debug!("Setting a new encryption key for current profile"); - - let mut profile = profiles::get_current_profile()?; - profile.key = new_key; - - profiles::save_profile(profile) - .map_err(|err| Error::IOError(format!("Unable to save profile data: {}", err))) -} - -/// Generates new key and overwrites the old for the current profile -pub fn generate_new_key() -> Result<()> { - log_debug!("Generating new encryption key for current profile"); - - let mut profile = profiles::get_current_profile()?; - profile.key = cipher::generate_key(); - - profiles::save_profile(profile) - .map_err(|err| Error::IOError(format!("Unable to save profile data: {}", err))) + let mut profiles = profile::get_profiles(); + let profile = profiles.get_current_profile()?; + profile.set_key(new_key); + profiles.save() } \ No newline at end of file diff --git a/src/core/data/mod.rs b/src/core/data/mod.rs index 714b54c..e53ff6f 100644 --- a/src/core/data/mod.rs +++ b/src/core/data/mod.rs @@ -2,8 +2,8 @@ //! interactions pub mod keys; -pub mod auth; -pub mod profiles; +mod auth; +pub mod profile; pub mod config; pub mod os; pub mod io; \ No newline at end of file diff --git a/src/core/data/profile.rs b/src/core/data/profile.rs new file mode 100644 index 0000000..5f25f50 --- /dev/null +++ b/src/core/data/profile.rs @@ -0,0 +1,229 @@ +//! Contains function for user profile manipulation + +use super::auth; +use super::io::{read_file, write_file}; +use crate::core::data::os; +use crate::core::encryption::cipher; +use crate::{log_debug, log_error, log_info, Error, Key, Result}; +use serde::{Deserialize, Serialize}; +use std::io::{self}; +use std::path::PathBuf; + +/// Name of the file which stores all the profile data +const PROFILES_FILE_NAME: &str = "profiles.json"; + +/// Struct holding all the needed profile information for the program. Saved on the disk as a JSON +/// file +#[derive(Serialize, Deserialize, Debug)] +pub struct LockboxProfiles { + current_profile: Option, + profiles: Vec, + #[serde(skip)] + file_path: PathBuf +} + +/// Object-driven approach +impl LockboxProfiles { + /// Imports self from the stored "profiles.json" file. In case of the file missing, generates a + /// new object with default empty values + pub fn import() -> Result { + log_debug!("Importing Lockbox profiles"); + let data_directory = os::get_data_dir()?; + let profiles_file = data_directory.join(PROFILES_FILE_NAME); + + let profiles = match read_file(&profiles_file) { + Ok(file_data) => { + let mut profiles: LockboxProfiles = serde_json::from_str(&file_data)?; + profiles.file_path = profiles_file; + profiles + }, + Err(err) => { + if err.kind() == io::ErrorKind::NotFound { + log_info!("\"profiles.json\" file doesn't exist. Generating new profiles data"); + Self::new(profiles_file) + } else { + return Err(err.into()); + } + } + }; + + Ok(profiles) + } + + /// Bare-minimum constructor to use in case of file not being available to import from + fn new( + file_path: PathBuf + ) -> Self { + LockboxProfiles { + current_profile: None, + profiles: vec![], + file_path + } + } + + /// Returns currently selected profile data + pub fn get_current_profile(&mut self) -> Result<&mut Profile> { + log_debug!("Getting current profile"); + let current_profile = self.current_profile.clone(); + + let profile = match current_profile { + None => return Err(Error::ProfileError("No profile is currently selected".to_string())), + Some(profile_name) => { + self.find_profile(&profile_name)? + } + }; + + Ok(profile) + } + + /// Returns a list of currently available profiles + pub fn get_profiles(&self) -> &Vec { + log_debug!("Getting all available profiles"); + &self.profiles + } + + /// Sets the current profile to profile which name was supplied. Returns an error if given + /// profile doesn't exist + pub fn set_current(&mut self, profile_name: &str) -> Result<()> { + log_debug!("Setting current profile to \"{}\"", profile_name); + + self.current_profile = Some(profile_name.to_string()); + self.save()?; + + log_debug!("Set current profile to \"{}\"", profile_name); + Ok(()) + } + + /// Deletes a profile with provided name + pub fn delete_profile(&mut self, profile_name: &str) -> Result<()> { + log_debug!("Deleting profile with name \"{}\"", profile_name); + + for (i, profile) in self.profiles.iter().enumerate() { + if profile.name == profile_name { + self.profiles.remove(i); + self.current_profile = { + if self.profiles.is_empty() { + None + } else { + Some(self.profiles.get(0).unwrap().name.clone()) + } + }; + self.save()?; + return Ok(()) + } + } + + Err(Error::ProfileError(format!("Profile with name \"{}\" doesn\'t exist", profile_name))) + } + + /// Saves provided profile data to profiles file. Updates existing profile or creates a new one, + /// if it doesn't already exist + #[allow(dead_code)] + pub fn save_profile(&mut self, profile: Profile) -> Result<()> { + log_debug!("Saving profile: {:?}", &profile); + + let profile_name = profile.name.clone(); + + if self.profiles.is_empty() { + self.profiles.push(profile); + self.current_profile = Some(profile_name); + } else { + for i in 0..self.profiles.len() { + if self.profiles[i].name == profile_name { + self.profiles.insert(i, profile); + break; + } + + if i == self.profiles.len() - 1 { + self.profiles.push(profile); + break; + } + } + } + + self.save()?; + Ok(()) + } + + /// Adds a new profile to the profiles file. Errors if the profile already exists, as this + /// functions only accepts new profiles + pub fn new_profile(&mut self, profile: Profile) -> Result<()> { + log_debug!("Adding a new profile: {:?}", &profile); + + let profile_name = profile.name.clone(); + if self.find_profile(&profile_name).is_ok() { + return Err(Error::ProfileError(format!("Profile with name \"{}\" already exists", profile_name))); + } + self.profiles.push(profile); + + self.save()?; + Ok(()) + } + + /// Returns profile for profile which name was supplied + pub fn find_profile(&mut self, profile_name: &str) -> Result<&mut Profile> { + log_debug!("Searching for profile with name \"{}\"", profile_name); + + for profile in &mut self.profiles { + if profile.name == profile_name { + return Ok(profile) + } + } + + Err(Error::ProfileError(format!("Profile with name \"{}\" doesn\'t exist", profile_name))) + } + + /// Writes to the profile data file. Overwrites old data + pub fn save(&self) -> Result<()> { + log_debug!("Saving profiles data to \"profiles.json\""); + let json_data = serde_json::to_string(&self)?; + + write_file(&self.file_path, &json_data, true)?; + Ok(()) + } +} + +/// Struct containing main information about a profile +#[derive(Serialize, Deserialize, Debug)] +pub struct Profile { + pub name: String, + pub key: Key, + password_hash: String +} + +impl Profile { + pub fn new( + name: &str, + password: &str + ) -> Result { + let (hash, _salt) = auth::hash_password(password)?; + Ok(Profile { + name: name.to_string(), + key: cipher::generate_key(), + password_hash: hash, + }) + } + + /// Checks whether the provided password is valid for the profile + pub fn verify_password(&self, password: &str) -> bool { + auth::verify_password(&self.password_hash, password) + } + + /// Sets a new key for the profile + pub fn set_key(&mut self, key: Key) { + self.key = key; + } +} + +pub fn get_profiles() -> LockboxProfiles { + log_debug!("Getting Lockbox profiles"); + + match LockboxProfiles::import() { + Ok(profiles) => profiles, + Err(err) => { + log_error!("Unable to import Lockbox profiles"); + log_error!("{}", err); + std::process::exit(1); + } + } +} \ No newline at end of file diff --git a/src/core/data/profiles.rs b/src/core/data/profiles.rs deleted file mode 100644 index c6c3307..0000000 --- a/src/core/data/profiles.rs +++ /dev/null @@ -1,232 +0,0 @@ -//! Contains function for user profile manipulation - -use std::io::{self, BufReader, Write}; -use std::fs::File; -use std::path::PathBuf; -use serde::{Deserialize, Serialize}; -use crate::core::encryption::cipher; -use crate::{log_debug, Error, Key, Result}; -use crate::core::data::os::get_data_dir; -use super::auth; - -/// Name of the file which stores all the profile data -const PROFILES_FILE_NAME: &str = "profiles.json"; - -/// Struct representing a JSON profile data file -#[derive(Serialize, Deserialize, Debug)] -struct ProfilesData { - pub current_profile: Option, - pub profiles: Vec -} - -/// Struct containing main information about a profile -#[derive(Serialize, Deserialize, Debug)] -pub struct Profile { - pub name: String, - pub key: Key, - pub password_hash: String, -} - -impl ProfilesData { - pub fn default() -> ProfilesData { - ProfilesData { - current_profile: None, - profiles: vec![] - } - } -} - -/// Sets the current profile to profile which name was supplied. Returns an error if given profile -/// doesn't exist -pub fn set_current_profile(profile_name: &str) -> Result<()> { - log_debug!("Setting current profile to \"{}\"", profile_name); - - let data_dir = get_data_dir()?; - let profiles_data = read_profiles_file(&data_dir); - if let Err(err) = profiles_data { - if err.kind() == io::ErrorKind::NotFound { - return Err(Error::ProfileError("No profile data found".to_string())) - } - return Err(Error::IOError(format!("Error getting profiles data: {}", err))) - } - let mut profiles_data = profiles_data?; - profiles_data.current_profile = Some(profile_name.to_string()); - - write_profiles_file(profiles_data, &data_dir)?; - Ok(()) -} - -/// Returns currently selected profile data -pub fn get_current_profile() -> Result { - log_debug!("Getting current profile"); - - let data_dir = get_data_dir()?; - let profiles_data = read_profiles_file(&data_dir); - if let Err(err) = profiles_data { - if err.kind() == io::ErrorKind::NotFound { - return Err(Error::ProfileError("No profile data found".to_string())) - } - return Err(Error::IOError(format!("Error getting profiles data: {}", err))) - } - - let current_profile = profiles_data?.current_profile; - if current_profile.is_none() { - return Err(Error::ProfileError("No profile is currently selected".to_string())) - } - - let profile = get_profile(¤t_profile.unwrap())?; - Ok(profile) -} - -/// Returns profile data for profile which name was supplied -pub fn get_profile(profile_name: &str) -> Result { - log_debug!("Getting profile with name \"{}\"", profile_name); - - for profile in get_profiles()? { - if profile.name == profile_name { - return Ok(profile) - } - } - Err(Error::ProfileError(format!("Profile with name \"{}\" doesn\'t exist", profile_name))) -} - -/// Returns a list of currently available profiles -pub fn get_profiles() -> Result> { - log_debug!("Getting all available profiles"); - - let data_dir = get_data_dir()?; - let profiles_data = read_profiles_file(&data_dir); - if let Err(err) = profiles_data { - if err.kind() == io::ErrorKind::NotFound { - return Err(Error::ProfileError("No profile data found".to_string())) - } - return Err(Error::IOError(format!("Error getting profiles data: {}", err))) - } - - Ok(profiles_data?.profiles) -} - -/// Creates a new profile with provided name and password. Password is hashed automatically -pub fn create_new_profile(name: &str, password: &str) -> Result<()> { - log_debug!("Creating a new profile named \"{}\"", name); - - if let Ok(_) = get_profile(name) { - return Err(Error::ProfileError(format!("Profile with name \"{}\" already exists", name))); - } - - let (hash, _salt) = auth::hash_password(password)?; - let profile = Profile { - name: name.to_string(), - key: cipher::generate_key(), - password_hash: hash, - }; - - save_profile(profile) - .map_err(|err| Error::IOError(format!("Unable to save profile data: {}", err))) -} - -/// Deletes a profile with provided name -pub fn delete_profile(profile_name: &str) -> Result<()> { - log_debug!("Deleting profile with name \"{}\"", profile_name); - - let data_dir = get_data_dir()?; - let mut profiles_data = read_profiles_file(&data_dir)?; - for (i, profile) in profiles_data.profiles.iter().enumerate() { - if profile.name == profile_name { - profiles_data.profiles.remove(i); - profiles_data.current_profile = { - if profiles_data.profiles.is_empty() { - None - } else { - Some(profiles_data.profiles.get(0).unwrap().name.clone()) - } - }; - write_profiles_file(profiles_data, &data_dir)?; - return Ok(()) - } - } - Err(Error::ProfileError(format!("Profile with name \"{}\" doesn\'t exist", profile_name))) -} - -/// Saves provided profile data to profiles file. Updates existing profile or creates a new one, if -/// it doesn't already exist -pub fn save_profile(updated_profile: Profile) -> Result<()> { - log_debug!("Saving profile data: {:?}", &updated_profile); - - let data_dir = get_data_dir()?; - let mut profiles_data = read_profiles_file(&data_dir)?; - let profile_name = updated_profile.name.clone(); - - if profiles_data.profiles.is_empty() { - if profiles_data.profiles.is_empty() { - profiles_data.profiles.push(updated_profile); - profiles_data.current_profile = Some(profile_name); - } - } else { - for (i, profile) in profiles_data.profiles.iter().enumerate() { - if i == profiles_data.profiles.len() - 1 { - profiles_data.profiles.push(updated_profile); - break; - } - if profile.name == profile_name { - profiles_data.profiles.insert(i, updated_profile); - break; - } - } - } - - write_profiles_file(profiles_data, &data_dir)?; - Ok(()) -} - -/// Writes to profiles profile provided profiles data. Overwrites old data -fn write_profiles_file(profiles_data: ProfilesData, profiles_directory: &PathBuf) -> io::Result<()> { - log_debug!("Writing data to profiles file: {:?}", profiles_data); - - let profiles_file = profiles_directory.join(PROFILES_FILE_NAME); - - let mut file = File::options() - .write(true) - .create(true) - .truncate(true) - .open(profiles_file)?; - - serde_json::to_writer(&file, &profiles_data) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Unable to serialize profiles file data"))?; - - file.flush()?; - Ok(()) -} - -/// Reads and returns profiles data from file -fn read_profiles_file(profiles_directory: &PathBuf) -> io::Result { - log_debug!("Getting profiles file data"); - - let profiles_file = profiles_directory.join(PROFILES_FILE_NAME); - - let profiles_data: ProfilesData = match File::open(&profiles_file) { - Ok(file) => { - let reader = BufReader::new(file); - let profiles_data = serde_json::from_reader(reader); - - if let Err(_) = profiles_data { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Unable to deserialize profiles file data" - )) - } else { - profiles_data? - } - }, - Err(err) => { - if err.kind() == io::ErrorKind::NotFound { - ProfilesData::default() - } else { - return Err(err); - } - } - }; - - log_debug!("Got keys file data: {:?}", profiles_data); - Ok(profiles_data) -} \ No newline at end of file diff --git a/src/core/encryption/mod.rs b/src/core/encryption/mod.rs index 9da7992..7f6e47d 100644 --- a/src/core/encryption/mod.rs +++ b/src/core/encryption/mod.rs @@ -5,8 +5,8 @@ use std::path::{Path, PathBuf}; use crate::{Error, Result}; use crate::options; use crate::log_debug; -use super::data::{auth, keys, profiles}; -use super::file::{header, io, parser}; +use super::data::{io, keys, profile}; +use super::file::{header, parser}; pub mod cipher; pub mod checksum; @@ -15,9 +15,10 @@ pub mod checksum; /// and get access to current profile. Additional options can be supplied to change the encryption /// process pub fn encrypt(password: &str, input_path: &Path, opts: &mut options::EncryptionOptions) -> Result<()> { - let profile = profiles::get_current_profile()?; + let mut profiles = profile::get_profiles(); + let profile = profiles.get_current_profile()?; - if !auth::verify_password(password, profile) { + if !profile.verify_password(password) { return Err(Error::AuthError("Invalid password entered".to_string())) } @@ -66,9 +67,10 @@ pub fn encrypt(password: &str, input_path: &Path, opts: &mut options::Encryption /// verify and get access to current profile. Additional options can be supplied to change the /// decryption process pub fn decrypt(password: &str, input_path: &Path, opts: &mut options::DecryptionOptions) -> Result<()> { - let profile = profiles::get_current_profile()?; + let mut profiles = profile::get_profiles(); + let profile = profiles.get_current_profile()?; - if !auth::verify_password(password, profile) { + if !profile.verify_password(password) { return Err(Error::AuthError("Invalid password entered".to_string())) } @@ -116,7 +118,7 @@ pub fn decrypt(password: &str, input_path: &Path, opts: &mut options::Decryption } let file_path = path_buffer.as_path(); - io::write_bytes(&file_path, &body).map_err(Error::from)?; + io::write_bytes(&file_path, &body, true).map_err(Error::from)?; Ok(()) } \ No newline at end of file diff --git a/src/core/error.rs b/src/core/error.rs index 800bd7a..5d4a7c4 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -13,6 +13,8 @@ pub enum Error { ProfileError(String), /// Error related to anything to do with current program custom configuration ConfigError(String), + /// Error related to serializing and deserializing of profile, config and other files + SerializeError(String), /// Error related to accessing, reading or writing files IOError(String), /// Error related to the user's filesystem, operating system and similar things @@ -32,6 +34,7 @@ impl fmt::Display for Error { match self { Error::ProfileError(ref msg) => write!(f, "Profile error - {}", msg), Error::ConfigError(ref msg) => write!(f, "Config error - {}", msg), + Error::SerializeError(ref msg) => write!(f, "Serialize error - {}", msg), Error::IOError(ref err) => write!(f, "{}", err), Error::OSError(ref err) => write!(f, "{}", err), Error::CipherError(ref err) => write!(f, "{}", err), @@ -46,4 +49,10 @@ impl From for Error { fn from(err: io::Error) -> Error { Error::IOError(err.to_string()) } +} + +impl From for Error { + fn from(err: serde_json::Error) -> Error { + Error::SerializeError(err.to_string()) + } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 1e8eab0..6419e1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ pub use core::error::{Error, Result}; pub use core::utils; +use crate::core::data::profile::Profile; // TODO: find a way to use the parser in utils without re-importing it pub mod cli; @@ -50,7 +51,8 @@ pub mod options { /// program will simply not work without a user profile pub fn encrypt(password: &str, input_path: &std::path::Path, opts: &mut options::EncryptionOptions) -> Result<()> { // TODO: split some code - core::encryption::encrypt(password, input_path, opts) + core::encryption::encrypt(password, input_path, opts)?; + Ok(()) } /// Decrypts the file at the given path. Extra options can be provided to control the process. @@ -65,7 +67,8 @@ pub fn encrypt(password: &str, input_path: &std::path::Path, opts: &mut options: /// program will simply not work without a user profile pub fn decrypt(password: &str, input_path: &std::path::Path, opts: &mut options::DecryptionOptions) -> Result<()> { // TODO: split some code - core::encryption::decrypt(password, input_path, opts) + core::encryption::decrypt(password, input_path, opts)?; + Ok(()) } /// Creates a new profile with the provided password and profile name. Will **not** automatically @@ -80,7 +83,9 @@ pub fn decrypt(password: &str, input_path: &std::path::Path, opts: &mut options: /// * `CipherError` - unsuccessful attempt to hash the password pub fn create_profile(password: &str, profile_name: &str) -> Result<()> { log_info!("Creating a new profile with name \"{}\"", profile_name); - core::data::profiles::create_new_profile(profile_name, password) + let mut profiles = core::data::profile::get_profiles(); + profiles.new_profile(Profile::new(profile_name, password)?)?; + Ok(()) } /// Deletes the profile with the corresponding name. After deletion will switch back to the first @@ -95,14 +100,16 @@ pub fn create_profile(password: &str, profile_name: &str) -> Result<()> { /// * `ProfileError` - if the target profile is not found /// * `IOError` - in case of failing to access or write to a `profiles.json` file pub fn delete_profile(password: &str, profile_name: &str) -> Result<()> { - let profile = core::data::profiles::get_profile(profile_name)?; + let mut profiles = core::data::profile::get_profiles(); + let profile = profiles.get_current_profile()?; - if !core::data::auth::verify_password(password, profile) { + if !profile.verify_password(password) { return Err(Error::AuthError("Invalid password entered".to_string())) } log_info!("Deleting profile \"{}\"", profile_name); - core::data::profiles::delete_profile(profile_name) + profiles.delete_profile(profile_name)?; + Ok(()) } /// Select (set as the current) the profile with the corresponding name @@ -117,18 +124,22 @@ pub fn delete_profile(password: &str, profile_name: &str) -> Result<()> { /// * `ProfileError` - if the target profile is not found /// * `IOError` - in case of failing to access or write to a `profiles.json` file pub fn select_profile(password: &str, profile_name: &str) -> Result<()> { - let profile = core::data::profiles::get_profile(profile_name)?; + let mut profiles = core::data::profile::get_profiles(); + let profile = profiles.find_profile(profile_name)?; - if !core::data::auth::verify_password(password, profile) { + if !profile.verify_password(password) { return Err(Error::AuthError("Invalid password entered".to_string())) } - if profile_name == core::data::profiles::get_current_profile()?.name { - return Err(Error::ProfileError(format!("Current profile is already set to \"{}\"", profile_name))) + if let Ok(profile) = profiles.get_current_profile() { + if profile_name == profile.name { + return Err(Error::ProfileError(format!("Current profile is already set to \"{}\"", profile_name))) + } } log_info!("Switching profile to \"{}\"", profile_name); - core::data::profiles::set_current_profile(profile_name) + profiles.set_current(profile_name)?; + Ok(()) } /// Returns the name of the currently selected profile @@ -142,9 +153,9 @@ pub fn select_profile(password: &str, profile_name: &str) -> Result<()> { /// * `IOError` - in case of failing to access or write to a `profiles.json` file pub fn get_profile() -> Result { log_info!("Getting current profile"); - - let profile = core::data::profiles::get_current_profile()?; - Ok(profile.name) + let mut profiles = core::data::profile::get_profiles(); + let profile = profiles.get_current_profile()?; + Ok(profile.name.to_string()) } /// Returns the names of all currently available profiles @@ -159,10 +170,11 @@ pub fn get_profile() -> Result { pub fn get_profiles() -> Result> { log_info!("Listing all available profiles"); - let profiles = core::data::profiles::get_profiles()?.iter() + let profiles = core::data::profile::get_profiles(); + let profile_list = profiles.get_profiles().into_iter() .map(|p| p.name.to_string()) .collect::>(); - Ok(profiles) + Ok(profile_list) } /// Generates a new encryption key for the current profile @@ -179,14 +191,16 @@ pub fn get_profiles() -> Result> { /// * `ProfileError` - if there is no current profile or no profiles found in general /// * `IOError` - in case of failing to access or write to a `profiles.json` file pub fn new_key(password: &str) -> Result<()> { - let profile = core::data::profiles::get_current_profile()?; - - if !core::data::auth::verify_password(password, profile) { + let mut profiles = core::data::profile::get_profiles(); + let profile = profiles.get_current_profile()?; + + if !profile.verify_password(password) { return Err(Error::AuthError("Invalid password entered".to_string())) } log_info!("Generating a new encryption key for current profile"); - core::data::keys::generate_new_key() + core::data::keys::set_key(core::encryption::cipher::generate_key())?; + Ok(()) } /// Returns the encryption key being used by the current profile in a hex format @@ -200,9 +214,10 @@ pub fn new_key(password: &str) -> Result<()> { /// * `ProfileError` - if there is no current profile or no profiles found in general /// * `IOError` - in case of failing to access or write to a `profiles.json` file pub fn get_key(password: &str, opts: options::GetKeyOptions) -> Result { - let profile = core::data::profiles::get_current_profile()?; - - if !core::data::auth::verify_password(password, profile) { + let mut profiles = core::data::profile::get_profiles(); + let profile = profiles.get_current_profile()?; + + if !profile.verify_password(password) { return Err(Error::AuthError("Invalid password entered".to_string())) } @@ -228,15 +243,15 @@ pub fn get_key(password: &str, opts: options::GetKeyOptions) -> Result { /// * `ProfileError` - if there is no current profile or no profiles found in general /// * `IOError` - in case of failing to access or write to a `profiles.json` file pub fn set_key(password: &str, new_key: &str) -> Result<()> { - let profile = core::data::profiles::get_current_profile()?; + let mut profiles = core::data::profile::get_profiles(); + let profile = profiles.get_current_profile()?; - if !core::data::auth::verify_password(password, profile) { + if !profile.verify_password(password) { return Err(Error::AuthError("Invalid password entered".to_string())) } log_info!("Setting the encryption key from the current profile"); let new_key = utils::hex::hex_string_to_key(new_key.to_string())?; core::data::keys::set_key(new_key)?; - Ok(()) } \ No newline at end of file diff --git a/tests/cli_tests.rs b/tests/cli_tests.rs index efa8ae4..922c91a 100644 --- a/tests/cli_tests.rs +++ b/tests/cli_tests.rs @@ -19,6 +19,7 @@ fn print_output(output: &Output) { /// Local test environment setup fn setup() { + common::cleanup(); common::setup(); } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 07ef6d0..0d1a025 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,11 +1,11 @@ //! Contains common functions and constants for running tests. -pub mod commands; - -use std::{fs, io, mem}; +use std::{fs, io}; use std::path::Path; use lockbox::Error; +pub mod commands; + pub const PROFILE_NAME: &str = "common-test-profile"; pub const PASSWORD: &str = "common-test-password"; pub const ORIGINAL_DIR: &str = "files/original"; @@ -14,15 +14,15 @@ pub const TEST_DIR: &str = "files/test"; /// Global test environment setup (must be run before each test) pub fn setup() { lockbox::create_profile(PASSWORD, PROFILE_NAME) - .unwrap_or_else(|err| - if mem::discriminant(&err) != mem::discriminant(&Error::ProfileError("".to_string())) { - panic!("Unable to create test profile: {}", err) + .unwrap_or_else(|err| match err { + Error::ProfileError(_) => {}, + _ => panic!("Unable to create test profile: {}", err) }); lockbox::select_profile(PASSWORD, PROFILE_NAME) - .unwrap_or_else(|err| - if mem::discriminant(&err) != mem::discriminant(&Error::ProfileError("".to_string())) { - panic!("Unable to select test profile: {}", err) + .unwrap_or_else(|err| match err { + Error::ProfileError(e) => println!("{}", e), + _ => panic!("Unable to select test profile: {}", err) }); copy_original_files() @@ -32,9 +32,9 @@ pub fn setup() { /// Global test environment cleanup (must be run after each test) pub fn cleanup() { lockbox::delete_profile(PASSWORD, PROFILE_NAME) - .unwrap_or_else(|err| - if mem::discriminant(&err) != mem::discriminant(&Error::ProfileError("".to_string())) { - panic!("Unable to delete test profile: {}", err) + .unwrap_or_else(|err| match err { + Error::ProfileError(_) => {}, + _ => panic!("Unable to delete test profile: {}", err) }); delete_test_files()