use std::iter::FromIterator;
use std::time::{SystemTime, UNIX_EPOCH};
use async_trait::async_trait;
use log::*;
use rs_password_utils;
use toml;
use toml::value::Table;
use zeroize::{Zeroize, Zeroizing};
use crate::asynch::dropbox::DropboxConfiguration;
use crate::asynch::nextcloud::NextcloudConfiguration;
use super::{datacrypt, dropbox, errors, nextcloud};
use self::safe::Safe;
pub mod safe;
pub(crate) struct RklContent {
pub entries: Vec<Entry>,
pub nextcloud_conf: nextcloud::NextcloudConfiguration,
pub dropbox_conf: dropbox::DropboxConfiguration,
pub system_conf: SystemConfiguration,
}
impl RklContent {
pub fn new(entries: Vec<Entry>,
nextcloud_conf: nextcloud::NextcloudConfiguration,
dropbox_conf: dropbox::DropboxConfiguration,
system_conf: SystemConfiguration)
-> RklContent {
RklContent {
entries,
nextcloud_conf,
dropbox_conf,
system_conf,
}
}
pub fn from(tup: (&Safe, &nextcloud::NextcloudConfiguration, &dropbox::DropboxConfiguration, &SystemConfiguration)) -> errors::Result<RklContent> {
let entries = tup.0.get_entries_decrypted();
let nextcloud_conf = nextcloud::NextcloudConfiguration::new(tup.1.server_url.clone(),
tup.1.username.clone(),
tup.1.decrypted_password()?.to_string(),
tup.1.use_self_signed_certificate);
let dropbox_conf = dropbox::DropboxConfiguration::new(tup.2.decrypted_token()?);
let system_conf = SystemConfiguration::new(tup.3.saved_at, tup.3.version, tup.3.last_sync_version);
Ok(RklContent::new(entries, nextcloud_conf?, dropbox_conf?, system_conf))
}
}
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct RklConfiguration {
pub system: SystemConfiguration,
pub nextcloud: nextcloud::NextcloudConfiguration,
pub dropbox: dropbox::DropboxConfiguration,
}
impl RklConfiguration {
pub fn update_system_for_save(&mut self, update_last_sync_version: bool) -> errors::Result<()> {
if update_last_sync_version {
self.update_system_last_sync();
} else {
self.system.version = Some((self.system.version.unwrap_or(0)) + 1);
}
let local_time_seconds = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
self.system.saved_at = Some(local_time_seconds as i64);
Ok(())
}
pub fn update_system_last_sync(&mut self) {
self.system.last_sync_version = self.system.version;
}
}
impl From<(nextcloud::NextcloudConfiguration, dropbox::DropboxConfiguration, SystemConfiguration)> for RklConfiguration {
fn from(confs: (nextcloud::NextcloudConfiguration, dropbox::DropboxConfiguration, SystemConfiguration)) -> Self {
RklConfiguration {
system: confs.2,
nextcloud: confs.0,
dropbox: confs.1,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct SystemConfiguration {
pub saved_at: Option<i64>,
pub version: Option<i64>,
pub last_sync_version: Option<i64>,
}
impl SystemConfiguration {
pub fn new(saved_at: Option<i64>, version: Option<i64>, last_sync_version: Option<i64>) -> SystemConfiguration {
SystemConfiguration {
saved_at,
version,
last_sync_version,
}
}
pub fn from_table(table: &Table) -> Result<SystemConfiguration, errors::RustKeylockError> {
let saved_at = table.get("saved_at").and_then(|value| value.as_integer().and_then(Some));
let version = table.get("version").and_then(|value| value.as_integer().and_then(Some));
let last_sync_version = table.get("last_sync_version").and_then(|value| value.as_integer().and_then(Some));
Ok(SystemConfiguration::new(saved_at, version, last_sync_version))
}
pub fn to_table(&self) -> errors::Result<Table> {
let mut table = Table::new();
if self.saved_at.is_some() {
table.insert("saved_at".to_string(), toml::Value::Integer(self.saved_at.unwrap()));
}
if self.version.is_some() {
table.insert("version".to_string(), toml::Value::Integer(self.version.unwrap()));
}
if self.last_sync_version.is_some() {
table.insert("last_sync_version".to_string(), toml::Value::Integer(self.last_sync_version.unwrap()));
}
Ok(table)
}
}
impl Default for SystemConfiguration {
fn default() -> SystemConfiguration {
SystemConfiguration {
saved_at: None,
version: None,
last_sync_version: None,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
#[zeroize(drop)]
pub struct EntryMeta {
pub leaked_password: bool
}
impl EntryMeta {
pub fn new(leaked_password: bool) -> EntryMeta {
EntryMeta { leaked_password }
}
pub fn from_table(table: &Table) -> Result<EntryMeta, errors::RustKeylockError> {
let leaked_password = table.get("leaked_password").and_then(|value| value.as_bool());
match leaked_password {
Some(lp) => Ok(Self::new(lp)),
_ => Err(errors::RustKeylockError::ParseError(toml::ser::to_string(&table).unwrap_or_else(|_| "Cannot dserialize toml for EntryMeta".to_string()))),
}
}
pub fn to_table(&self) -> Table {
let mut table = Table::new();
table.insert("leaked_password".to_string(), toml::Value::Boolean(self.leaked_password));
table
}
}
impl Default for EntryMeta {
fn default() -> Self {
EntryMeta {
leaked_password: false,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
#[zeroize(drop)]
pub struct Entry {
pub name: String,
pub url: String,
pub user: String,
pub pass: String,
pub desc: String,
pub meta: EntryMeta,
pub encrypted: bool,
}
impl Entry {
pub fn new(name: String, url: String, user: String, pass: String, desc: String, meta: EntryMeta) -> Entry {
Entry {
name,
url,
user,
pass,
desc,
meta,
encrypted: false,
}
}
pub fn empty() -> Entry {
Entry {
name: "".to_string(),
url: "".to_string(),
user: "".to_string(),
pass: "".to_string(),
desc: "".to_string(),
meta: EntryMeta::default(),
encrypted: false,
}
}
pub fn from_table(table: &Table) -> Result<Entry, errors::RustKeylockError> {
let name = table.get("name").and_then(|value| value.as_str().and_then(|str_ref| Some(str_ref.to_string())));
let url = table.get("url").and_then(|value| value.as_str().and_then(|str_ref| Some(str_ref.to_string())));
let user = table.get("user").and_then(|value| value.as_str().and_then(|str_ref| Some(str_ref.to_string())));
let pass = table.get("pass").and_then(|value| value.as_str().and_then(|str_ref| Some(str_ref.to_string())));
let desc = table.get("desc").and_then(|value| value.as_str().and_then(|str_ref| Some(str_ref.to_string())));
let meta = table.get("meta").and_then(|value| {
value.as_table().map(|table| EntryMeta::from_table(table).unwrap_or_default())
}).unwrap_or_default();
match (name, url, user, pass, desc) {
(Some(n), Some(ul), Some(u), Some(p), Some(d)) => Ok(Self::new(n, ul, u, p, d, meta)),
_ => Err(errors::RustKeylockError::ParseError(toml::ser::to_string(&table).unwrap_or_else(|_| "Cannot serialize toml".to_string()))),
}
}
pub fn to_table(&self) -> Table {
let mut table = Table::new();
table.insert("name".to_string(), toml::Value::String(self.name.clone()));
table.insert("url".to_string(), toml::Value::String(self.url.clone()));
table.insert("user".to_string(), toml::Value::String(self.user.clone()));
table.insert("pass".to_string(), toml::Value::String(self.pass.clone()));
table.insert("desc".to_string(), toml::Value::String(self.desc.clone()));
table.insert("meta".to_string(), toml::Value::Table(self.meta.to_table()));
table
}
pub fn encrypted(&self, cryptor: &datacrypt::EntryPasswordCryptor) -> Entry {
let (encrypted_password, encryption_succeeded) = match cryptor.encrypt_str(&self.pass) {
Ok(encrypted) => (encrypted, true),
Err(_) => {
error!("Could not encrypt password for {}. Defaulting in keeping it in plain...", &self.name);
(self.pass.clone(), false)
}
};
Entry {
name: self.name.clone(),
url: self.url.clone(),
user: self.user.clone(),
pass: encrypted_password,
desc: self.desc.clone(),
meta: self.meta.clone(),
encrypted: encryption_succeeded,
}
}
pub fn decrypted(&self, cryptor: &datacrypt::EntryPasswordCryptor) -> Entry {
let decrypted_password = if self.encrypted {
match cryptor.decrypt_str(&self.pass) {
Ok(decrypted) => decrypted,
Err(_) => self.pass.clone(),
}
} else {
self.pass.clone()
};
Entry::new(self.name.clone(), self.url.clone(), self.user.clone(), decrypted_password, self.desc.clone(), self.meta.clone())
}
}
#[derive(Debug)]
pub enum EntryPresentationType {
View,
Delete,
Edit,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Props {
idle_timeout_seconds: isize,
generated_passphrases_words_count: isize,
}
impl Default for Props {
fn default() -> Self {
Props {
idle_timeout_seconds: 1800,
generated_passphrases_words_count: 5,
}
}
}
impl Props {
pub(crate) fn new(idle_timeout_seconds: isize, generated_passphrases_words_count: isize) -> Props {
Props {
idle_timeout_seconds,
generated_passphrases_words_count,
}
}
pub fn from_table(table: &Table) -> Result<Props, errors::RustKeylockError> {
let idle_timeout_seconds = table.get("idle_timeout_seconds")
.and_then(|value| value.as_integer().and_then(|v| Some(v as isize)))
.unwrap_or_else(|| Props::default().idle_timeout_seconds());
let generated_passphrases_words_count = table.get("generated_passphrases_words_count")
.and_then(|value| value.as_integer().and_then(|v| Some(v as isize)))
.unwrap_or_else(|| Props::default().generated_passphrases_words_count());
Ok(Self::new(idle_timeout_seconds, generated_passphrases_words_count))
}
#[allow(dead_code)]
pub fn to_table(&self) -> Table {
let mut table = Table::new();
table.insert("idle_timeout_seconds".to_string(), toml::Value::Integer(self.idle_timeout_seconds as i64));
table.insert("generated_passphrases_words_count".to_string(), toml::Value::Integer(self.generated_passphrases_words_count as i64));
table
}
pub fn idle_timeout_seconds(&self) -> isize {
self.idle_timeout_seconds
}
pub fn generated_passphrases_words_count(&self) -> isize {
self.generated_passphrases_words_count
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Menu {
TryPass(bool),
ChangePass,
Main,
EntriesList(String),
NewEntry(Option<Entry>),
ShowEntry(usize),
EditEntry(usize),
DeleteEntry(usize),
Save(bool),
Exit,
ForceExit,
TryFileRecovery,
ImportEntries,
ExportEntries,
ShowConfiguration,
WaitForDbxTokenCallback(String),
SetDbxToken(Zeroizing<String>),
Current,
}
#[derive(Debug, PartialEq, Clone)]
pub enum UserSelection {
NewEntry(Entry),
ReplaceEntry(usize, Entry),
DeleteEntry(usize),
GoTo(Menu),
ProvidedPassword(Zeroizing<String>, Zeroizing<usize>),
Ack,
ExportTo(String),
ImportFrom(String, Zeroizing<String>, Zeroizing<usize>),
ImportFromDefaultLocation(String, Zeroizing<String>, Zeroizing<usize>),
UserOption(UserOption),
UpdateConfiguration(AllConfigurations),
AddToClipboard(String),
GeneratePassphrase(Option<usize>, Entry),
CheckPasswords,
}
impl UserSelection {
pub fn is_same_variant_with(&self, other: &UserSelection) -> bool {
self.ordinal() == other.ordinal()
}
pub fn new_provided_password<T: Into<Zeroizing<String>>, U: Into<Zeroizing<usize>>>(password: T, number: U) -> UserSelection {
UserSelection::ProvidedPassword(password.into(), number.into())
}
pub fn new_import_from<T: Into<Zeroizing<String>>, U: Into<Zeroizing<usize>>>(location: String, password: T, number: U) -> UserSelection {
UserSelection::ImportFrom(location, password.into(), number.into())
}
pub fn new_import_from_default_location<T: Into<Zeroizing<String>>, U: Into<Zeroizing<usize>>>(location: String, password: T, number: U) -> UserSelection {
UserSelection::ImportFromDefaultLocation(location, password.into(), number.into())
}
fn ordinal(&self) -> i8 {
match self {
UserSelection::NewEntry(_) => 1,
UserSelection::ReplaceEntry(_, _) => 2,
UserSelection::DeleteEntry(_) => 3,
UserSelection::GoTo(_) => 4,
UserSelection::ProvidedPassword(_, _) => 5,
UserSelection::Ack => 6,
UserSelection::ExportTo(_) => 7,
UserSelection::ImportFrom(_, _, _) => 8,
UserSelection::ImportFromDefaultLocation(_, _, _) => 9,
UserSelection::UserOption(_) => 10,
UserSelection::UpdateConfiguration(_) => 11,
UserSelection::AddToClipboard(_) => 12,
UserSelection::GeneratePassphrase(_, _) => 13,
UserSelection::CheckPasswords => 14,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct AllConfigurations {
pub nextcloud: nextcloud::NextcloudConfiguration,
pub dropbox: dropbox::DropboxConfiguration,
}
impl AllConfigurations {
pub fn new(nextcloud: nextcloud::NextcloudConfiguration, dropbox: dropbox::DropboxConfiguration) -> AllConfigurations {
AllConfigurations {
nextcloud,
dropbox,
}
}
}
impl Default for AllConfigurations {
fn default() -> Self {
AllConfigurations::new(NextcloudConfiguration::default(), DropboxConfiguration::default())
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct UserOption {
pub label: String,
pub value: UserOptionType,
pub short_label: String,
}
impl<'a> From<&'a UserOption> for UserOption {
fn from(uo: &UserOption) -> Self {
UserOption {
label: uo.label.clone(),
value: uo.value.clone(),
short_label: uo.short_label.clone(),
}
}
}
impl From<(String, String, String)> for UserOption {
fn from(f: (String, String, String)) -> Self {
UserOption {
label: f.0,
value: UserOptionType::from(f.1.as_ref()),
short_label: f.2,
}
}
}
impl UserOption {
pub fn new(label: &str, option_type: UserOptionType, short_label: &str) -> UserOption {
UserOption {
label: label.to_string(),
value: option_type,
short_label: short_label.to_string(),
}
}
pub fn empty() -> UserOption {
UserOption {
label: "".to_string(),
value: UserOptionType::None,
short_label: "".to_string(),
}
}
pub fn ok() -> UserOption {
UserOption {
label: "Ok".to_string(),
value: UserOptionType::String("Ok".to_string()),
short_label: "o".to_string(),
}
}
pub fn cancel() -> UserOption {
UserOption {
label: "Cancel".to_string(),
value: UserOptionType::String("Cancel".to_string()),
short_label: "c".to_string(),
}
}
pub fn yes() -> UserOption {
UserOption {
label: "Yes".to_string(),
value: UserOptionType::String("Yes".to_string()),
short_label: "y".to_string(),
}
}
pub fn no() -> UserOption {
UserOption {
label: "No".to_string(),
value: UserOptionType::String("No".to_string()),
short_label: "n".to_string(),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum UserOptionType {
Number(isize),
String(String),
None,
}
impl UserOptionType {
fn extract_value_from_string(s: &str) -> errors::Result<String> {
let start = s.find('(');
if s.ends_with(')') {
match start {
Some(st) => {
let i = s.chars().skip(st + 1).take(s.len() - st - 2);
let s = String::from_iter(i);
Ok(s)
}
_ => {
Err(errors::RustKeylockError::ParseError(format!("Could not extract UserOptionType value from {}. The \
UserOptionType can be extracted from strings like String(value)",
s)))
}
}
} else {
Err(errors::RustKeylockError::ParseError(format!("Could not extract UserOptionType value from {}. The UserOptionType can be \
extracted from strings like String(value)",
s)))
}
}
}
impl ToString for UserOptionType {
fn to_string(&self) -> String {
match *self {
UserOptionType::String(ref s) => format!("String({})", s),
_ => format!("{:?}", &self),
}
}
}
impl<'a> From<&'a str> for UserOptionType {
fn from(string: &str) -> Self {
match string {
ref s if s.starts_with("String") => {
match Self::extract_value_from_string(s) {
Ok(value) => UserOptionType::String(value),
Err(error) => {
error!("Could not create UserOptionType from {}: {:?}. Please consider opening a bug to the developers.", s, error);
UserOptionType::None
}
}
}
ref s if s.starts_with("Number") => {
let m = Self::extract_value_from_string(s).and_then(|value| {
value.parse::<i64>().map_err(|_| errors::RustKeylockError::ParseError(format!("Could not parse {} to i64", value)))
});
match m {
Ok(num) => UserOptionType::Number(num as isize),
Err(error) => {
error!("Could not create UserOptionType from {}: {:?}. Please consider opening a bug to the developers.", s, error);
UserOptionType::None
}
}
}
other => {
error!("Could not create UserOptionType from {}. Please consider opening a bug to the developers.", other);
UserOptionType::None
}
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct EditorShowMessageWrapper {
pub message: String,
pub user_options: Vec<UserOption>,
pub severity: MessageSeverity,
}
impl EditorShowMessageWrapper {
pub(crate) fn new(message: &str, user_options: Vec<UserOption>, severity: MessageSeverity) -> EditorShowMessageWrapper {
EditorShowMessageWrapper {
message: message.to_string(),
user_options,
severity,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum MessageSeverity {
Info,
Warn,
Error,
}
impl Default for MessageSeverity {
fn default() -> Self {
MessageSeverity::Info
}
}
impl ToString for MessageSeverity {
fn to_string(&self) -> String {
format!("{:?}", &self)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub(crate) enum UiCommand {
ShowPasswordEnter,
ShowChangePassword,
ShowMenu(Menu),
ShowEntries(Vec<Entry>, String),
ShowEntry(Entry, usize, EntryPresentationType),
ShowConfiguration(NextcloudConfiguration, DropboxConfiguration),
Exit(bool),
ShowMessage(String, Vec<UserOption>, MessageSeverity),
}
#[async_trait]
pub(crate) trait PasswordChecker {
async fn is_unsafe(&self, password: &str) -> errors::Result<bool>;
}
pub(crate) struct RklPasswordChecker {}
impl Default for RklPasswordChecker {
fn default() -> Self {
RklPasswordChecker {}
}
}
#[async_trait]
impl PasswordChecker for RklPasswordChecker {
async fn is_unsafe(&self, password: &str) -> errors::Result<bool> {
Ok(rs_password_utils::pwned::is_pwned(password).await?)
}
}
#[cfg(test)]
mod api_unit_tests {
use toml;
use crate::api::{AllConfigurations, EntryMeta};
use crate::datacrypt::EntryPasswordCryptor;
use super::{Entry, Menu, UserOption, UserSelection};
#[test]
fn entry_from_table_success() {
let toml = r#"
name = "name1"
url = "url1"
user = "user1"
pass = "123"
desc = "some description"
meta = { leaked_password = true }
"#;
let value = toml.parse::<toml::value::Value>().unwrap();
let table = value.as_table().unwrap();
let entry_opt = Entry::from_table(&table);
assert!(entry_opt.is_ok());
let entry = entry_opt.unwrap();
assert!(entry.name == "name1");
assert!(entry.url == "url1");
assert!(entry.user == "user1");
assert!(entry.pass == "123");
assert!(entry.desc == "some description");
assert!(entry.meta.leaked_password)
}
#[test]
fn entry_from_table_success_no_meta() {
let toml = r#"
name = "name1"
url = "url1"
user = "user1"
pass = "123"
desc = "some description"
"#;
let value = toml.parse::<toml::value::Value>().unwrap();
let table = value.as_table().unwrap();
let entry_opt = Entry::from_table(&table);
assert!(entry_opt.is_ok());
let entry = entry_opt.unwrap();
assert!(entry.name == "name1");
assert!(entry.url == "url1");
assert!(entry.user == "user1");
assert!(entry.pass == "123");
assert!(entry.desc == "some description");
assert!(!entry.meta.leaked_password)
}
#[test]
fn entry_from_table_failure_wrong_key() {
let toml = r#"
wrong_key = "name1"
url = "url"
user = "user1"
pass = "123"
desc = "some description"
"#;
let value = toml.parse::<toml::value::Value>().unwrap();
let table = value.as_table().unwrap();
let entry_opt = super::Entry::from_table(&table);
assert!(entry_opt.is_err());
}
#[test]
fn entry_from_table_failure_wrong_value() {
let toml = r#"
name = 1
url = "url"
user = "user1"
pass = "123"
desc = "some description"
"#;
let value = toml.parse::<toml::value::Value>().unwrap();
let table = value.as_table().unwrap();
let entry_opt = super::Entry::from_table(&table);
assert!(entry_opt.is_err());
}
#[test]
fn entry_to_table() {
let toml = r#"
name = "name1"
url = "url1"
user = "user1"
pass = "123"
desc = "some description"
meta = {leaked_password = false}
"#;
let value = toml.parse::<toml::value::Value>().unwrap();
let table = value.as_table().unwrap();
let entry_opt = super::Entry::from_table(&table);
assert!(entry_opt.is_ok());
let entry = entry_opt.unwrap();
let new_table = entry.to_table();
dbg!(&new_table);
assert!(table == &new_table);
}
#[test]
fn entry_to_encrypted() {
let cryptor = EntryPasswordCryptor::new();
let entry = super::Entry::new("name".to_string(), "url".to_string(), "user".to_string(), "pass".to_string(), "desc".to_string(), EntryMeta::default());
let enc_entry = entry.encrypted(&cryptor);
assert!(enc_entry.name == entry.name);
assert!(enc_entry.url == entry.url);
assert!(enc_entry.user == entry.user);
assert!(enc_entry.pass != entry.pass);
assert!(enc_entry.desc == entry.desc);
let dec_entry = enc_entry.decrypted(&cryptor);
assert!(dec_entry.pass == entry.pass);
}
#[test]
fn entry_to_encrypted_encryption_may_fail() {
let cryptor = EntryPasswordCryptor::new();
let entry = super::Entry::new("name".to_string(), "url".to_string(), "user".to_string(), "pass".to_string(), "desc".to_string(), EntryMeta::default());
let dec_entry = entry.decrypted(&cryptor);
assert!(dec_entry.pass == entry.pass);
}
#[test]
fn props_from_table_success() {
let toml = r#"
idle_timeout_seconds = 33
generated_passphrases_words_count = 5
"#;
let value = toml.parse::<toml::value::Value>().unwrap();
let table = value.as_table().unwrap();
let props_opt = super::Props::from_table(&table);
assert!(props_opt.is_ok());
let props = props_opt.unwrap();
assert!(props.idle_timeout_seconds() == 33);
assert!(props.generated_passphrases_words_count() == 5);
}
#[test]
fn props_from_table_not_all_elements() {
let toml1 = r#"
idle_timeout_seconds = 33
"#;
let value1 = toml1.parse::<toml::value::Value>().unwrap();
let table1 = value1.as_table().unwrap();
let props_opt1 = super::Props::from_table(&table1);
assert!(props_opt1.is_ok());
let props1 = props_opt1.unwrap();
assert!(props1.idle_timeout_seconds() == 33);
assert!(props1.generated_passphrases_words_count() == 5);
let toml2 = r#"
generated_passphrases_words_count = 5
"#;
let value2 = toml2.parse::<toml::value::Value>().unwrap();
let table2 = value2.as_table().unwrap();
let props_opt2 = super::Props::from_table(&table2);
assert!(props_opt2.is_ok());
let props2 = props_opt2.unwrap();
assert!(props2.idle_timeout_seconds() == 1800);
assert!(props2.generated_passphrases_words_count() == 5);
}
#[test]
fn props_from_table_failure_wrong_key() {
let toml = r#"wrong_key = "alas""#;
let value = toml.parse::<toml::value::Value>().unwrap();
let table = value.as_table().unwrap();
let props_opt = super::Props::from_table(&table);
assert!(props_opt.is_ok());
}
#[test]
fn props_from_table_failure_wrong_value() {
let toml = r#"salt = 1"#;
let value = toml.parse::<toml::value::Value>().unwrap();
let table = value.as_table().unwrap();
let props_opt = super::Props::from_table(&table);
assert!(props_opt.is_ok());
}
#[test]
fn props_to_table() {
let toml = r#"
idle_timeout_seconds = 33
generated_passphrases_words_count = 5
"#;
let value = toml.parse::<toml::value::Value>().unwrap();
let table = value.as_table().unwrap();
let props_opt = super::Props::from_table(&table);
assert!(props_opt.is_ok());
let props = props_opt.unwrap();
let new_table = props.to_table();
assert!(table == &new_table);
}
#[test]
fn user_option_constructors() {
let opt1 = super::UserOption::cancel();
assert!(&opt1.label == "Cancel");
assert!(opt1.value == super::UserOptionType::String("Cancel".to_string()));
assert!(&opt1.short_label == "c");
let opt2 = super::UserOption::empty();
assert!(&opt2.label == "");
assert!(opt2.value == super::UserOptionType::None);
assert!(&opt2.short_label == "");
let opt3 = super::UserOption::ok();
assert!(&opt3.label == "Ok");
assert!(opt3.value == super::UserOptionType::String("Ok".to_string()));
assert!(&opt3.short_label == "o");
}
#[test]
fn user_option_type_extract_value_from_string() {
let s1 = "String(my string)";
let sr1 = super::UserOptionType::extract_value_from_string(&s1).unwrap();
assert!(sr1 == "my string");
let s2 = "String(wrong";
assert!(super::UserOptionType::extract_value_from_string(&s2).is_err());
let s3 = "String wrong)";
assert!(super::UserOptionType::extract_value_from_string(&s3).is_err());
let s4 = "String((my string))";
let sr4 = super::UserOptionType::extract_value_from_string(&s4).unwrap();
assert!(sr4 == "(my string)");
}
#[test]
fn user_option_type_from_string() {
assert!(super::UserOptionType::from("String(my string)") == super::UserOptionType::String("my string".to_string()));
assert!(super::UserOptionType::from("Number(33)") == super::UserOptionType::Number(33));
assert!(super::UserOptionType::from("Other(33)") == super::UserOptionType::None);
}
#[test]
fn user_option_type_to_string_from_string() {
let user_option_type = super::UserOptionType::String("my string".to_string());
let string = user_option_type.to_string();
assert!(string == "String(my string)");
let s: &str = &string;
assert!(super::UserOptionType::from(s) == super::UserOptionType::String("my string".to_string()));
}
#[test]
fn system_configuration_to_table() {
let toml = r#"
saved_at = 123
version = 1
"#;
let value = toml.parse::<toml::value::Value>().unwrap();
let table = value.as_table().unwrap();
let res = super::SystemConfiguration::from_table(&table);
assert!(res.is_ok());
let conf = res.unwrap();
let new_table = conf.to_table().unwrap();
assert!(table == &new_table);
}
#[test]
fn system_configuration_from_table_success() {
let toml = r#"
saved_at = 123
version = 1
"#;
let value = toml.parse::<toml::value::Value>().unwrap();
let table = value.as_table().unwrap();
let res = super::SystemConfiguration::from_table(&table);
assert!(res.is_ok());
let conf = res.unwrap();
assert!(conf.saved_at == Some(123));
assert!(conf.version == Some(1));
}
#[test]
fn user_selection_ordinal() {
assert!(UserSelection::NewEntry(Entry::empty()).ordinal() == 1);
assert!(UserSelection::ReplaceEntry(1, Entry::empty()).ordinal() == 2);
assert!(UserSelection::DeleteEntry(1).ordinal() == 3);
assert!(UserSelection::GoTo(Menu::TryPass(false)).ordinal() == 4);
assert!(UserSelection::new_provided_password("".to_owned(), 33).ordinal() == 5);
assert!(UserSelection::Ack.ordinal() == 6);
assert!(UserSelection::ExportTo("".to_owned()).ordinal() == 7);
assert!(UserSelection::new_import_from("".to_owned(), "".to_owned(), 1).ordinal() == 8);
assert!(UserSelection::new_import_from_default_location("".to_owned(), "".to_owned(), 1).ordinal() == 9);
assert!(UserSelection::UserOption(UserOption::empty()).ordinal() == 10);
assert!(UserSelection::UpdateConfiguration(AllConfigurations::default()).ordinal() == 11);
assert!(UserSelection::AddToClipboard("".to_owned()).ordinal() == 12);
}
#[test]
fn is_same_variant_with() {
assert!(UserSelection::new_provided_password("".to_owned(), 33).is_same_variant_with(&UserSelection::new_provided_password("other".to_owned(), 11)));
}
}