use super::auth;
use super::io::{read_file, write_file};
use crate::core::encryption::cipher;
use crate::{log_debug, log_info, new_err, Key, Nonce, Result};
use serde::{Deserialize, Serialize};
use std::io::{self};
use std::path::PathBuf;
const PROFILES_FILE_NAME: &str = "profiles.json";
#[derive(Serialize, Deserialize, Debug)]
pub struct DataboxerProfiles {
current_profile: Option<String>,
profiles: Vec<Profile>,
#[serde(skip)]
file_path: PathBuf
}
impl DataboxerProfiles {
pub fn import(data_directory: PathBuf) -> Result<Self> {
log_debug!("Importing Databoxer profiles");
let profiles_file = data_directory.join(PROFILES_FILE_NAME);
let profiles = match read_file(&profiles_file) {
Ok(file_data) => {
let mut profiles: DataboxerProfiles = 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)
}
fn new(
file_path: PathBuf
) -> Self {
DataboxerProfiles {
current_profile: None,
profiles: vec![],
file_path
}
}
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(new_err!(ProfileError: NotSelected)),
Some(profile_name) => {
self.find_profile(&profile_name)?
}
};
Ok(profile)
}
pub fn get_profiles(&self) -> &Vec<Profile> {
log_debug!("Getting all available profiles");
&self.profiles
}
pub fn set_current(&mut self, password: &str, profile_name: &str) -> Result<()> {
log_debug!("Setting current profile to \"{}\"", profile_name);
let profile = self.find_profile(profile_name)?;
profile.verify_password(password)?;
self.current_profile = Some(profile_name.to_string());
self.save()?;
log_debug!("Set current profile to \"{}\"", profile_name);
Ok(())
}
pub fn delete_profile(&mut self, profile_password: &str, profile_name: &str) -> Result<()> {
log_debug!("Trying to delete a profile with name \"{}\"", profile_name);
for (i, profile) in self.profiles.iter().enumerate() {
if profile.name == profile_name {
profile.verify_password(profile_password)?;
self.profiles.remove(i);
self.current_profile = {
if self.profiles.is_empty() {
None
} else {
Some(self.profiles.get(0).unwrap().name.clone())
}
};
self.save()?;
log_debug!("Deleted profile \"{}\"", profile_name);
return Ok(())
}
}
Err(new_err!(ProfileError: NotFound, profile_name))
}
#[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(())
}
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(new_err!(ProfileError: AlreadyExists, profile_name));
}
self.profiles.push(profile);
self.save()?;
Ok(())
}
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(new_err!(ProfileError: NotFound, profile_name))
}
pub fn save(&self) -> Result<()> {
log_debug!("Saving profiles data to \"profiles.json\"");
let json_data = serde_json::to_string_pretty(&self)?;
write_file(&self.file_path, &json_data, true)?;
Ok(())
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Profile {
pub name: String,
password_hash: String,
nonce: Nonce,
key: Vec<u8>,
}
impl Profile {
pub fn new(
name: &str,
password: &str
) -> Result<Self> {
let (password_hash, password_key) = auth::hash_password(password)?;
let key = cipher::generate_key();
let nonce = cipher::generate_nonce();
let encrypted_key = cipher::encrypt(&password_key, &nonce, &key)?;
Ok(Profile {
name: name.to_string(),
key: encrypted_key,
nonce,
password_hash,
})
}
pub fn verify_password(&self, password: &str) -> Result<()> {
match auth::verify_password(&self.password_hash, password) {
Ok(_) => Ok(()),
Err(err) => Err(err)
}
}
pub fn set_key(&mut self, password: &str, key: Key) -> Result<()> {
let password_key = auth::get_password_key(&self.password_hash, password)?;
let encrypted_key = cipher::encrypt(&password_key, &self.nonce, &key)?;
self.key = encrypted_key;
Ok(())
}
pub fn get_key(&self, password: &str) -> Result<Key> {
let encrypted_key = self.key.clone();
let password_key = auth::get_password_key(&self.password_hash, password)?;
let key = cipher::decrypt(&password_key, &self.nonce, &encrypted_key)?.try_into()
.map_err(|_| new_err!(InvalidData: InvalidLength, "encryption key"))?;
Ok(key)
}
}
#[cfg(test)]
mod tests {
use crate::core::data::os;
use super::*;
#[test]
#[ignore]
fn write_default_profiles() {
let data_directory = os::get_data_dir().expect("Cannot get data directory");
let config = DataboxerProfiles::import(data_directory);
assert!(config.is_ok())
}
}