From c986d879bb4c5af52c312c7ab84788962436fce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Kj=C3=A4ll?= Date: Sun, 4 Jan 2026 09:36:05 +0100 Subject: [PATCH 1/5] enabled pedantic clippy lints --- Cargo.toml | 3 + cursive/src/main.rs | 40 ++++---- gtk/src/window/imp.rs | 2 +- gtk/src/window/mod.rs | 6 +- src/crypto.rs | 83 ++++++++-------- src/error.rs | 11 ++- src/git.rs | 34 ++++++- src/pass.rs | 151 +++++++++++++++++------------ src/passphrase_generator.rs | 20 ++-- src/password_generator.rs | 30 ++++-- src/signature.rs | 92 +++++++++--------- src/tests/crypto.rs | 4 +- src/tests/pass.rs | 167 +++++++++++++------------------- src/tests/password_generator.rs | 4 +- src/tests/test_helpers.rs | 62 ++++++++++-- 15 files changed, 397 insertions(+), 312 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cc834610..4054f9c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,9 @@ flate2 = "1" tar = "0.4" criterion = "0.8" +[lints.clippy] +pedantic = "warn" + [workspace] members = [ diff --git a/cursive/src/main.rs b/cursive/src/main.rs index d38e85eb..0a3fc554 100644 --- a/cursive/src/main.rs +++ b/cursive/src/main.rs @@ -54,6 +54,7 @@ use crate::helpers::{ }; use lazy_static::lazy_static; use ripasso::crypto::Fingerprint; +use ripasso::password_generator::PasswordGenerationCategory; use zeroize::Zeroize; /// The 'pointer' to the current PasswordStore is of this convoluted type. @@ -444,7 +445,7 @@ fn open(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { }) .button(CATALOG.gettext("Generate Password"), move |s| { - let mut new_password = password_generator(20, 0); + let mut new_password = password_generator(20, PasswordGenerationCategory::AsciiOnly); s.call_on_name("editbox", |e: &mut TextArea| { e.set_content(&new_password); }); @@ -456,7 +457,7 @@ fn open(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { let mut new_password = match passphrase_generator(6) { Ok(words) => words.join(" "), Err(err) => { - helpers::errorbox(s, &ripasso::pass::Error::from(err)); + helpers::errorbox(s, &err); return; } }; @@ -791,8 +792,12 @@ fn create(ui: &mut Cursive, store: PasswordStoreType) { move |s| { let category = *category_value.lock().unwrap(); let length = *password_length.lock().unwrap(); - let new_password = - ripasso::password_generator::password_generator(length, category); + let category = if category == 0 { + PasswordGenerationCategory::AsciiOnly + } else { + PasswordGenerationCategory::AsciiExtended + }; + let new_password = password_generator(length, category); s.call_on_name("new_password_input", |e: &mut EditView| { e.set_content(new_password); @@ -803,7 +808,7 @@ fn create(ui: &mut Cursive, store: PasswordStoreType) { let new_password = match ripasso::passphrase_generator::passphrase_generator(6) { Ok(words) => words.join(" "), Err(err) => { - helpers::errorbox(s, &ripasso::pass::Error::from(err)); + helpers::errorbox(s, &err); return; } }; @@ -1592,10 +1597,7 @@ fn save_edit_config( let e_k = if e_k_bool { let mut recipients: Vec = vec![]; - for (i, r) in all_recipients_from_stores(stores.clone())? - .iter() - .enumerate() - { + for (i, r) in all_recipients_from_stores(&stores)?.iter().enumerate() { if is_checkbox_checked(ui, &format!("edit_recipient_{i}")) && r.fingerprint.is_some() { recipients.push(r.clone()); } @@ -1648,7 +1650,7 @@ fn save_edit_config( } } - let save_res = pass::save_config(stores, config_file_location); + let save_res = pass::save_config(&stores, config_file_location); if let Err(err) = save_res { helpers::errorbox(ui, &err); } @@ -1692,7 +1694,7 @@ fn save_new_config( stores_borrowed.push(Arc::new(Mutex::new(new_store))); } - pass::save_config(stores, config_file_location)?; + pass::save_config(&stores, config_file_location)?; let mut l = ui.find_name::>("stores").unwrap(); @@ -1711,7 +1713,7 @@ fn edit_store_in_config( config_file_location: &Path, home: &Option, ) -> Result<()> { - let all_recipients = all_recipients_from_stores(stores.clone())?; + let all_recipients = all_recipients_from_stores(&stores)?; let l = ui.find_name::>("stores").unwrap(); @@ -1881,7 +1883,7 @@ fn delete_store_from_config( stores_borrowed.retain(|store| store.lock().unwrap().get_name() != name); } - let save_res = pass::save_config(stores, config_file_location); + let save_res = pass::save_config(&stores, config_file_location); if let Err(err) = save_res { helpers::errorbox(ui, &err); return Ok(()); @@ -1903,7 +1905,7 @@ fn add_store_to_config( config_file_location: &Path, home: &Option, ) -> Result<()> { - let all_recipients = all_recipients_from_stores(stores.clone())?; + let all_recipients = all_recipients_from_stores(&stores)?; let mut fields = LinearLayout::vertical(); let mut name_fields = LinearLayout::horizontal(); @@ -2153,10 +2155,10 @@ fn main() -> Result<()> { }; pass::read_config( - &password_store_dir, - &password_store_signing_key, - &home, - &xdg_config_home, + password_store_dir.as_deref(), + password_store_signing_key.as_deref(), + home.as_ref(), + xdg_config_home.as_ref(), ) }; if let Err(err) = config_res { @@ -2189,7 +2191,7 @@ fn main() -> Result<()> { eprintln!("Error {err}"); process::exit(1); } - if let Err(err) = pass::save_config(stores.clone(), &config_file_location) { + if let Err(err) = pass::save_config(&stores, &config_file_location) { eprintln!("Error {err}"); process::exit(1); } diff --git a/gtk/src/window/imp.rs b/gtk/src/window/imp.rs index 21b093bf..394cc3b3 100644 --- a/gtk/src/window/imp.rs +++ b/gtk/src/window/imp.rs @@ -76,7 +76,7 @@ impl ObjectImpl for Window { // Setup let obj = self.obj(); obj.setup_collections(); - obj.restore_data(home_dir, user_config_dir); + obj.restore_data(home_dir.as_ref(), user_config_dir.as_ref()); obj.setup_callbacks(); obj.setup_actions(); } diff --git a/gtk/src/window/mod.rs b/gtk/src/window/mod.rs index bcb068a5..2c4b4669 100644 --- a/gtk/src/window/mod.rs +++ b/gtk/src/window/mod.rs @@ -101,15 +101,15 @@ impl Window { ) } - fn restore_data(&self, home_dir: Option, user_config_dir: Option) { - let (config, home) = ripasso::pass::read_config(&None, &None, &home_dir, &user_config_dir) + fn restore_data(&self, home_dir: Option<&PathBuf>, user_config_dir: Option<&PathBuf>) { + let (config, home) = ripasso::pass::read_config(None, None, home_dir, user_config_dir) .expect("No config file present"); let stores = get_stores(&config, &Some(home)).expect("Problem constructing stores"); // Convert `Vec` to `Vec` let collections: Vec = stores .into_iter() - .map(|s| CollectionObject::from_store_data(s, &user_config_dir.clone().unwrap())) + .map(|s| CollectionObject::from_store_data(s, user_config_dir.unwrap())) .collect(); // Insert restored objects into model diff --git a/src/crypto.rs b/src/crypto.rs index 2f86c211..0b2149a6 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -182,13 +182,16 @@ impl AsRef<[u8]> for Fingerprint { /// Models the interactions that can be done on a pgp key pub trait Key { - /// returns a list of names associated with the key + /// Returns a list of names associated with the key. fn user_id_names(&self) -> Vec; - /// returns the keys fingerprint + /// Returns the keys fingerprint. + /// + /// # Errors + /// If a gpg context can't be created, and the gpg backend is chosen. fn fingerprint(&self) -> Result; - /// returns if the key isn't usable + /// Returns if the key isn't usable. fn is_not_usable(&self) -> bool; } @@ -228,6 +231,7 @@ pub trait Crypto { /// Will return `Err` if decryption fails, for example if the current user isn't the /// recipient of the message. fn decrypt_string(&self, ciphertext: &[u8]) -> Result; + /// Encrypts a string /// # Errors /// Will return `Err` if encryption fails, for example if the current users key @@ -256,6 +260,9 @@ pub trait Crypto { ) -> std::result::Result; /// Returns true if a recipient is in the user's keyring. + /// + /// # Errors + /// If a gpg context can't be created, and the gpg backend is chosen. fn is_key_in_keyring(&self, recipient: &Recipient) -> Result; /// Pull keys from the keyserver for those recipients. @@ -273,7 +280,7 @@ pub trait Crypto { /// Will return `Err` if `key_id` didn't correspond to a key. fn get_key(&self, key_id: &str) -> Result>; - /// Returns a map from key fingerprints to OwnerTrustLevel's + /// Returns a map from key fingerprints to `OwnerTrustLevel`'s /// # Errors /// Will return `Err` on failure to obtain trust levels. fn get_all_trust_items(&self) -> Result>; @@ -294,7 +301,7 @@ impl Crypto for GpgMe { let mut ctx = gpgme::Context::from_protocol(gpgme::Protocol::OpenPgp)?; let mut output = Vec::new(); ctx.decrypt(ciphertext, &mut output)?; - let result = String::from_utf8(output.to_vec())?; + let result = String::from_utf8(output.clone())?; output.zeroize(); Ok(result) } @@ -514,7 +521,7 @@ struct Helper<'a> { key_ring: &'a HashMap>, /// This is all the certificates that are allowed to sign something public_keys: Vec>, - /// context if talking to gpg_agent for example + /// context if talking to `gpg_agent` for example ctx: Option, /// to do verification or not do_signature_verification: bool, @@ -555,29 +562,27 @@ impl VerificationHelper for Helper<'_> { fn find( key_ring: &HashMap>, - recipient: &Option, + recipient: Option<&KeyHandle>, ) -> Result> { let recipient = recipient.as_ref().ok_or(Error::Generic("No recipient"))?; match recipient { - KeyHandle::Fingerprint(fpr) => { - match fpr { - sequoia_openpgp::Fingerprint::V6(v6) => { - if let Some(key_handle) = key_ring.get(&Fingerprint::V6(*v6)) { - return Ok(key_handle.clone()); - } + KeyHandle::Fingerprint(fpr) => match fpr { + sequoia_openpgp::Fingerprint::V6(v6) => { + if let Some(key_handle) = key_ring.get(&Fingerprint::V6(*v6)) { + return Ok(key_handle.clone()); } - sequoia_openpgp::Fingerprint::V4(v4) => { - if let Some(key_handle) = key_ring.get(&Fingerprint::V4(*v4)) { - return Ok(key_handle.clone()); - } - } - sequoia_openpgp::Fingerprint::Unknown { .. } => { - return Err(Error::Generic("unknown fingerprint version")); + } + sequoia_openpgp::Fingerprint::V4(v4) => { + if let Some(key_handle) = key_ring.get(&Fingerprint::V4(*v4)) { + return Ok(key_handle.clone()); } - _ => {} - }; - } + } + sequoia_openpgp::Fingerprint::Unknown { .. } => { + return Err(Error::Generic("unknown fingerprint version")); + } + _ => {} + }, KeyHandle::KeyID(key_id) => match key_id { KeyID::Long(bytes) => { for (key, value) in key_ring { @@ -608,7 +613,7 @@ impl DecryptionHelper for Helper<'_> { // we don't know which key is the users own key, so lets try them all let mut selected_fingerprint: Option> = None; for pkesk in pkesks { - if let Ok(cert) = find(self.key_ring, &pkesk.recipient()) { + if let Ok(cert) = find(self.key_ring, pkesk.recipient().as_ref()) { let key = cert.primary_key().key(); let mut pair = sequoia_gpg_agent::KeyPair::new_for_gnupg_context( self.ctx @@ -648,8 +653,7 @@ impl DecryptionHelper for Helper<'_> { for pkesk in pkesks { if pkesk .decrypt(&mut pair, sym_algo) - .map(|(algo, sk)| decrypt(algo, &sk)) - .unwrap_or(false) + .is_some_and(|(algo, sk)| decrypt(algo, &sk)) { return Ok(Some( (*self @@ -688,9 +692,8 @@ impl Key for SequoiaKey { fn is_not_usable(&self) -> bool { let p = sequoia_openpgp::policy::StandardPolicy::new(); - let policy = match self.cert.with_policy(&p, None) { - Err(_) => return true, - Ok(p) => p, + let Ok(policy) = self.cert.with_policy(&p, None) else { + return true; }; self.cert.revocation_status(&p, None) != RevocationStatus::NotAsFarAsWeKnow @@ -737,6 +740,7 @@ impl Sequoia { }) } + #[must_use] pub fn from_values( user_key_id: Fingerprint, key_ring: HashMap>, @@ -756,8 +760,8 @@ impl Sequoia { let mut result = vec![]; for recipient in input { - match &recipient.fingerprint { - Some(fp) => match self.key_ring.get(fp) { + if let Some(fp) = &recipient.fingerprint { + match self.key_ring.get(fp) { Some(cert) => result.push(cert.clone()), None => { return Err(Error::GenericDyn(format!( @@ -765,17 +769,16 @@ impl Sequoia { recipient.key_id ))); } - }, - None => { - let kh: KeyHandle = recipient.key_id.parse()?; - - for cert in self.key_ring.values() { - if cert.key_handle().aliases(&kh) { - result.push(cert.clone()); - } + } + } else { + let kh: KeyHandle = recipient.key_id.parse()?; + + for cert in self.key_ring.values() { + if cert.key_handle().aliases(&kh) { + result.push(cert.clone()); } } - }; + } } Ok(result) diff --git a/src/error.rs b/src/error.rs index 57f9d815..b8e65b13 100644 --- a/src/error.rs +++ b/src/error.rs @@ -190,21 +190,20 @@ impl std::fmt::Display for Error { Self::Gpg(err) => write!(f, "{err}"), Self::Utf8(err) => write!(f, "{err}"), Self::Generic(err) => write!(f, "{err}"), - Self::GenericDyn(err) => write!(f, "{err}"), + Self::GenericDyn(err) | Self::RecipientNotInKeyRing(err) => write!(f, "{err}"), Self::PathError(err) => write!(f, "{err}"), Self::PatternError(err) => write!(f, "{err}"), Self::GlobError(err) => write!(f, "{err}"), Self::Utf8Error(err) => write!(f, "{err}"), - Self::RecipientNotInKeyRing(err) => write!(f, "{err}"), Self::ConfigError(err) => write!(f, "{err}"), Self::SerError(err) => write!(f, "{err}"), Self::ReqwestError(err) => write!(f, "{err}"), Self::AnyhowError(err) => write!(f, "{err}"), - Self::NoneError => write!(f, "NoneError"), Self::HexError(err) => write!(f, "{err}"), Self::FmtError(err) => write!(f, "{err}"), - Self::TotpUrlError(_err) => write!(f, "TOTP url error"), Self::SystemTimeError(err) => write!(f, "{err}"), + Self::NoneError => write!(f, "NoneError"), + Self::TotpUrlError(_err) => write!(f, "TOTP url error"), } } } @@ -212,6 +211,10 @@ impl std::fmt::Display for Error { /// Convenience type for Results pub type Result = std::result::Result; +/// Converts a `LocalResult` to a normal `Result`. +/// +/// # Errors +/// If the supplied `LocalResult` have no timezone or more than one timezone. pub fn to_result( res: chrono::LocalResult>, ) -> Result> { diff --git a/src/git.rs b/src/git.rs index b6c3d2dc..1ab775f3 100644 --- a/src/git.rs +++ b/src/git.rs @@ -31,6 +31,9 @@ fn git_branch_name(repo: &Repository) -> Result { } /// Apply the changes to the git repository. +/// +/// # Errors +/// If there is io problems. pub fn commit( repo: &Repository, signature: &git2::Signature, @@ -75,6 +78,10 @@ pub fn commit( } } +/// Gets the last commit of the repo. +/// +/// # Errors +/// If there is io problems or no commits. pub fn find_last_commit(repo: &Repository) -> Result> { let obj = repo.head()?.resolve()?.peel(git2::ObjectType::Commit)?; obj.into_commit() @@ -89,6 +96,9 @@ fn should_sign(repo: &Repository) -> bool { /// returns true if the diff between the two commits contains the path that the `DiffOptions` /// have been prepared with +/// +/// # Errors +/// If there is io problems. pub fn match_with_parent( repo: &Repository, commit: &git2::Commit, @@ -102,6 +112,9 @@ pub fn match_with_parent( } /// Add a file to the store, and commit it to the supplied git repository. +/// +/// # Errors +/// If there is io problems. pub fn add_and_commit_internal( repo: &Repository, paths: &[PathBuf], @@ -130,6 +143,9 @@ pub fn add_and_commit_internal( } /// Remove a file from the store, and commit the deletion to the supplied git repository. +/// +/// # Errors +/// If there is io problems. pub fn remove_and_commit(store: &PasswordStore, paths: &[PathBuf], message: &str) -> Result { let repo = store .repo() @@ -165,6 +181,9 @@ pub fn remove_and_commit(store: &PasswordStore, paths: &[PathBuf], message: &str } /// Move a file to a new place in the store, and commit the move to the supplied git repository. +/// +/// # Errors +/// If there is io problems. pub fn move_and_commit( store: &PasswordStore, old_name: &Path, @@ -259,9 +278,8 @@ pub fn push(store: &PasswordStore) -> Result<()> { let res = { let mut callbacks = git2::RemoteCallbacks::new(); let mut tried_ssh_key = false; - callbacks.credentials(|_url, username, allowed| { - cred(&mut tried_ssh_key, _url, username, allowed) - }); + callbacks + .credentials(|url, username, allowed| cred(&mut tried_ssh_key, url, username, allowed)); callbacks.push_update_reference(|_refname, status| { ref_status = status.map(std::borrow::ToOwned::to_owned); Ok(()) @@ -291,7 +309,7 @@ pub fn pull(store: &PasswordStore) -> Result<()> { let mut cb = git2::RemoteCallbacks::new(); let mut tried_ssh_key = false; - cb.credentials(|_url, username, allowed| cred(&mut tried_ssh_key, _url, username, allowed)); + cb.credentials(|url, username, allowed| cred(&mut tried_ssh_key, url, username, allowed)); let mut opts = git2::FetchOptions::new(); opts.remote_callbacks(cb); @@ -345,6 +363,10 @@ fn triple( ) } +/// Returns meta-data for a file in git. +/// +/// # Panics +/// Should not panic. pub fn read_git_meta_data( base: &Path, path: &Path, @@ -390,6 +412,10 @@ pub fn read_git_meta_data( (time_return, name_return, signature_return) } +/// Verifies a signature of a git commit. +/// +/// # Errors +/// If the signature can't be verified. pub fn verify_git_signature( repo: &Repository, id: &Oid, diff --git a/src/pass.rs b/src/pass.rs index b991b5ac..64596dee 100644 --- a/src/pass.rs +++ b/src/pass.rs @@ -220,34 +220,45 @@ impl PasswordStore { } /// Returns the name of the store, configured to the configuration file + #[must_use] pub fn get_name(&self) -> &String { &self.name } /// Returns a vec with the keys that are allowed to sign the .gpg-id file + #[must_use] pub fn get_valid_gpg_signing_keys(&self) -> &Vec { &self.valid_gpg_signing_keys } - /// returns the path to the directory where the store is located. + /// Returns the path to the directory where the store is located. + #[must_use] pub fn get_store_path(&self) -> PathBuf { self.root.clone() } + /// Returns the home of the user. + #[must_use] pub fn get_user_home(&self) -> Option { self.user_home.clone() } - /// returns the style file for the store + /// Returns the style file for the store, + #[must_use] pub fn get_style_file(&self) -> Option { self.style_file.clone() } - /// returns the crypto implementation for the store + /// Returns the crypto implementation for the store. + #[must_use] pub fn get_crypto(&self) -> &(dyn Crypto + Send) { &*self.crypto } + /// Gets the repo. + /// + /// # Errors + /// If creating a `git2` repository fails. pub fn repo(&self) -> Result { Ok(git2::Repository::open(&self.root)?) } @@ -259,13 +270,10 @@ impl PasswordStore { gpg_id_sig_file.push(".gpg-id.sig"); let gpg_id = fs::read(gpg_id_file)?; - let gpg_id_sig = match fs::read(gpg_id_sig_file) { - Ok(c) => c, - Err(_) => { - return Err(Error::Generic( - "problem reading .gpg-id.sig, and strict signature checking was asked for", - )); - } + let Ok(gpg_id_sig) = fs::read(gpg_id_sig_file) else { + return Err(Error::Generic( + "problem reading .gpg-id.sig, and strict signature checking was asked for", + )); }; match self @@ -312,13 +320,10 @@ impl PasswordStore { }; let gpg_id = fs::read(gpg_id_file)?; - let gpg_id_sig = match fs::read(gpg_id_sig_file) { - Ok(c) => c, - Err(_) => { - return Err(Error::Generic( - "problem reading .gpg-id.sig, and strict signature checking was asked for", - )); - } + let Ok(gpg_id_sig) = fs::read(gpg_id_sig_file) else { + return Err(Error::Generic( + "problem reading .gpg-id.sig, and strict signature checking was asked for", + )); }; match self @@ -448,6 +453,7 @@ impl PasswordStore { } /// checks if there is a username configured in git + #[must_use] pub fn has_configured_username(&self) -> bool { if self.repo().is_err() { return true; @@ -591,7 +597,7 @@ impl PasswordStore { /// Return a list of all the Recipients in the `$PASSWORD_STORE_DIR/.gpg-id` file. /// # Errors - /// Returns an `Err` if the gpg_id file should be verified and it can't be + /// Returns an `Err` if the `.gpg-id` file should be verified and it can't be pub fn all_recipients(&self) -> Result> { if !self.valid_gpg_signing_keys.is_empty() { self.verify_gpg_id_files()?; @@ -611,7 +617,7 @@ impl PasswordStore { /// Return a list of all the Recipients in the `.gpg-id` file that is the /// closest parent to `path`. /// # Errors - /// Returns an `Err` if the gpg_id file should be verified and it can't be + /// Returns an `Err` if the `.gpg-id` file should be verified and it can't be pub fn recipients_for_path(&self, path: &Path) -> Result> { if !self.valid_gpg_signing_keys.is_empty() { self.verify_gpg_id_file_for_path(path)?; @@ -673,9 +679,9 @@ impl PasswordStore { self.reencrypt_all_password_entries() } - /// Removes a key from the .gpg-id file and re-encrypts all the passwords + /// Removes a key from the `.gpg-id` file and re-encrypts all the passwords /// # Errors - /// Returns an `Err` if the gpg_id file can't be verified when it should + /// Returns an `Err` if the `.gpg-id` file can't be verified when it should /// or if the recipient is the last one. pub fn remove_recipient(&self, r: &Recipient, path: &Path) -> Result<()> { let gpg_id_file = &self.recipients_file_for_dir(path)?; @@ -689,9 +695,9 @@ impl PasswordStore { res } - /// Adds a key to the .gpg-id file in the path directory and re-encrypts all the passwords + /// Adds a key to the `.gpg-id` file in the path directory and re-encrypts all the passwords /// # Errors - /// Returns an `Err` if the gpg_id file can't be verified when it should or there is some problem with + /// Returns an `Err` if the `.gpg-id` file can't be verified when it should or there is some problem with /// the encryption. pub fn add_recipient(&mut self, r: &Recipient, path: &Path, config_path: &Path) -> Result<()> { if !self.crypto.is_key_in_keyring(r)? { @@ -729,7 +735,7 @@ impl PasswordStore { /// Reencrypt all the entries in the store, for example when a new collaborator is added /// to the team. /// # Errors - /// Returns an `Err` if the gpg_id file can't be verified when it should or there is some problem with + /// Returns an `Err` if the `.gpg-id` file can't be verified when it should or there is some problem with /// the encryption. fn reencrypt_all_password_entries(&self) -> Result<()> { let mut names: Vec = Vec::new(); @@ -861,7 +867,7 @@ impl PasswordStore { Ok(passwords.len() - 1) } - /// Creates a `Recipient` their key_id. + /// Creates a `Recipient` their `key_id`. /// # Errors /// Returns an `Err` if there is anything wrong with the `Recipient` pub fn recipient_from( @@ -878,7 +884,7 @@ impl PasswordStore { /// # Errors /// Returns an `Err` if there is a problem locking the mutex pub fn all_recipients_from_stores( - stores: Arc>>>>, + stores: &Arc>>>>, ) -> Result> { let all_recipients: Vec = { let mut ar: HashMap = HashMap::new(); @@ -918,6 +924,7 @@ pub struct GitLogLine { impl GitLogLine { /// creates a `GitLogLine` + #[must_use] pub fn new( message: String, commit_time: DateTime, @@ -976,6 +983,7 @@ fn to_name(relpath: &Path) -> String { impl PasswordEntry { /// constructs a `PasswordEntry` from the supplied parts + #[must_use] pub fn new( base: &Path, // Root of the password directory relpath: &Path, // Relative path to the password. @@ -995,6 +1003,7 @@ impl PasswordEntry { } /// Consumes an `PasswordEntry`, and returns a new one with a new name + #[must_use] pub fn with_new_name(old: Self, base: &Path, relpath: &Path) -> Self { Self { name: to_name(relpath), @@ -1007,6 +1016,10 @@ impl PasswordEntry { } /// creates a `PasswordEntry` by running git blame on the specified path + /// + /// # Panics + /// If `path` doesn't have `base` as a prefix. + #[must_use] pub fn load_from_git( base: &Path, path: &Path, @@ -1031,6 +1044,7 @@ impl PasswordEntry { } /// creates a `PasswordEntry` based on data in the filesystem + #[must_use] pub fn load_from_filesystem(base: &Path, relpath: &Path) -> Self { Self { name: to_name(relpath), @@ -1093,7 +1107,7 @@ impl PasswordEntry { } } - /// All calls to this function must be followed by secret.zeroize() + /// All calls to this function must be followed by `secret.zeroize()` fn update_internal(&self, secret: &str, store: &PasswordStore) -> Result<()> { if !store.valid_gpg_signing_keys.is_empty() { store.verify_gpg_id_files()?; @@ -1217,7 +1231,8 @@ impl PasswordEntry { } } -/// Import the key_ids from the signature file from a keyserver. +/// Import the `key_ids` from the signature file from a keyserver. +/// /// # Errors /// Returns an `Err` if the download fails pub fn pgp_pull(store: &mut PasswordStore, config_path: &Path) -> Result { @@ -1236,20 +1251,27 @@ pub fn pgp_import(store: &mut PasswordStore, text: &str, config_path: &Path) -> store.crypto.import_key(text, config_path) } +fn search_normalized(s: &str) -> String { + s.to_lowercase() +} +fn search_matches(s: &str, q: &str) -> bool { + search_normalized(s) + .as_str() + .contains(search_normalized(q).as_str()) +} + /// Return a list of all passwords whose name contains `query`. +#[must_use] pub fn search(store: &PasswordStore, query: &str) -> Vec { let passwords = &store.passwords; - fn normalized(s: &str) -> String { - s.to_lowercase() - } - fn matches(s: &str, q: &str) -> bool { - normalized(s).as_str().contains(normalized(q).as_str()) - } - let matching = passwords.iter().filter(|p| matches(&p.name, query)); + let matching = passwords.iter().filter(|p| search_matches(&p.name, query)); matching.cloned().collect() } -/// Determine password directory +/// Determine password directory. +/// +/// # Errors +/// If the home directory doesn't exist. pub fn password_dir( password_store_dir: &Option, home: &Option, @@ -1261,7 +1283,8 @@ pub fn password_dir( Ok(pass_home) } -/// Determine password directory +/// Determine password directory. +#[must_use] pub fn password_dir_raw(password_store_dir: &Option, home: &Option) -> PathBuf { // If a directory is provided via env var, use it match password_store_dir.as_ref() { @@ -1273,7 +1296,7 @@ pub fn password_dir_raw(password_store_dir: &Option, home: &Option, settings: &Config) -> bool { +fn home_exists(home: Option<&PathBuf>, settings: &Config) -> bool { if home.is_none() { return false; } @@ -1310,11 +1333,11 @@ fn home_exists(home: &Option, settings: &Config) -> bool { true } -fn env_var_exists(store_dir: &Option, signing_keys: &Option) -> bool { +fn env_var_exists(store_dir: Option<&str>, signing_keys: Option<&str>) -> bool { store_dir.is_some() || signing_keys.is_some() } -fn settings_file_exists(home: &Option, xdg_config_home: &Option) -> bool { +fn settings_file_exists(home: Option<&PathBuf>, xdg_config_home: Option<&PathBuf>) -> bool { if home.is_none() { return false; } @@ -1333,7 +1356,7 @@ fn settings_file_exists(home: &Option, xdg_config_home: &Option) -> Result { +fn home_settings(home: Option<&PathBuf>) -> Result { let mut default_store = HashMap::new(); let home = home.as_ref().ok_or("no home directory set")?; @@ -1352,18 +1375,18 @@ fn home_settings(home: &Option) -> Result { Ok(config::ConfigBuilder::::build(new_settings)?) } -fn var_settings(store_dir: &Option, signing_keys: &Option) -> Result { +fn var_settings(store_dir: Option<&str>, signing_keys: Option<&str>) -> Result { let mut default_store = HashMap::new(); if let Some(dir) = store_dir { if dir.ends_with('/') { - default_store.insert("path".to_owned(), dir.clone()); + default_store.insert("path".to_owned(), dir.to_owned()); } else { - default_store.insert("path".to_owned(), dir.clone() + "/"); + default_store.insert("path".to_owned(), format!("{dir}/")); } } if let Some(keys) = signing_keys { - default_store.insert("valid_signing_keys".to_owned(), keys.clone()); + default_store.insert("valid_signing_keys".to_owned(), keys.to_owned()); } else { default_store.insert("valid_signing_keys".to_owned(), "-1".to_owned()); } @@ -1378,10 +1401,10 @@ fn var_settings(store_dir: &Option, signing_keys: &Option) -> Re } fn xdg_config_file_location( - home: &Option, - xdg_config_home: &Option, + home: Option<&PathBuf>, + xdg_config_home: Option<&PathBuf>, ) -> Result { - match xdg_config_home.as_ref() { + match xdg_config_home { Some(p) => Ok(p.join("ripasso/settings.toml")), None => { if let Some(h) = home { @@ -1406,11 +1429,14 @@ fn append_extension(path: PathBuf, extension: &str) -> PathBuf { } /// reads ripassos config file, in `$XDG_CONFIG_HOME/ripasso/settings.toml` +/// +/// # Errors +/// Fails if there is io problems. pub fn read_config( - store_dir: &Option, - signing_keys: &Option, - home: &Option, - xdg_config_home: &Option, + store_dir: Option<&str>, + signing_keys: Option<&str>, + home: Option<&PathBuf>, + xdg_config_home: Option<&PathBuf>, ) -> Result<(Config, PathBuf)> { let mut settings = config::ConfigBuilder::default(); let config_file_location = xdg_config_file_location(home, xdg_config_home)?; @@ -1419,7 +1445,7 @@ pub fn read_config( settings = config::ConfigBuilder::::add_source( settings, file_settings(&config_file_location), - ) + ); } if home_exists(home, &settings.clone().build()?) { @@ -1433,15 +1459,18 @@ pub fn read_config( Ok((settings.build()?, config_file_location)) } +/// Save the config of the supplied stores into a file at `config_file_location`. +/// +/// # Errors +/// Fails if there is io problems. pub fn save_config( - stores: Arc>>>>, + stores: &Arc>>>>, config_file_location: &Path, ) -> Result<()> { - let mut stores_map = HashMap::new(); + let mut all_stores_map = HashMap::new(); let stores_borrowed = stores .lock() .map_err(|_e| Error::Generic("problem locking the mutex"))?; - #[allow(clippy::significant_drop_in_scrutinee)] for store in stores_borrowed.iter() { let store = store .lock() @@ -1449,11 +1478,7 @@ pub fn save_config( let mut store_map = HashMap::new(); store_map.insert( "path", - store - .get_store_path() - .to_string_lossy() - .into_owned() - .to_string(), + store.get_store_path().to_string_lossy().into_owned(), ); if !store.get_valid_gpg_signing_keys().is_empty() { store_map.insert( @@ -1478,11 +1503,11 @@ pub fn save_config( if let Some(fp) = store.crypto.own_fingerprint() { store_map.insert("own_fingerprint", hex::encode_upper(fp)); } - stores_map.insert(store.get_name().clone(), store_map); + all_stores_map.insert(store.get_name().clone(), store_map); } let mut settings = HashMap::new(); - settings.insert("stores", stores_map); + settings.insert("stores", all_stores_map); let f = File::create(config_file_location)?; let mut f = std::io::BufWriter::new(f); diff --git a/src/passphrase_generator.rs b/src/passphrase_generator.rs index 562df810..b9f4b55e 100644 --- a/src/passphrase_generator.rs +++ b/src/passphrase_generator.rs @@ -1,29 +1,33 @@ -use std::io; - +use crate::error::Result; +use crate::pass::Error; use rand::prelude::IndexedRandom; static WORDLIST: &str = include_str!("wordlists/eff_large.wordlist"); -pub fn passphrase_generator(wordcount: i32) -> io::Result> { +/// Returns a pass phrase consisting of `word_count` number of +///words from the large wordlist from EFF. +/// +/// # Errors +/// Fails if the loading of the wordlist fails. +pub fn passphrase_generator(word_count: usize) -> Result> { let words: Vec = WORDLIST .lines() - .map(|line| line.trim()) + .map(str::trim) .filter(|line| !line.is_empty()) .map(String::from) .collect(); if words.is_empty() { - eprintln!("The word list is empty!"); - return Ok(Vec::new()); + return Err(Error::Generic("empty wordlist")); } let mut rng = rand::rng(); - let selected = if words.len() <= wordcount as usize { + let selected = if words.len() <= word_count { words.clone() } else { words - .choose_multiple(&mut rng, wordcount as usize) + .choose_multiple(&mut rng, word_count) .cloned() .collect() }; diff --git a/src/password_generator.rs b/src/password_generator.rs index 399eba95..1cd7f763 100644 --- a/src/password_generator.rs +++ b/src/password_generator.rs @@ -1,22 +1,34 @@ use rand::Rng; -pub fn password_generator(length: usize, category: usize) -> String { +#[non_exhaustive] +#[derive(Debug, Copy, Clone)] +pub enum PasswordGenerationCategory { + AsciiOnly, + AsciiExtended, +} + +/// generates a password with the specified `length`. +/// +/// # Panics +/// If the random function returns a value outside the +/// specified range, this can't happen. +#[must_use] +pub fn password_generator(length: usize, category: PasswordGenerationCategory) -> String { let mut rng = rand::rng(); - if category == 0 { - (0..length) + match category { + PasswordGenerationCategory::AsciiOnly => (0..length) .map(|_| { let ascii_val = rng.random_range(33..=126); - ascii_val as u8 as char + char::from(u8::try_from(ascii_val).expect("Invalid character")) }) - .collect() - } else { - (0..length) + .collect(), + PasswordGenerationCategory::AsciiExtended => (0..length) .map(|_| { let ascii_val = rng.random_range(33..=255); - ascii_val as u8 as char + char::from(u8::try_from(ascii_val).expect("Invalid character")) }) - .collect() + .collect(), } } diff --git a/src/signature.rs b/src/signature.rs index 28fccf2f..c8caa88a 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -7,7 +7,7 @@ use std::{ }; use crate::crypto::{FindSigningFingerprintStrategy, Fingerprint}; -pub use crate::error::{Error, Result}; +use crate::error::{Error, Result}; /// A git commit for a password might be signed by a gpg key, and this signature's verification /// state is one of these values. @@ -37,36 +37,39 @@ impl From for SignatureStatus { /// Turns an optional string into a vec of parsed gpg fingerprints in the form of strings. /// If any of the fingerprints isn't a full 40 chars or if they haven't been imported to /// the gpg keyring yet, this function instead returns an error. +/// +/// # Errors +/// Fails if the signing keys can't be parsed. pub fn parse_signing_keys( password_store_signing_key: &Option, crypto: &(dyn crate::crypto::Crypto + Send), ) -> Result> { - if password_store_signing_key.is_none() { - return Ok(vec![]); - } - - let mut signing_keys = vec![]; - for key in password_store_signing_key.as_ref().unwrap().split(',') { - let trimmed = key.trim().to_owned(); - let len = trimmed.len(); - let have_0x = trimmed.starts_with("0x"); + if let Some(password_store_signing_key) = password_store_signing_key { + let mut signing_keys = vec![]; + for key in password_store_signing_key.split(',') { + let trimmed = key.trim().to_owned(); + let len = trimmed.len(); + let have_0x = trimmed.starts_with("0x"); + + if !(len == 40 || len == 64 || len == 42 && have_0x || len == 66 && have_0x) { + return Err(Error::Generic( + "signing key isn't in full 40/64 hex character fingerprint format", + )); + } - if !(len == 40 || len == 64 || len == 42 && have_0x || len == 66 && have_0x) { - return Err(Error::Generic( - "signing key isn't in full 40/64 hex character fingerprint format", - )); - } + let key_res = crypto.get_key(&trimmed); + if let Some(err) = key_res.err() { + return Err(Error::GenericDyn(format!( + "signing key not found in keyring, error: {err}", + ))); + } - let key_res = crypto.get_key(&trimmed); - if let Some(err) = key_res.err() { - return Err(Error::GenericDyn(format!( - "signing key not found in keyring, error: {err}", - ))); + signing_keys.push(trimmed.as_str().try_into()?); } - - signing_keys.push(trimmed.as_str().try_into()?); + Ok(signing_keys) + } else { + Ok(vec![]) } - Ok(signing_keys) } /// the GPG trust level for a key @@ -159,7 +162,7 @@ pub struct Comment { /// Represents one person on the team. /// -/// All secrets are encrypted with the key_id of the recipients. +/// All secrets are encrypted with the `key_id` of the recipients. #[derive(Clone, Debug)] pub struct Recipient { /// Human-readable name of the person. @@ -238,10 +241,7 @@ impl Recipient { let mut names = real_key.user_id_names(); - let name = match names.len() { - 0 => "?".to_owned(), - _ => names.pop().unwrap(), - }; + let name = names.pop().unwrap_or("?".to_owned()); let trusts: HashMap = crypto.get_all_trust_items()?; @@ -263,7 +263,7 @@ impl Recipient { /// Return a list of all the Recipients in the supplied file. /// # Errors - /// Returns an `Err` if there is a problem reading the .gpg_id file + /// Returns an `Err` if there is a problem reading the `.gpg_id` file pub fn all_recipients( recipients_file: &Path, crypto: &(dyn crate::crypto::Crypto + Send), @@ -279,15 +279,17 @@ impl Recipient { comment_buf.push(key.chars().skip(1).collect()); } else if key.contains('#') { let mut splitter = key.splitn(2, '#'); - let key = splitter.next().unwrap().trim(); - let comment = splitter.next().unwrap(); - - unique_recipients_keys.insert(IdComment { - id: key.to_owned(), - pre_comment: comment_buf.clone(), - post_comment: Some(comment.to_owned()), - }); - comment_buf.clear(); + if let Some(key) = splitter.next() { + let key = key.trim(); + if let Some(comment) = splitter.next() { + unique_recipients_keys.insert(IdComment { + id: key.to_owned(), + pre_comment: comment_buf.clone(), + post_comment: Some(comment.to_owned()), + }); + comment_buf.clear(); + } + } } else { unique_recipients_keys.insert(IdComment { id: key.to_owned(), @@ -323,7 +325,7 @@ impl Recipient { ) } }; - recipients.push(recipient) + recipients.push(recipient); } Ok(recipients) @@ -353,8 +355,8 @@ impl Recipient { None => recipient.key_id, }; - if recipient.comment.pre_comment.is_some() { - for line in recipient.comment.pre_comment.as_ref().unwrap().split('\n') { + if let Some(pre_comment) = recipient.comment.pre_comment.as_ref() { + for line in pre_comment.split('\n') { file_content.push('#'); file_content.push_str(line); file_content.push('\n'); @@ -366,9 +368,9 @@ impl Recipient { } file_content.push_str(&to_add); - if recipient.comment.post_comment.is_some() { + if let Some(post_comment) = recipient.comment.post_comment.as_ref() { file_content.push_str(" #"); - file_content.push_str(recipient.comment.post_comment.as_ref().unwrap()); + file_content.push_str(post_comment); } file_content.push('\n'); } @@ -402,7 +404,7 @@ impl Recipient { /// Delete one of the persons from the list of team members to encrypt the passwords for. /// # Errors - /// Return an `Err` if there is an error reading the gpg_id file + /// Return an `Err` if there is an error reading the `gpg_id` file pub fn remove_recipient_from_file( s: &Self, recipients_file: &Path, @@ -438,7 +440,7 @@ impl Recipient { /// Add a new person to the list of team members to encrypt the passwords for. /// # Errors - /// Return an `Err` if there is an error reading the gpg_id file + /// Return an `Err` if there is an error reading the `gpg_id` file pub fn add_recipient_to_file( recipient: &Self, recipients_file: &Path, diff --git a/src/tests/crypto.rs b/src/tests/crypto.rs index 99615bd1..4ac741fe 100644 --- a/src/tests/crypto.rs +++ b/src/tests/crypto.rs @@ -392,14 +392,14 @@ fn cert_v6() -> Arc { Arc::new(cert) } -fn fingerprint_from_cert(cert: Arc) -> [u8; 32] { +fn fingerprint_from_cert(cert: &Arc) -> [u8; 32] { <[u8; 32]>::try_from(cert.fingerprint().as_bytes()).unwrap() } #[test] fn test_fingerprint_from_cert() { let cert = cert_v6(); - assert_eq!(32, fingerprint_from_cert(cert).iter().len()); + assert_eq!(32, fingerprint_from_cert(&cert).iter().len()); } #[test] diff --git a/src/tests/pass.rs b/src/tests/pass.rs index 67fb1828..6dbd5bfb 100644 --- a/src/tests/pass.rs +++ b/src/tests/pass.rs @@ -20,7 +20,7 @@ use crate::test_helpers::{ impl PartialEq for Error { fn eq(&self, other: &Error) -> bool { - format!("{:?}", self) == format!("{:?}", *other) + format!("{self:?}") == format!("{:?}", *other) } } @@ -334,13 +334,13 @@ fn password_store_with_symlink() -> Result<()> { #[test] fn home_exists_missing_home_env() { - assert!(!home_exists(&None, &Config::default())); + assert!(!home_exists(None, &Config::default())); } #[test] fn home_exists_home_dir_without_config_dir() { let dir = tempdir().unwrap(); - let result = home_exists(&Some(dir.keep()), &Config::default()); + let result = home_exists(Some(&dir.keep()), &Config::default()); assert!(!result); } @@ -349,7 +349,7 @@ fn home_exists_home_dir_without_config_dir() { fn home_exists_home_dir_with_file_instead_of_dir() -> Result<()> { let dir = tempdir()?; File::create(dir.path().join(".password-store"))?; - let result = home_exists(&Some(dir.keep()), &Config::default()); + let result = home_exists(Some(&dir.keep()), &Config::default()); assert!(!result); @@ -360,7 +360,7 @@ fn home_exists_home_dir_with_file_instead_of_dir() -> Result<()> { fn home_exists_home_dir_with_config_dir() -> Result<()> { let dir = tempdir()?; fs::create_dir(dir.path().join(".password-store"))?; - let result = home_exists(&Some(dir.keep()), &Config::default()); + let result = home_exists(Some(&dir.keep()), &Config::default()); assert!(result); @@ -369,7 +369,7 @@ fn home_exists_home_dir_with_config_dir() -> Result<()> { #[test] fn env_var_exists_test_none() { - assert!(!env_var_exists(&None, &None)); + assert!(!env_var_exists(None, None)); } #[test] @@ -377,14 +377,8 @@ fn env_var_exists_test_without_dir() { let dir = tempdir().unwrap(); assert!(env_var_exists( - &Some( - dir.path() - .join(".password-store") - .to_str() - .unwrap() - .to_owned() - ), - &None + Some(dir.path().join(".password-store").to_str().unwrap()), + None )); } @@ -392,17 +386,14 @@ fn env_var_exists_test_without_dir() { fn env_var_exists_test_with_dir() { let dir = tempdir().unwrap(); - assert!(env_var_exists( - &Some(dir.path().to_str().unwrap().to_owned()), - &None - )); + assert!(env_var_exists(Some(dir.path().to_str().unwrap()), None)); } #[test] fn home_settings_missing() { assert_eq!( Error::GenericDyn("no home directory set".to_owned()), - home_settings(&None).err().unwrap() + home_settings(None).err().unwrap() ); } @@ -411,20 +402,13 @@ fn home_settings_dir_exists() -> Result<()> { let dir = tempdir()?; fs::create_dir(dir.path().join(".password-store"))?; - let settings = home_settings(&Some(PathBuf::from(dir.path())))?; + let settings = home_settings(Some(&PathBuf::from(dir.path())))?; let stores = settings.get_table("stores")?; let work = stores["default"].clone().into_table()?; let path = work["path"].clone().into_string()?; - assert_eq!( - dir.path() - .join(".password-store/") - .to_str() - .unwrap() - .to_owned(), - path - ); + assert_eq!(dir.path().join(".password-store/").to_str().unwrap(), path); Ok(()) } @@ -434,20 +418,13 @@ fn home_settings_dir_exists() -> Result<()> { fn home_settings_dir_doesnt_exists() -> Result<()> { let dir = tempdir()?; - let settings = home_settings(&Some(PathBuf::from(dir.path())))?; + let settings = home_settings(Some(&PathBuf::from(dir.path())))?; let stores = settings.get_table("stores")?; let work = stores["default"].clone().into_table()?; let path = work["path"].clone().into_string()?; - assert_eq!( - dir.path() - .join(".password-store/") - .to_str() - .unwrap() - .to_owned(), - path - ); + assert_eq!(dir.path().join(".password-store/").to_str().unwrap(), path); Ok(()) } @@ -455,8 +432,8 @@ fn home_settings_dir_doesnt_exists() -> Result<()> { #[test] fn var_settings_test() -> Result<()> { let settings = var_settings( - &Some("/home/user/.password-store".to_owned()), - &Some("E6A7D758338EC2EF2A8A9F4EE7E3DB4B3217482F".to_owned()), + Some("/home/user/.password-store"), + Some("E6A7D758338EC2EF2A8A9F4EE7E3DB4B3217482F"), )?; let stores = settings.get_table("stores")?; @@ -493,7 +470,7 @@ fn file_settings_simple_file() -> Result<()> { let mut settings = ConfigBuilder::default(); settings = config::ConfigBuilder::::add_source( settings, - file_settings(&xdg_config_file_location(&Some(dir.keep()), &None)?), + file_settings(&xdg_config_file_location(Some(&dir.keep()), None)?), ); let settings = settings.build()?; @@ -528,8 +505,8 @@ fn file_settings_file_in_xdg_config_home() -> Result<()> { settings = config::ConfigBuilder::::add_source( settings, file_settings(&xdg_config_file_location( - &Some(dir.keep()), - &Some(dir2.path().join(".random_config")), + Some(&dir.keep()), + Some(&dir2.path().join(".random_config")), )?), ); let settings = settings.build()?; @@ -555,20 +532,13 @@ fn read_config_empty_config_file() -> Result<()> { .join("settings.toml"), )?; - let (settings, _) = read_config(&None, &None, &Some(PathBuf::from(dir.path())), &None)?; + let (settings, _) = read_config(None, None, Some(&PathBuf::from(dir.path())), None)?; let stores = settings.get_table("stores")?; let work = stores["default"].clone().into_table()?; let path = work["path"].clone().into_string()?; - assert_eq!( - dir.path() - .join(".password-store/") - .to_str() - .unwrap() - .to_owned(), - path - ); + assert_eq!(dir.path().join(".password-store/").to_str().unwrap(), path); Ok(()) } @@ -579,10 +549,10 @@ fn read_config_empty_config_file_with_keys_env() -> Result<()> { create_dir_all(dir.path().join(".password-store"))?; let (settings, _) = read_config( - &None, - &Some("E6A7D758338EC2EF2A8A9F4EE7E3DB4B3217482F".to_owned()), - &Some(PathBuf::from(dir.path())), - &None, + None, + Some("E6A7D758338EC2EF2A8A9F4EE7E3DB4B3217482F"), + Some(&PathBuf::from(dir.path())), + None, )?; let stores = settings.get_table("stores")?; @@ -590,14 +560,7 @@ fn read_config_empty_config_file_with_keys_env() -> Result<()> { let path = work["path"].clone().into_string()?; let valid_signing_keys = work["valid_signing_keys"].clone().into_string()?; - assert_eq!( - dir.path() - .join(".password-store/") - .to_str() - .unwrap() - .to_owned(), - path - ); + assert_eq!(dir.path().join(".password-store/").to_str().unwrap(), path); assert_eq!( "E6A7D758338EC2EF2A8A9F4EE7E3DB4B3217482F", valid_signing_keys @@ -612,17 +575,16 @@ fn read_config_env_vars() -> Result<()> { create_dir_all(dir.path().join("env_var").join(".password-store"))?; let (settings, _) = read_config( - &Some( + Some( dir.path() .join("env_var") .join(".password-store") .to_str() - .unwrap() - .to_owned(), + .unwrap(), ), - &Some("E6A7D758338EC2EF2A8A9F4EE7E3DB4B3217482F".to_owned()), - &Some(PathBuf::from(dir.path())), - &None, + Some("E6A7D758338EC2EF2A8A9F4EE7E3DB4B3217482F"), + Some(&PathBuf::from(dir.path())), + None, )?; let stores = settings.get_table("stores")?; @@ -635,8 +597,7 @@ fn read_config_env_vars() -> Result<()> { .join("env_var") .join(".password-store/") .to_str() - .unwrap() - .to_owned(), + .unwrap(), path ); assert_eq!( @@ -654,17 +615,16 @@ fn read_config_home_and_env_vars() -> Result<()> { create_dir_all(dir.path().join("env_var").join(".password-store"))?; let (settings, _) = read_config( - &Some( + Some( dir.path() .join("env_var") .join(".password-store") .to_str() - .unwrap() - .to_owned(), + .unwrap(), ), - &Some("E6A7D758338EC2EF2A8A9F4EE7E3DB4B3217482F".to_owned()), - &Some(PathBuf::from(dir.path())), - &None, + Some("E6A7D758338EC2EF2A8A9F4EE7E3DB4B3217482F"), + Some(&PathBuf::from(dir.path())), + None, )?; let stores = settings.get_table("stores")?; @@ -677,8 +637,7 @@ fn read_config_home_and_env_vars() -> Result<()> { .join("env_var") .join(".password-store/") .to_str() - .unwrap() - .to_owned(), + .unwrap(), path ); assert_eq!( @@ -712,7 +671,7 @@ fn read_config_default_path_in_config_file() -> Result<()> { )?; file.flush()?; - let (settings, _) = read_config(&None, &None, &Some(PathBuf::from(dir.path())), &None)?; + let (settings, _) = read_config(None, None, Some(&PathBuf::from(dir.path())), None)?; let stores = settings.get_table("stores")?; @@ -747,7 +706,7 @@ fn read_config_default_path_in_env_var() -> Result<()> { )?; file.flush()?; - let (settings, _) = read_config(&Some("/tmp/t2".to_owned()), &None, &Some(dir.keep()), &None)?; + let (settings, _) = read_config(Some("/tmp/t2"), None, Some(&dir.keep()), None)?; let stores = settings.get_table("stores")?; @@ -784,7 +743,7 @@ fn read_config_default_path_in_env_var_with_pgp_setting() -> Result<()> { )?; file.flush()?; - let (settings, _) = read_config(&Some("/tmp/t2".to_owned()), &None, &Some(dir.keep()), &None)?; + let (settings, _) = read_config(Some("/tmp/t2"), None, Some(&dir.keep()), None)?; let stores = settings.get_table("stores")?; @@ -822,7 +781,7 @@ fn save_config_one_store() { .unwrap(); save_config( - Arc::new(Mutex::new(vec![Arc::new(Mutex::new(s1))])), + &Arc::new(Mutex::new(vec![Arc::new(Mutex::new(s1))])), config_file.path(), ) .unwrap(); @@ -855,7 +814,7 @@ fn save_config_one_store_with_pgp_impl() { .unwrap(); save_config( - Arc::new(Mutex::new(vec![Arc::new(Mutex::new(store))])), + &Arc::new(Mutex::new(vec![Arc::new(Mutex::new(store))])), &dir.path().join("file.toml"), ) .unwrap(); @@ -885,7 +844,7 @@ fn save_config_one_store_with_fingerprint() { .unwrap(); save_config( - Arc::new(Mutex::new(vec![Arc::new(Mutex::new(store))])), + &Arc::new(Mutex::new(vec![Arc::new(Mutex::new(store))])), &dir.path().join("file.toml"), ) .unwrap(); @@ -1188,7 +1147,7 @@ fn decrypt_password_multiline() -> Result<()> { Ok(()) } -fn mfa_setup(payload: String) -> Result<(tempfile::TempDir, PasswordEntry, PasswordStore)> { +fn mfa_setup(payload: &str) -> Result<(tempfile::TempDir, PasswordEntry, PasswordStore)> { let dir = tempdir()?; create_dir_all(dir.path().join(".password-store"))?; let mut gpg_file = File::create(dir.path().join(".password-store").join(".gpg-id"))?; @@ -1208,7 +1167,7 @@ fn mfa_setup(payload: String) -> Result<(tempfile::TempDir, PasswordEntry, Passw RepositoryStatus::NoRepo, ); - let crypto = MockCrypto::new().with_decrypt_string_return(&payload); + let crypto = MockCrypto::new().with_decrypt_string_return(payload); let store = PasswordStore { name: "store_name".to_owned(), @@ -1225,43 +1184,49 @@ fn mfa_setup(payload: String) -> Result<(tempfile::TempDir, PasswordEntry, Passw #[test] fn mfa_example1() -> Result<()> { - let (_dir, pe, store) = mfa_setup("otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXPAAAAAAAAAAAA&issuer=Example".to_owned())?; + let (_dir, pe, store) = mfa_setup( + "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXPAAAAAAAAAAAA&issuer=Example", + )?; let res = pe.mfa(&store)?; assert_eq!(6, res.len()); - assert_eq!(6, res.chars().filter(|c| c.is_ascii_digit()).count()); + assert_eq!(6, res.chars().filter(char::is_ascii_digit).count()); Ok(()) } #[test] fn mfa_example2() -> Result<()> { - let (_dir, pe, store) = mfa_setup("some text\n otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXPAAAAAAAAAAAA&issuer=Example\nmore txt\n\n".to_owned())?; + let (_dir, pe, store) = mfa_setup( + "some text\n otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXPAAAAAAAAAAAA&issuer=Example\nmore txt\n\n", + )?; let res = pe.mfa(&store)?; assert_eq!(6, res.len()); - assert_eq!(6, res.chars().filter(|c| c.is_ascii_digit()).count()); + assert_eq!(6, res.chars().filter(char::is_ascii_digit).count()); Ok(()) } #[test] fn mfa_example3() -> Result<()> { - let (_dir, pe, store) = mfa_setup("lots and lots and lots and lots and lots and lots and lots and lots and lots and lots of text\n otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXPAAAAAAAAAAAA&issuer=Example\nmore txt\n\n".to_owned())?; + let (_dir, pe, store) = mfa_setup( + "lots and lots and lots and lots and lots and lots and lots and lots and lots and lots of text\n otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXPAAAAAAAAAAAA&issuer=Example\nmore txt\n\n", + )?; let res = pe.mfa(&store)?; assert_eq!(6, res.len()); - assert_eq!(6, res.chars().filter(|c| c.is_ascii_digit()).count()); + assert_eq!(6, res.chars().filter(char::is_ascii_digit).count()); Ok(()) } #[test] fn mfa_no_otpauth_url() -> Result<()> { - let (_dir, pe, store) = mfa_setup("password".to_owned())?; + let (_dir, pe, store) = mfa_setup("password")?; let res = pe.mfa(&store); @@ -1592,10 +1557,10 @@ fn test_move_and_commit_signed() -> Result<()> { } #[test] -fn test_search() -> Result<()> { +fn test_search() { let p1 = PasswordEntry { name: "no/match/check".to_owned(), - path: Default::default(), + path: PathBuf::default(), updated: None, committed_by: None, signature_status: None, @@ -1603,7 +1568,7 @@ fn test_search() -> Result<()> { }; let p2 = PasswordEntry { name: "dir/test/middle".to_owned(), - path: Default::default(), + path: PathBuf::default(), updated: None, committed_by: None, signature_status: None, @@ -1611,7 +1576,7 @@ fn test_search() -> Result<()> { }; let p3 = PasswordEntry { name: " space test ".to_owned(), - path: Default::default(), + path: PathBuf::default(), updated: None, committed_by: None, signature_status: None, @@ -1633,8 +1598,6 @@ fn test_search() -> Result<()> { assert_eq!(2, result.len()); assert_eq!("dir/test/middle", result[0].name); assert_eq!(" space test ", result[1].name); - - Ok(()) } #[test] @@ -1895,7 +1858,7 @@ fn test_verify_gpg_id_files_untrusted_key_in_keyring() { let store = PasswordStore { name: "store_name".to_owned(), - root: password_store_dir.to_path_buf(), + root: password_store_dir.clone(), valid_gpg_signing_keys: vec![sofp], passwords: [].to_vec(), style_file: None, @@ -2545,7 +2508,7 @@ fn all_recipients_from_stores_plain() -> Result<()> { user_home: None, }; - let result = all_recipients_from_stores(Arc::new(Mutex::new(vec![Arc::new(Mutex::new(s1))])))?; + let result = all_recipients_from_stores(&Arc::new(Mutex::new(vec![Arc::new(Mutex::new(s1))])))?; assert_eq!(1, result.len()); assert_eq!("7E068070D5EF794B00C8A9D91D108E6C07CBC406", result[0].key_id); diff --git a/src/tests/password_generator.rs b/src/tests/password_generator.rs index a079be1a..523a091b 100644 --- a/src/tests/password_generator.rs +++ b/src/tests/password_generator.rs @@ -1,9 +1,9 @@ -use crate::password_generator::password_generator; +use crate::password_generator::{PasswordGenerationCategory, password_generator}; #[test] fn password_length_varies_correctly() { for len in [8, 12, 20] { - let pass = password_generator(len, 0); + let pass = password_generator(len, PasswordGenerationCategory::AsciiOnly); assert_eq!( pass.len(), len, diff --git a/src/tests/test_helpers.rs b/src/tests/test_helpers.rs index e7830cf5..b26747d4 100644 --- a/src/tests/test_helpers.rs +++ b/src/tests/test_helpers.rs @@ -40,6 +40,10 @@ impl Drop for UnpackedDir { } impl UnpackedDir { + /// Unpacks a `tar.gz` file and returns a reference to the directory. + /// + /// # Errors + /// On io errors and similar. pub fn new(name: &str) -> Result { let base_path: PathBuf = get_testres_path(); @@ -55,10 +59,12 @@ impl UnpackedDir { }) } + #[must_use] pub fn path(&self) -> &Path { self.dir.as_path() } + #[must_use] pub fn dir(&self) -> PathBuf { self.dir.clone() } @@ -102,19 +108,22 @@ impl Default for MockKey { } impl MockKey { + #[must_use] pub fn new() -> MockKey { MockKey { - fingerprint: Fingerprint::V4( - <[u8; 20]>::from_hex("7E068070D5EF794B00C8A9D91D108E6C07CBC406").unwrap(), - ), + fingerprint: Fingerprint::V4([ + 0x7E, 0x06, 0x80, 0x70, 0xD5, 0xEF, 0x79, 0x4B, 0x00, 0xC8, 0xA9, 0xD9, 0x1D, 0x10, + 0x8E, 0x6C, 0x07, 0xCB, 0xC4, 0x06, + ]), user_id_names: vec!["Alexander Kjäll ".to_owned()], } } + #[must_use] pub fn from_args(fingerprint: Fingerprint, user_id_names: Vec) -> MockKey { MockKey { - user_id_names, fingerprint, + user_id_names, } } } @@ -140,6 +149,7 @@ impl Default for MockCrypto { } impl MockCrypto { + #[must_use] pub fn new() -> MockCrypto { MockCrypto { decrypt_called: RefCell::new(false), @@ -155,36 +165,42 @@ impl MockCrypto { } } + #[must_use] pub fn with_encrypt_string_return(mut self, data: Vec) -> MockCrypto { self.encrypt_string_return = data; self } + #[must_use] pub fn with_decrypt_string_return(mut self, data: &str) -> MockCrypto { self.decrypt_string_return = Some(data.to_string()); self } + #[must_use] pub fn with_encrypt_error(mut self, err_str: &str) -> MockCrypto { self.encrypt_string_error = Some(err_str.to_string()); self } + #[must_use] pub fn with_get_key_error(mut self, err_str: &str) -> MockCrypto { self.get_key_string_error = Some(err_str.to_string()); self } + #[must_use] pub fn with_get_key_result(mut self, key_id: &str, key: MockKey) -> MockCrypto { self.get_key_answers.insert(key_id.to_string(), key); self } + #[must_use] pub fn with_sign_string_return(mut self, sign_str: &str) -> MockCrypto { self.sign_string_return = Some(sign_str.to_string()); @@ -273,6 +289,7 @@ impl Crypto for MockCrypto { } } +#[must_use] pub fn recipient_alex() -> Recipient { Recipient { name: "Alexander Kjäll ".to_owned(), @@ -281,14 +298,17 @@ pub fn recipient_alex() -> Recipient { post_comment: None, }, key_id: "1D108E6C07CBC406".to_owned(), - fingerprint: Some(Fingerprint::V4( - <[u8; 20]>::from_hex("7E068070D5EF794B00C8A9D91D108E6C07CBC406").unwrap(), - )), + fingerprint: Some(Fingerprint::V4([ + 0x7E, 0x06, 0x80, 0x70, 0xD5, 0xEF, 0x79, 0x4B, 0x00, 0xC8, 0xA9, 0xD9, 0x1D, 0x10, + 0x8E, 0x6C, 0x07, 0xCB, 0xC4, 0x06, + ])), key_ring_status: KeyRingStatus::InKeyRing, trust_level: OwnerTrustLevel::Ultimate, not_usable: false, } } + +#[must_use] pub fn recipient_alex_old() -> Recipient { Recipient { name: "Alexander Kjäll ".to_owned(), @@ -297,14 +317,20 @@ pub fn recipient_alex_old() -> Recipient { post_comment: None, }, key_id: "DF0C3D316B7312D5".to_owned(), - fingerprint: Some(Fingerprint::V4( - <[u8; 20]>::from_hex("DB07DAC5B3882EAB659E1D2FDF0C3D316B7312D5").unwrap(), - )), + fingerprint: Some(Fingerprint::V4([ + 0xDB, 0x07, 0xDA, 0xC5, 0xB3, 0x88, 0x2E, 0xAB, 0x65, 0x9E, 0x1D, 0x2F, 0xDF, 0x0C, + 0x3D, 0x31, 0x6B, 0x73, 0x12, 0xD5, + ])), key_ring_status: KeyRingStatus::InKeyRing, trust_level: OwnerTrustLevel::Ultimate, not_usable: false, } } + +/// Generate a Recipient object from a cert +/// +/// # Panics +/// If the generation fails. pub fn recipient_from_cert(cert: &Cert) -> Recipient { Recipient { name: String::from_utf8(cert.userids().next().unwrap().userid().value().to_vec()).unwrap(), @@ -322,6 +348,7 @@ pub fn recipient_from_cert(cert: &Cert) -> Recipient { } } +#[must_use] pub fn append_file_name(file: &Path) -> PathBuf { let rf = file.to_path_buf(); let mut sig = rf.into_os_string(); @@ -329,6 +356,11 @@ pub fn append_file_name(file: &Path) -> PathBuf { sig.into() } +/// Generates a pgp cert. +/// +/// # Panics +/// If the generation fails. +#[must_use] pub fn generate_sequoia_cert(email: &str) -> Cert { let (cert, _) = CertBuilder::general_purpose([UserID::from(email)]) .generate() @@ -337,6 +369,11 @@ pub fn generate_sequoia_cert(email: &str) -> Cert { cert } +/// Generates a pgp cert. +/// +/// # Panics +/// If the generation fails. +#[must_use] pub fn generate_sequoia_cert_without_private_key(email: &str) -> Cert { let (cert, _) = CertBuilder::general_purpose([UserID::from(email)]) .generate() @@ -380,6 +417,11 @@ impl DecryptionHelper for &mut KeyLister { } } +/// counts the number of recipients of a pgp packet. +/// +/// # Panics +/// if a `DecryptorBuilder` can't be built. +#[must_use] pub fn count_recipients(data: &[u8]) -> usize { let p = StandardPolicy::new(); let mut h = KeyLister { ids: vec![] }; From 3ac4b8d34181c96baf50657d2d06e59e127fb6e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Kj=C3=A4ll?= Date: Sun, 4 Jan 2026 15:27:37 +0100 Subject: [PATCH 2/5] enable pedantic clippy lints for ripasso-cursive --- Cargo.lock | 77 +-- Cargo.toml | 2 +- benches/library_benchmark.rs | 10 +- cursive/Cargo.toml | 6 +- cursive/build.rs | 12 +- cursive/src/helpers.rs | 8 +- cursive/src/main.rs | 993 ++++++++++++++++++++--------------- cursive/src/tests/main.rs | 28 +- cursive/src/wizard.rs | 91 ++-- gtk/src/window/mod.rs | 30 +- src/crypto.rs | 2 +- src/git.rs | 3 +- src/pass.rs | 44 +- src/signature.rs | 2 +- src/tests/pass.rs | 163 +++--- src/tests/signature.rs | 25 +- 16 files changed, 829 insertions(+), 667 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3cc6954..668d962c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,9 +182,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" [[package]] name = "bindgen" @@ -519,18 +519,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstyle", "clap_lex", @@ -1499,7 +1499,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -2340,9 +2340,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.178" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "libgit2-sys" @@ -2546,7 +2546,7 @@ checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] @@ -2934,9 +2934,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", "ucd-trie", @@ -2944,9 +2944,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" dependencies = [ "pest", "pest_generator", @@ -2954,9 +2954,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" dependencies = [ "pest", "pest_meta", @@ -2967,9 +2967,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" dependencies = [ "pest", "sha2", @@ -3445,7 +3445,6 @@ dependencies = [ "gettext", "glob", "hex", - "lazy_static", "locale_config", "man", "ripasso", @@ -3994,9 +3993,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.112" +version = "2.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4" +checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" dependencies = [ "proc-macro2", "quote", @@ -4270,9 +4269,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -4294,9 +4293,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -4627,6 +4626,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -4638,9 +4646,12 @@ dependencies = [ [[package]] name = "wasite" -version = "0.1.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" +checksum = "313fb64fed616e75426cf738284efe4e85017243bccfae42698be1d7fa9b05d5" +dependencies = [ + "wasi 0.14.7+wasi-0.2.4", +] [[package]] name = "wasm-bindgen" @@ -4792,9 +4803,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" dependencies = [ "rustls-pki-types", ] @@ -4807,9 +4818,9 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] name = "whoami" -version = "1.6.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +checksum = "86dc1eeef7866078951fc09f1857d3d33a37432fe376d7ff45449c8bb50318c1" dependencies = [ "libredox", "wasite", @@ -5442,9 +5453,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.7" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9211a9f64b825911bdf0240f58b7a8dac217fe260fc61f080a07f61372fbd5" +checksum = "30e0d8dffbae3d840f64bda38e28391faef673a7b5a6017840f2a106c8145868" [[package]] name = "zune-core" diff --git a/Cargo.toml b/Cargo.toml index 4054f9c3..650b7b77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ gpgme = "0.11" chrono = { version = "0.4", default-features = false, features = ["clock"] } git2 = "0.20" rand = "0.9" -whoami = "1" +whoami = "2" toml = "0.9" reqwest = { version = "0.13", features = ["blocking"] } hex = "0.4" diff --git a/benches/library_benchmark.rs b/benches/library_benchmark.rs index cbb34f6e..9b4856c8 100644 --- a/benches/library_benchmark.rs +++ b/benches/library_benchmark.rs @@ -30,12 +30,12 @@ fn cleanup(mut base_path: PathBuf, path_name: &str) -> Result<(), std::io::Error fn pop_list(password_dir: PathBuf) -> pass::Result<()> { let store = pass::PasswordStore::new( "", - &Some(password_dir), - &None, - &None, - &None, + Some(&password_dir), + None, + None, + None, &CryptoImpl::GpgMe, - &None, + None, )?; let results = store.all_passwords().unwrap(); diff --git a/cursive/Cargo.toml b/cursive/Cargo.toml index 80cc9c75..23a9ca4a 100644 --- a/cursive/Cargo.toml +++ b/cursive/Cargo.toml @@ -17,7 +17,6 @@ ripasso = { path = "../", version = "0.8.0" } locale_config = "0.3" unic-langid = "0.9" gettext = "0.4" -lazy_static = "1" terminal_size = "0.4" hex = "0.4" zeroize = { version = "1", features = ["zeroize_derive", "alloc"] } @@ -28,9 +27,12 @@ default-features = false features = ["toml"] [dev-dependencies] -tempfile = "3.10.1" +tempfile = "3" chrono = { version = "0.4", default-features = false, features = ["clock"] } [build-dependencies] glob = "0.3" man = "0.3" + +[lints.clippy] +pedantic = "warn" diff --git a/cursive/build.rs b/cursive/build.rs index eb3e3075..0d0a1a3a 100644 --- a/cursive/build.rs +++ b/cursive/build.rs @@ -65,7 +65,7 @@ fn generate_man_page_file() { dest_path.pop(); dest_path.pop(); dest_path.push("man-page"); - print!("creating directory: {:?} ", &dest_path); + print!("creating directory: {} ", dest_path.display()); let res = std::fs::create_dir(&dest_path); if res.is_ok() { println!("success"); @@ -73,7 +73,7 @@ fn generate_man_page_file() { println!("error: {:?}", res.err().unwrap()); } dest_path.push("cursive"); - print!("creating directory: {:?} ", &dest_path); + print!("creating directory: {} ", dest_path.display()); let res = std::fs::create_dir(&dest_path); if res.is_ok() { println!("success"); @@ -94,7 +94,7 @@ fn generate_translation_files() { dest_path.pop(); dest_path.pop(); dest_path.push("translations"); - print!("creating directory: {:?} ", &dest_path); + print!("creating directory: {} ", dest_path.display()); let res = std::fs::create_dir(&dest_path); if res.is_ok() { println!("success"); @@ -102,7 +102,7 @@ fn generate_translation_files() { println!("error: {:?}", res.err().unwrap()); } dest_path.push("cursive"); - print!("creating directory: {:?} ", &dest_path); + print!("creating directory: {} ", dest_path.display()); let res = std::fs::create_dir(&dest_path); if res.is_ok() { println!("success"); @@ -128,8 +128,8 @@ fn generate_translation_files() { filename.replace_range(3..4, "m"); print!( - "generating .mo file for {:?} to {}/{} ", - &file, + "generating .mo file for {} to {}/{} ", + file.display(), dest_path.display(), &filename ); diff --git a/cursive/src/helpers.rs b/cursive/src/helpers.rs index 5ce11856..f38c4929 100644 --- a/cursive/src/helpers.rs +++ b/cursive/src/helpers.rs @@ -14,7 +14,7 @@ along with this program. If not, see . */ -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, LazyLock, Mutex}; use arboard::Clipboard; use cursive::{ @@ -22,13 +22,11 @@ use cursive::{ event::Key, views::{Checkbox, Dialog, EditView, OnEventView, RadioButton, TextView}, }; -use lazy_static::lazy_static; use pass::Result; use ripasso::{crypto::CryptoImpl, pass, pass::Recipient}; -lazy_static! { - static ref CLIPBOARD: Arc> = Arc::new(Mutex::new(Clipboard::new().unwrap())); -} +static CLIPBOARD: LazyLock>> = + LazyLock::new(|| Arc::new(Mutex::new(Clipboard::new().unwrap()))); /// Displays an error in a cursive dialog pub fn errorbox(ui: &mut Cursive, err: &pass::Error) { diff --git a/cursive/src/main.rs b/cursive/src/main.rs index 0a3fc554..0acc7a72 100644 --- a/cursive/src/main.rs +++ b/cursive/src/main.rs @@ -14,14 +14,7 @@ along with this program. If not, see . */ -use std::{ - collections::HashMap, - path::{Path, PathBuf}, - process, - sync::{Arc, Mutex}, - thread, time, -}; - +use config::Config; use cursive::{ Cursive, CursiveExt, direction::Orientation, @@ -44,32 +37,39 @@ use ripasso::{ passphrase_generator::passphrase_generator, password_generator::password_generator, }; +use std::sync::{LazyLock, MutexGuard}; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + process, + sync::{Arc, Mutex}, + thread, time, +}; use unic_langid::LanguageIdentifier; -mod helpers; -mod wizard; - use crate::helpers::{ get_value_from_input, is_checkbox_checked, is_radio_button_selected, recipients_widths, }; -use lazy_static::lazy_static; use ripasso::crypto::Fingerprint; use ripasso::password_generator::PasswordGenerationCategory; use zeroize::Zeroize; -/// The 'pointer' to the current PasswordStore is of this convoluted type. +mod helpers; +mod wizard; + +/// The 'pointer' to the current `PasswordStore` is of this convoluted type. type PasswordStoreType = Arc>>>; /// The list of stores that the user have. type StoreListType = Arc>>>>; -lazy_static! { - static ref CATALOG: gettext::Catalog = get_translation_catalog(); - static ref DEFAULT_TERMINAL_SIZE: (usize, usize) = match terminal_size::terminal_size() { - Some((terminal_size::Width(w), terminal_size::Height(h))) => - (usize::from(w + 8), usize::from(h)), +static CATALOG: LazyLock = LazyLock::new(get_translation_catalog); +static DEFAULT_TERMINAL_SIZE: LazyLock<(usize, usize)> = + LazyLock::new(|| match terminal_size::terminal_size() { + Some((terminal_size::Width(w), terminal_size::Height(h))) => { + (usize::from(w + 8), usize::from(h)) + } _ => (0, 0), - }; -} + }); fn screen_width(ui: &Cursive) -> usize { match ui.screen_size().x { @@ -135,7 +135,7 @@ fn page_up(ui: &mut Cursive) { ); } -fn copy(ui: &mut Cursive, store: PasswordStoreType) { +fn copy(ui: &mut Cursive, store: &PasswordStoreType) { let sel = ui .find_name::>("results") .unwrap() @@ -163,7 +163,7 @@ fn copy(ui: &mut Cursive, store: PasswordStoreType) { }); } -fn copy_first_line(ui: &mut Cursive, store: PasswordStoreType) { +fn copy_first_line(ui: &mut Cursive, store: &PasswordStoreType) { let sel = ui .find_name::>("results") .unwrap() @@ -193,7 +193,7 @@ fn copy_first_line(ui: &mut Cursive, store: PasswordStoreType) { }); } -fn copy_mfa(ui: &mut Cursive, store: PasswordStoreType) { +fn copy_mfa(ui: &mut Cursive, store: &PasswordStoreType) { let sel = ui .find_name::>("results") .unwrap() @@ -242,7 +242,7 @@ fn copy_name(ui: &mut Cursive) { }); } -fn do_delete(ui: &mut Cursive, store: PasswordStoreType) { +fn do_delete(ui: &mut Cursive, store: &PasswordStoreType) { ui.call_on_name( "results", |l: &mut SelectView| -> Result<()> { @@ -270,13 +270,14 @@ fn do_delete(ui: &mut Cursive, store: PasswordStoreType) { ui.pop_layer(); } -fn delete(ui: &mut Cursive, store: PasswordStoreType) { +fn delete(ui: &mut Cursive, store: &PasswordStoreType) { + let store = store.clone(); ui.add_layer(CircularFocus::new( Dialog::around(TextView::new( CATALOG.gettext("Are you sure you want to delete the password?"), )) .button(CATALOG.gettext("Yes"), move |ui: &mut Cursive| { - do_delete(ui, store.clone()); + do_delete(ui, &store); ui.call_on_name("status_bar", |l: &mut TextView| { l.set_content(CATALOG.gettext("Password deleted")); }); @@ -300,7 +301,7 @@ fn get_selected_password_entry(ui: &mut Cursive) -> Option Some(password_entry) } -fn show_file_history(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { +fn show_file_history(ui: &mut Cursive, store: &PasswordStoreType) -> Result<()> { let password_entry_opt = get_selected_password_entry(ui); if password_entry_opt.is_none() { return Ok(()); @@ -346,7 +347,7 @@ fn show_file_history(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { Ok(()) } -fn do_show_file_history(ui: &mut Cursive, store: PasswordStoreType) { +fn do_show_file_history(ui: &mut Cursive, store: &PasswordStoreType) { let res = show_file_history(ui, store); if let Err(err) = res { @@ -354,7 +355,7 @@ fn do_show_file_history(ui: &mut Cursive, store: PasswordStoreType) { } } -fn do_password_save(ui: &mut Cursive, password: &str, store: PasswordStoreType, do_pop: bool) { +fn do_password_save(ui: &mut Cursive, password: &str, store: &PasswordStoreType, do_pop: bool) { let res = password_save(ui, password, store, do_pop); if let Err(err) = res { helpers::errorbox(ui, &err); @@ -364,7 +365,7 @@ fn do_password_save(ui: &mut Cursive, password: &str, store: PasswordStoreType, fn password_save( ui: &mut Cursive, password: &str, - store: PasswordStoreType, + store: &PasswordStoreType, do_pop: bool, ) -> Result<()> { let password_entry_opt = get_selected_password_entry(ui); @@ -377,7 +378,7 @@ fn password_save( let r = password_entry.update(password.to_string(), &*store.lock()?.lock()?); if let Err(err) = r { - helpers::errorbox(ui, &err) + helpers::errorbox(ui, &err); } else { if do_pop { ui.pop_layer(); @@ -392,14 +393,14 @@ fn password_save( Ok(()) } -fn do_open(ui: &mut Cursive, store: PasswordStoreType) { +fn do_open(ui: &mut Cursive, store: &PasswordStoreType) { let res = open(ui, store); if let Err(err) = res { helpers::errorbox(ui, &err); } } -fn open(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { +fn open(ui: &mut Cursive, store: &PasswordStoreType) -> Result<()> { let password_entry_opt = get_selected_password_entry(ui); if password_entry_opt.is_none() { return Ok(()); @@ -416,6 +417,7 @@ fn open(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { } } }; + let store = store.clone(); let d = Dialog::around(TextArea::new().content(&password).with_name("editbox")) .button(CATALOG.gettext("Save"), move |s| { let mut new_secret = s @@ -429,7 +431,7 @@ fn open(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { let mut confirmed_new_secret = s .call_on_name("editbox", |e: &mut TextArea| e.get_content().to_string()) .unwrap(); - do_password_save(s, &confirmed_new_secret, store.clone(), true); + do_password_save(s, &confirmed_new_secret, &store, true); confirmed_new_secret.zeroize(); }) .dismiss_button(CATALOG.gettext("Close")); @@ -439,8 +441,8 @@ fn open(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { }); s.add_layer(ev); } else { - do_password_save(s, &new_secret, store.clone(), false); - }; + do_password_save(s, &new_secret, &store, false); + } new_secret.zeroize(); }) @@ -449,7 +451,7 @@ fn open(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { s.call_on_name("editbox", |e: &mut TextArea| { e.set_content(&new_password); }); - new_password.zeroize() + new_password.zeroize(); }) @@ -477,7 +479,7 @@ fn open(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { Ok(()) } -fn do_rename_file(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { +fn do_rename_file(ui: &mut Cursive, store: &PasswordStoreType) -> Result<()> { let old_name = ui .find_name::("old_name_input") .unwrap() @@ -518,7 +520,7 @@ fn do_rename_file(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { Ok(()) } -fn rename_file_dialog(ui: &mut Cursive, store: PasswordStoreType) { +fn rename_file_dialog(ui: &mut Cursive, store: &PasswordStoreType) { let sel = ui .find_name::>("results") .unwrap() @@ -557,12 +559,13 @@ fn rename_file_dialog(ui: &mut Cursive, store: PasswordStoreType) { fields.add_child(old_name_fields); fields.add_child(new_name_fields); + let store = store.clone(); let store2 = store.clone(); let d = Dialog::around(fields) .title(CATALOG.gettext("Rename File")) .button(CATALOG.gettext("Rename"), move |ui: &mut Cursive| { - if let Err(e) = do_rename_file(ui, store.clone()) { + if let Err(e) = do_rename_file(ui, &store) { helpers::errorbox(ui, &e); } }) @@ -573,7 +576,7 @@ fn rename_file_dialog(ui: &mut Cursive, store: PasswordStoreType) { s.pop_layer(); }) .on_event(Key::Enter, move |ui: &mut Cursive| { - if let Err(e) = do_rename_file(ui, store2.clone()) { + if let Err(e) = do_rename_file(ui, &store2) { helpers::errorbox(ui, &e); } }); @@ -585,7 +588,7 @@ fn do_new_password_save( s: &mut Cursive, path: &str, password: &str, - store: PasswordStoreType, + store: &PasswordStoreType, do_pop: bool, ) { let res = new_password_save(s, path, password, store, do_pop); @@ -598,7 +601,7 @@ fn new_password_save( s: &mut Cursive, path: &str, password: &str, - store: PasswordStoreType, + store: &PasswordStoreType, do_pop: bool, ) -> Result<()> { let entry = store @@ -630,7 +633,7 @@ fn new_password_save( Ok(()) } -fn create_save(s: &mut Cursive, store: PasswordStoreType) { +fn create_save(s: &mut Cursive, store: &PasswordStoreType) { let password = get_value_from_input(s, "new_password_input"); if password.is_none() { return; @@ -655,9 +658,10 @@ fn create_save(s: &mut Cursive, store: PasswordStoreType) { } if password.contains("otpauth://") { + let store = store.clone(); let d = Dialog::around(TextView::new(CATALOG.gettext("It seems like you are trying to save a TOTP code to the password store. This will reduce your 2FA solution to just 1FA, do you want to proceed?"))) .button(CATALOG.gettext("Save"), move |s| { - do_new_password_save(s, path.as_ref(), password.as_ref(), store.clone(), true); + do_new_password_save(s, path.as_ref(), password.as_ref(), &store, true); }) .dismiss_button(CATALOG.gettext("Close")); @@ -670,7 +674,109 @@ fn create_save(s: &mut Cursive, store: PasswordStoreType) { do_new_password_save(s, path.as_ref(), password.as_ref(), store, false); } } -fn create(ui: &mut Cursive, store: PasswordStoreType) { + +fn generate_password_callback( + category_value: &Arc>, + password_length: &Arc>, + s: &mut Cursive, +) { + let category = *category_value.lock().unwrap(); + let length = *password_length.lock().unwrap(); + let category = if category == 0 { + PasswordGenerationCategory::AsciiOnly + } else { + PasswordGenerationCategory::AsciiExtended + }; + let new_password = password_generator(length, category); + + s.call_on_name("new_password_input", |e: &mut EditView| { + e.set_content(new_password); + }); +} + +fn generate_passphrase_callback(s: &mut Cursive) { + let new_password = match passphrase_generator(6) { + Ok(words) => words.join(" "), + Err(err) => { + helpers::errorbox(s, &err); + return; + } + }; + s.call_on_name("new_password_input", |e: &mut EditView| { + e.set_content(new_password); + }); +} + +fn create_password_options_dialog( + category_value: &Arc>, + reveal_flag: &Arc>, + password_length: &Arc>, + s: &mut Cursive, +) { + let mut select = SelectView::::new(); + select.add_item("Category 0 (ASCII 33–126)", 0); + select.add_item("Category 1 (ASCII 33–255)", 1); + select.set_selection(*category_value.lock().unwrap()); + let select = select.with_name("password_category"); + + let length_input = EditView::new() + .content(password_length.lock().unwrap().to_string()) + .with_name("password_length") + .fixed_width(5); + + let reveal_checkbox = LinearLayout::horizontal() + .child(Checkbox::new().on_change({ + let reveal_flag = reveal_flag.clone(); + move |siv, checked| { + siv.call_on_name("new_password_input", |e: &mut EditView| { + e.set_secret(!checked); + }); + *reveal_flag.lock().unwrap() = checked; + } + })) + .child(TextView::new("Reveal password")); + + let dialog_content = LinearLayout::vertical() + .child(select.scrollable().fixed_size((30, 5))) + .child( + LinearLayout::horizontal() + .child(TextView::new("Length: ")) + .child(length_input), + ) + .child(reveal_checkbox); + + let save_selection = { + let category_value = category_value.clone(); + let password_length = password_length.clone(); + move |s: &mut Cursive| { + s.call_on_name("password_category", |view: &mut SelectView| { + if let Some(sel) = view.selection() { + *category_value.lock().unwrap() = *sel; + } + }); + + s.call_on_name("password_length", |view: &mut EditView| { + if let Ok(len) = view.get_content().parse::() { + *password_length.lock().unwrap() = len; + } + }); + + s.pop_layer(); + } + }; + + let popup = OnEventView::new( + Dialog::around(dialog_content) + .title("Password Options") + .button("OK", save_selection.clone()) + .dismiss_button("Cancel"), + ) + .on_event(Key::Enter, save_selection); + + s.add_layer(popup); +} + +fn create(ui: &mut Cursive, store: &PasswordStoreType) { let mut fields = LinearLayout::vertical(); let mut path_fields = LinearLayout::horizontal(); let mut password_fields = LinearLayout::horizontal(); @@ -716,6 +822,7 @@ fn create(ui: &mut Cursive, store: PasswordStoreType) { let reveal_flag = Arc::new(Mutex::new(false)); let password_length = Arc::new(Mutex::new(20_usize)); + let store = store.clone(); let d = Dialog::around(fields) .title(CATALOG.gettext("Add new password")) .button(CATALOG.gettext("Password Options"), { @@ -723,101 +830,17 @@ fn create(ui: &mut Cursive, store: PasswordStoreType) { let reveal_flag = reveal_flag.clone(); let password_length = password_length.clone(); move |s| { - let mut select = SelectView::::new(); - select.add_item("Category 0 (ASCII 33–126)", 0); - select.add_item("Category 1 (ASCII 33–255)", 1); - select.set_selection(*category_value.lock().unwrap()); - let select = select.with_name("password_category"); - - let length_input = EditView::new() - .content(password_length.lock().unwrap().to_string()) - .with_name("password_length") - .fixed_width(5); - - let reveal_checkbox = LinearLayout::horizontal() - .child(cursive::views::Checkbox::new().on_change({ - let reveal_flag = reveal_flag.clone(); - move |siv, checked| { - siv.call_on_name("new_password_input", |e: &mut EditView| { - e.set_secret(!checked); - }); - *reveal_flag.lock().unwrap() = checked; - } - })) - .child(TextView::new("Reveal password")); - - let dialog_content = LinearLayout::vertical() - .child(select.scrollable().fixed_size((30, 5))) - .child( - LinearLayout::horizontal() - .child(TextView::new("Length: ")) - .child(length_input), - ) - .child(reveal_checkbox); - - let save_selection = { - let category_value = category_value.clone(); - let password_length = password_length.clone(); - move |s: &mut Cursive| { - s.call_on_name("password_category", |view: &mut SelectView| { - if let Some(sel) = view.selection() { - *category_value.lock().unwrap() = *sel; - } - }); - - s.call_on_name("password_length", |view: &mut EditView| { - if let Ok(len) = view.get_content().parse::() { - *password_length.lock().unwrap() = len; - } - }); - - s.pop_layer(); - } - }; - - let popup = OnEventView::new( - Dialog::around(dialog_content) - .title("Password Options") - .button("OK", save_selection.clone()) - .dismiss_button("Cancel"), - ) - .on_event(Key::Enter, save_selection); - - s.add_layer(popup); + create_password_options_dialog(&category_value, &reveal_flag, &password_length, s); } }) - .button(CATALOG.gettext("Generate Password"), { - let category_value = category_value.clone(); - let password_length = password_length.clone(); - move |s| { - let category = *category_value.lock().unwrap(); - let length = *password_length.lock().unwrap(); - let category = if category == 0 { - PasswordGenerationCategory::AsciiOnly - } else { - PasswordGenerationCategory::AsciiExtended - }; - let new_password = password_generator(length, category); - - s.call_on_name("new_password_input", |e: &mut EditView| { - e.set_content(new_password); - }); - } + .button(CATALOG.gettext("Generate Password"), move |s| { + generate_password_callback(&category_value, &password_length, s); }) .button(CATALOG.gettext("Generate Passphrase"), move |s| { - let new_password = match ripasso::passphrase_generator::passphrase_generator(6) { - Ok(words) => words.join(" "), - Err(err) => { - helpers::errorbox(s, &err); - return; - } - }; - s.call_on_name("new_password_input", |e: &mut EditView| { - e.set_content(new_password); - }); + generate_passphrase_callback(s); }) .button(CATALOG.gettext("Save"), move |ui: &mut Cursive| { - create_save(ui, store.clone()) + create_save(ui, &store); }) .dismiss_button(CATALOG.gettext("Cancel")); @@ -827,14 +850,14 @@ fn create(ui: &mut Cursive, store: PasswordStoreType) { }) .on_event(Key::Enter, move |ui: &mut Cursive| { if ui.screen_mut().len() == 1 { - create_save(ui, store2.clone()); + create_save(ui, &store2); } }); ui.add_layer(ev); } -fn delete_recipient(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { +fn delete_recipient(ui: &mut Cursive, store: &PasswordStoreType) -> Result<()> { let mut l = ui .find_name::>>("recipients") .unwrap(); @@ -862,15 +885,16 @@ fn delete_recipient(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { } } -fn delete_recipient_verification(ui: &mut Cursive, store: PasswordStoreType) { +fn delete_recipient_verification(ui: &mut Cursive, store: &PasswordStoreType) { + let store = store.clone(); ui.add_layer(CircularFocus::new( Dialog::around(TextView::new( CATALOG.gettext("Are you sure you want to remove this person?"), )) .button(CATALOG.gettext("Yes"), move |ui: &mut Cursive| { - let res = delete_recipient(ui, store.clone()); + let res = delete_recipient(ui, &store); if let Err(err) = res { - helpers::errorbox(ui, &err) + helpers::errorbox(ui, &err); } else { ui.pop_layer(); } @@ -879,7 +903,7 @@ fn delete_recipient_verification(ui: &mut Cursive, store: PasswordStoreType) { )); } -fn add_recipient(ui: &mut Cursive, store: PasswordStoreType, config_path: &Path) -> Result<()> { +fn add_recipient(ui: &mut Cursive, store: &PasswordStoreType, config_path: &Path) -> Result<()> { let l = &*get_value_from_input(ui, "key_id_input").unwrap(); let dir = &*get_value_from_input(ui, "dir_id_input").unwrap(); @@ -896,30 +920,27 @@ fn add_recipient(ui: &mut Cursive, store: PasswordStoreType, config_path: &Path) let dir_path = std::path::PathBuf::from(dir); let res = store.add_recipient(&recipient, &dir_path, config_path); - match res { - Err(err) => helpers::errorbox(ui, &err), - Ok(_) => { - let all_recipients_res = store.recipients_for_path(&dir_path); - match all_recipients_res { - Err(err) => helpers::errorbox(ui, &err), - Ok(recipients) => { - let (max_width_key, max_width_name) = recipients_widths(&recipients); - - let mut recipients_view = ui - .find_name::>>("recipients") - .unwrap(); - recipients_view.add_item( - render_recipient_label(&recipient, max_width_key, max_width_name), - Some((dir_path, recipient)), - ); - - ui.pop_layer(); - ui.call_on_name("status_bar", |l: &mut TextView| { - l.set_content( - CATALOG.gettext("Added team member to password store"), - ); - }); - } + if let Err(err) = res { + helpers::errorbox(ui, &err); + } else { + let all_recipients_res = store.recipients_for_path(&dir_path); + match all_recipients_res { + Err(err) => helpers::errorbox(ui, &err), + Ok(recipients) => { + let (max_width_key, max_width_name) = recipients_widths(&recipients); + + let mut recipients_view = ui + .find_name::>>("recipients") + .unwrap(); + recipients_view.add_item( + render_recipient_label(&recipient, max_width_key, max_width_name), + Some((dir_path, recipient)), + ); + + ui.pop_layer(); + ui.call_on_name("status_bar", |l: &mut TextView| { + l.set_content(CATALOG.gettext("Added team member to password store")); + }); } } } @@ -929,7 +950,7 @@ fn add_recipient(ui: &mut Cursive, store: PasswordStoreType, config_path: &Path) Ok(()) } -fn add_recipient_dialog(ui: &mut Cursive, store: PasswordStoreType, config_path: &Path) { +fn add_recipient_dialog(ui: &mut Cursive, store: &PasswordStoreType, config_path: &Path) { let mut all_fields = LinearLayout::vertical(); let mut recipient_fields = LinearLayout::horizontal(); let mut dir_fields = LinearLayout::horizontal(); @@ -959,13 +980,14 @@ fn add_recipient_dialog(ui: &mut Cursive, store: PasswordStoreType, config_path: all_fields.add_child(recipient_fields); all_fields.add_child(dir_fields); + let store = store.clone(); let config_path = config_path.to_path_buf(); let cf = CircularFocus::new( Dialog::around(all_fields) .button(CATALOG.gettext("Yes"), move |ui: &mut Cursive| { - let res = add_recipient(ui, store.clone(), &config_path); + let res = add_recipient(ui, &store, &config_path); if let Err(err) = res { - helpers::errorbox(ui, &err) + helpers::errorbox(ui, &err); } }) .dismiss_button(CATALOG.gettext("Cancel")), @@ -1032,7 +1054,7 @@ fn get_sub_dirs(dir: &PathBuf) -> Result> { Ok(all) } -fn view_recipients(ui: &mut Cursive, store: PasswordStoreType, config_path: &Path) -> Result<()> { +fn view_recipients(ui: &mut Cursive, store: &PasswordStoreType, config_path: &Path) -> Result<()> { let sub_dirs = get_sub_dirs(&store.lock()?.lock()?.get_store_path()); if let Err(err) = sub_dirs { helpers::errorbox(ui, &err); @@ -1054,10 +1076,10 @@ fn view_recipients(ui: &mut Cursive, store: PasswordStoreType, config_path: &Pat path_to_recipients.insert(dir.clone(), recipients_res?); } - view_recipients_for_many_dirs(ui, store, path_to_recipients, config_path); + view_recipients_for_many_dirs(ui, store, &path_to_recipients, config_path); } std::cmp::Ordering::Equal => { - do_view_recipients_for_dir(ui, store, sub_dirs[0].clone(), config_path); + do_view_recipients_for_dir(ui, store, &sub_dirs[0], config_path); } std::cmp::Ordering::Less => { helpers::errorbox(ui, &pass::Error::Generic("no subdirectories found")); @@ -1067,7 +1089,7 @@ fn view_recipients(ui: &mut Cursive, store: PasswordStoreType, config_path: &Pat Ok(()) } -fn do_view_recipients(ui: &mut Cursive, store: PasswordStoreType, config_path: &Path) { +fn do_view_recipients(ui: &mut Cursive, store: &PasswordStoreType, config_path: &Path) { let res = view_recipients(ui, store, config_path); if let Err(err) = res { helpers::errorbox(ui, &err); @@ -1076,15 +1098,15 @@ fn do_view_recipients(ui: &mut Cursive, store: PasswordStoreType, config_path: & fn view_recipients_for_many_dirs( ui: &mut Cursive, - store: PasswordStoreType, - path_to_recipients: HashMap>, + store: &PasswordStoreType, + path_to_recipients: &HashMap>, config_path: &Path, ) { let mut recipients_view = SelectView::>::new() .h_align(cursive::align::HAlign::Left) .with_name("recipients"); - for (path, recipients) in &path_to_recipients { + for (path, recipients) in path_to_recipients { recipients_view .get_mut() .add_item(path.to_string_lossy(), None); @@ -1092,7 +1114,7 @@ fn view_recipients_for_many_dirs( for recipient in recipients { recipients_view.get_mut().add_item( render_recipient_label(recipient, max_width_key, max_width_name), - Some((path.to_path_buf(), recipient.clone())), + Some((path.clone(), recipient.clone())), ); } } @@ -1106,15 +1128,16 @@ fn view_recipients_for_many_dirs( .child(TextView::new(CATALOG.gettext("del: Remove"))), ); + let store = store.clone(); let store2 = store.clone(); let config_path = config_path.to_path_buf(); let recipients_event = OnEventView::new(ll) .on_event(Key::Del, move |ui: &mut Cursive| { - delete_recipient_verification(ui, store.clone()) + delete_recipient_verification(ui, &store); }) .on_event(Key::Ins, move |ui: &mut Cursive| { - add_recipient_dialog(ui, store2.clone(), &config_path) + add_recipient_dialog(ui, &store2, &config_path); }) .on_event(Key::Esc, |s| { s.pop_layer(); @@ -1125,8 +1148,8 @@ fn view_recipients_for_many_dirs( fn do_view_recipients_for_dir( ui: &mut Cursive, - store: PasswordStoreType, - dir: PathBuf, + store: &PasswordStoreType, + dir: &Path, config_path: &Path, ) { let res = view_recipients_for_dir(ui, store, dir, config_path); @@ -1137,11 +1160,11 @@ fn do_view_recipients_for_dir( fn view_recipients_for_dir( ui: &mut Cursive, - store: PasswordStoreType, - dir: PathBuf, + store: &PasswordStoreType, + dir: &Path, config_path: &Path, ) -> Result<()> { - let path = store.lock()?.lock()?.get_store_path().join(dir.clone()); + let path = store.lock()?.lock()?.get_store_path().join(dir); let recipients_res = store.lock()?.lock()?.recipients_for_path(&path); if let Err(err) = recipients_res { @@ -1158,7 +1181,7 @@ fn view_recipients_for_dir( for recipient in recipients { recipients_view.get_mut().add_item( render_recipient_label(&recipient, max_width_key, max_width_name), - Some((dir.clone(), recipient)), + Some((dir.to_path_buf(), recipient)), ); } @@ -1172,15 +1195,16 @@ fn view_recipients_for_dir( .child(TextView::new(CATALOG.gettext("del: Remove"))), ); + let store = store.clone(); let store2 = store.clone(); let config_path = config_path.to_path_buf(); let recipients_event = OnEventView::new(ll) .on_event(Key::Del, move |ui: &mut Cursive| { - delete_recipient_verification(ui, store.clone()) + delete_recipient_verification(ui, &store); }) .on_event(Key::Ins, move |ui: &mut Cursive| { - add_recipient_dialog(ui, store2.clone(), &config_path) + add_recipient_dialog(ui, &store2, &config_path); }) .on_event(Key::Esc, |s| { s.pop_layer(); @@ -1254,18 +1278,18 @@ fn help() { println!("{}", CATALOG.gettext("A password manager that uses the file format of the standard unix password manager 'pass', implemented in Rust. Ripasso reads $HOME/.password-store/ by default, override this by setting the PASSWORD_STORE_DIR environmental variable.")); } -fn do_git_push(ui: &mut Cursive, store: PasswordStoreType) { +fn do_git_push(ui: &mut Cursive, store: &PasswordStoreType) { let res = git_push(ui, store); if let Err(err) = res { helpers::errorbox(ui, &err); } } -fn git_push(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { +fn git_push(ui: &mut Cursive, store: &PasswordStoreType) -> Result<()> { let push_result = push(&*store.lock()?.lock()?); match push_result { Err(err) => helpers::errorbox(ui, &err), - Ok(_) => { + Ok(()) => { ui.call_on_name("status_bar", |l: &mut TextView| { l.set_content(CATALOG.gettext("Pushed to remote git repository")); }); @@ -1274,14 +1298,14 @@ fn git_push(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { Ok(()) } -fn do_git_pull(ui: &mut Cursive, store: PasswordStoreType) { +fn do_git_pull(ui: &mut Cursive, store: &PasswordStoreType) { let res = git_pull(ui, store); if let Err(err) = res { helpers::errorbox(ui, &err); } } -fn git_pull(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { +fn git_pull(ui: &mut Cursive, store: &PasswordStoreType) -> Result<()> { let _ = pull(&*store.lock()?.lock()?).map_err(|err| helpers::errorbox(ui, &err)); let _ = store .lock()? @@ -1295,8 +1319,7 @@ fn git_pull(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { "results", |l: &mut SelectView| -> Result<()> { l.clear(); - #[allow(clippy::significant_drop_in_scrutinee)] - for p in store.lock()?.lock()?.passwords.iter() { + for p in &store.lock()?.lock()?.passwords { l.add_item(create_label(p, col), p.clone()); } Ok(()) @@ -1309,7 +1332,7 @@ fn git_pull(ui: &mut Cursive, store: PasswordStoreType) -> Result<()> { Ok(()) } -fn do_gpg_import(ui: &mut Cursive, store: PasswordStoreType, config_path: &Path) -> Result<()> { +fn do_gpg_import(ui: &mut Cursive, store: &PasswordStoreType, config_path: &Path) -> Result<()> { let ta = ui.find_name::