From acd1173848b4db1c733af7d3f53d24aab900b542 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Tue, 14 Dec 2021 23:00:15 -0500 Subject: clippy --- src/actions.rs | 6 +- src/api.rs | 78 +++++++------- src/bin/rbw-agent/actions.rs | 46 ++++---- src/bin/rbw-agent/agent.rs | 10 +- src/bin/rbw-agent/main.rs | 19 +++- src/bin/rbw-agent/sock.rs | 2 + src/bin/rbw/actions.rs | 10 +- src/bin/rbw/commands.rs | 251 +++++++++++++++++++++---------------------- src/bin/rbw/main.rs | 17 ++- src/config.rs | 14 ++- src/db.rs | 3 + src/dirs.rs | 10 ++ src/edit.rs | 1 + src/lib.rs | 16 +-- src/locked.rs | 14 +++ src/pinentry.rs | 6 +- src/protocol.rs | 1 + src/pwgen.rs | 3 +- 18 files changed, 278 insertions(+), 229 deletions(-) (limited to 'src') diff --git a/src/actions.rs b/src/actions.rs index 02ec854..b16e6f2 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -39,13 +39,13 @@ pub async fn login( Ok((access_token, refresh_token, iterations, protected_key)) } -pub async fn unlock( +pub fn unlock( email: &str, password: &crate::locked::Password, iterations: u32, protected_key: &str, protected_private_key: &str, - protected_org_keys: &std::collections::HashMap, + protected_org_keys: &std::collections::HashMap, ) -> Result<( crate::locked::Keys, std::collections::HashMap, @@ -148,7 +148,7 @@ fn add_once( let config = crate::config::Config::load()?; let client = crate::api::Client::new(&config.base_url(), &config.identity_url()); - client.add(access_token, name, data, notes, folder_id.as_deref())?; + client.add(access_token, name, data, notes, folder_id)?; Ok(()) } diff --git a/src/api.rs b/src/api.rs index 14c11fd..fc3912a 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,3 +1,7 @@ +// serde_repr generates some as conversions that we can't seem to silence from +// here, unfortunately +#![allow(clippy::as_conversions)] + use crate::prelude::*; use crate::json::{ @@ -138,8 +142,6 @@ struct PreloginReq { #[derive(serde::Deserialize, Debug)] struct PreloginRes { - #[serde(rename = "Kdf")] - kdf: u32, #[serde(rename = "KdfIterations")] kdf_iterations: u32, } @@ -169,8 +171,6 @@ struct ConnectPasswordReq { #[derive(serde::Deserialize, Debug)] struct ConnectPasswordRes { access_token: String, - expires_in: u32, - token_type: String, refresh_token: String, #[serde(rename = "Key")] key: String, @@ -202,9 +202,6 @@ struct ConnectRefreshTokenReq { #[derive(serde::Deserialize, Debug)] struct ConnectRefreshTokenRes { access_token: String, - expires_in: u32, - token_type: String, - refresh_token: String, } #[derive(serde::Deserialize, Debug)] @@ -253,32 +250,37 @@ impl SyncResCipher { if self.deleted_date.is_some() { return None; } - let history = if let Some(history) = &self.password_history { - history - .iter() - .filter_map(|entry| { - // Gets rid of entries with a non-existent password - entry.password.clone().map(|p| crate::db::HistoryEntry { - last_used_date: entry.last_used_date.clone(), - password: p, - }) - }) - .collect() - } else { - vec![] - }; + let history = + self.password_history + .as_ref() + .map_or_else(Vec::new, |history| { + history + .iter() + .filter_map(|entry| { + // Gets rid of entries with a non-existent + // password + entry.password.clone().map(|p| { + crate::db::HistoryEntry { + last_used_date: entry + .last_used_date + .clone(), + password: p, + } + }) + }) + .collect() + }); - let (folder, folder_id) = if let Some(folder_id) = &self.folder_id { - let mut folder_name = None; - for folder in folders { - if &folder.id == folder_id { - folder_name = Some(folder.name.clone()); + let (folder, folder_id) = + self.folder_id.as_ref().map_or((None, None), |folder_id| { + let mut folder_name = None; + for folder in folders { + if &folder.id == folder_id { + folder_name = Some(folder.name.clone()); + } } - } - (folder_name, Some(folder_id)) - } else { - (None, None) - }; + (folder_name, Some(folder_id)) + }); let data = if let Some(login) = &self.login { crate::db::EntryData::Login { username: login.username.clone(), @@ -332,7 +334,7 @@ impl SyncResCipher { } else { return None; }; - let fields = if let Some(fields) = &self.fields { + let fields = self.fields.as_ref().map_or_else(Vec::new, |fields| { fields .iter() .map(|field| crate::db::Field { @@ -340,9 +342,7 @@ impl SyncResCipher { value: field.value.clone(), }) .collect() - } else { - vec![] - }; + }); Some(crate::db::Entry { id: self.id.clone(), org_id: self.organization_id.clone(), @@ -554,6 +554,7 @@ pub struct Client { } impl Client { + #[must_use] pub fn new(base_url: &str, identity_url: &str) -> Self { Self { base_url: base_url.to_string(), @@ -607,7 +608,7 @@ impl Client { .send() .await .map_err(|source| Error::Reqwest { source })?; - if let reqwest::StatusCode::OK = res.status() { + if res.status() == reqwest::StatusCode::OK { Ok(()) } else { let code = res.status().as_u16(); @@ -636,6 +637,9 @@ impl Client { device_push_token: "".to_string(), two_factor_token: two_factor_token .map(std::string::ToString::to_string), + // enum casts are safe, and i don't think there's a better way to + // write it without some explicit impls + #[allow(clippy::as_conversions)] two_factor_provider: two_factor_provider.map(|ty| ty as u32), }; let client = reqwest::Client::new(); @@ -649,7 +653,7 @@ impl Client { .send() .await .map_err(|source| Error::Reqwest { source })?; - if let reqwest::StatusCode::OK = res.status() { + if res.status() == reqwest::StatusCode::OK { let connect_res: ConnectPasswordRes = res.json_with_path().await?; Ok(( diff --git a/src/bin/rbw-agent/actions.rs b/src/bin/rbw-agent/actions.rs index 1cc71c3..87a8276 100644 --- a/src/bin/rbw-agent/actions.rs +++ b/src/bin/rbw-agent/actions.rs @@ -61,10 +61,9 @@ pub async fn register( message, }) .context("failed to log in to bitwarden instance"); - } else { - err_msg = Some(message); - continue; } + err_msg = Some(message); + continue; } Err(e) => { return Err(e) @@ -172,9 +171,8 @@ pub async fn login( ) .await?; break; - } else { - return Err(anyhow::anyhow!("TODO")); } + return Err(anyhow::anyhow!("TODO")); } Err(rbw::error::Error::IncorrectPassword { message }) => { if i == 3 { @@ -182,10 +180,9 @@ pub async fn login( message, }) .context("failed to log in to bitwarden instance"); - } else { - err_msg = Some(message); - continue; } + err_msg = Some(message); + continue; } Err(e) => { return Err(e) @@ -249,10 +246,9 @@ async fn two_factor( message, }) .context("failed to log in to bitwarden instance"); - } else { - err_msg = Some(message); - continue; } + err_msg = Some(message); + continue; } // can get this if the user passes an empty string Err(rbw::error::Error::TwoFactorRequired { .. }) => { @@ -262,10 +258,9 @@ async fn two_factor( message, }) .context("failed to log in to bitwarden instance"); - } else { - err_msg = Some(message); - continue; } + err_msg = Some(message); + continue; } Err(e) => { return Err(e) @@ -313,8 +308,7 @@ async fn login_success( &protected_key, &protected_private_key, &db.protected_org_keys, - ) - .await; + ); match res { Ok((keys, org_keys)) => { @@ -362,7 +356,7 @@ pub async fn unlock( let email = config_email().await?; let mut err_msg = None; - for i in 1u8..=3 { + for i in 1_u8..=3 { let err = if i > 1 { // this unwrap is safe because we only ever continue the loop // if we have set err_msg @@ -387,9 +381,7 @@ pub async fn unlock( &protected_key, &protected_private_key, &db.protected_org_keys, - ) - .await - { + ) { Ok((keys, org_keys)) => { unlock_success(state, keys, org_keys).await?; break; @@ -400,10 +392,9 @@ pub async fn unlock( message, }) .context("failed to unlock database"); - } else { - err_msg = Some(message); - continue; } + err_msg = Some(message); + continue; } Err(e) => return Err(e).context("failed to unlock database"), } @@ -579,11 +570,10 @@ async fn respond_encrypt( async fn config_email() -> anyhow::Result { let config = rbw::config::Config::load_async().await?; - if let Some(email) = config.email { - Ok(email) - } else { - Err(anyhow::anyhow!("failed to find email address in config")) - } + config.email.map_or_else( + || Err(anyhow::anyhow!("failed to find email address in config")), + Ok, + ) } async fn load_db() -> anyhow::Result { diff --git a/src/bin/rbw-agent/agent.rs b/src/bin/rbw-agent/agent.rs index fae8c7b..d64bf21 100644 --- a/src/bin/rbw-agent/agent.rs +++ b/src/bin/rbw-agent/agent.rs @@ -32,7 +32,7 @@ impl State { pub fn clear(&mut self) { self.priv_key = None; - self.org_keys = Default::default(); + self.org_keys = None; // no real better option to unwrap here self.timeout_chan.send(TimeoutEvent::Clear).unwrap(); } @@ -57,7 +57,7 @@ impl Agent { timeout_chan: r, state: std::sync::Arc::new(tokio::sync::RwLock::new(State { priv_key: None, - org_keys: Default::default(), + org_keys: None, timeout_chan: w, })), }) @@ -81,11 +81,7 @@ impl Agent { tokio::time::Duration::from_secs(60 * 60 * 24 * 365 * 2), )); loop { - let timeout = if let Some(timeout) = &mut self.timeout { - timeout - } else { - &mut forever - }; + let timeout = self.timeout.as_mut().unwrap_or(&mut forever); tokio::select! { sock = listener.accept() => { let mut sock = crate::sock::Sock::new( diff --git a/src/bin/rbw-agent/main.rs b/src/bin/rbw-agent/main.rs index 69411ae..f5d478d 100644 --- a/src/bin/rbw-agent/main.rs +++ b/src/bin/rbw-agent/main.rs @@ -1,4 +1,15 @@ +#![warn(clippy::cargo)] +#![warn(clippy::pedantic)] +#![warn(clippy::nursery)] +#![warn(clippy::as_conversions)] +#![warn(clippy::get_unwrap)] +#![allow(clippy::cognitive_complexity)] +#![allow(clippy::missing_const_for_fn)] +#![allow(clippy::similar_names)] +#![allow(clippy::struct_excessive_bools)] #![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] +#![allow(clippy::type_complexity)] use anyhow::Context as _; @@ -29,11 +40,9 @@ fn real_main() -> anyhow::Result<()> { ) .init(); - let no_daemonize = if let Some(arg) = std::env::args().nth(1) { - arg == "--no-daemonize" - } else { - false - }; + let no_daemonize = std::env::args() + .nth(1) + .map_or(false, |arg| arg == "--no-daemonize"); let startup_ack = if no_daemonize { None diff --git a/src/bin/rbw-agent/sock.rs b/src/bin/rbw-agent/sock.rs index 311176c..9458239 100644 --- a/src/bin/rbw-agent/sock.rs +++ b/src/bin/rbw-agent/sock.rs @@ -45,6 +45,8 @@ impl Sock { pub fn listen() -> anyhow::Result { let path = rbw::dirs::socket_file(); // if the socket already doesn't exist, that's fine + // https://github.com/rust-lang/rust-clippy/issues/8003 + #[allow(clippy::let_underscore_drop)] let _ = std::fs::remove_file(&path); let sock = tokio::net::UnixListener::bind(&path) .context("failed to listen on socket")?; diff --git a/src/bin/rbw/actions.rs b/src/bin/rbw/actions.rs index 39fde15..321bff5 100644 --- a/src/bin/rbw/actions.rs +++ b/src/bin/rbw/actions.rs @@ -35,7 +35,7 @@ pub fn quit() -> anyhow::Result<()> { sock.send(&rbw::protocol::Request { tty: nix::unistd::ttyname(0) .ok() - .and_then(|p| p.to_str().map(|s| s.to_string())), + .and_then(|p| p.to_str().map(std::string::ToString::to_string)), action: rbw::protocol::Action::Quit, })?; wait_for_exit(pid); @@ -59,7 +59,7 @@ pub fn decrypt( sock.send(&rbw::protocol::Request { tty: nix::unistd::ttyname(0) .ok() - .and_then(|p| p.to_str().map(|s| s.to_string())), + .and_then(|p| p.to_str().map(std::string::ToString::to_string)), action: rbw::protocol::Action::Decrypt { cipherstring: cipherstring.to_string(), org_id: org_id.map(std::string::ToString::to_string), @@ -84,7 +84,7 @@ pub fn encrypt( sock.send(&rbw::protocol::Request { tty: nix::unistd::ttyname(0) .ok() - .and_then(|p| p.to_str().map(|s| s.to_string())), + .and_then(|p| p.to_str().map(std::string::ToString::to_string)), action: rbw::protocol::Action::Encrypt { plaintext: plaintext.to_string(), org_id: org_id.map(std::string::ToString::to_string), @@ -106,7 +106,7 @@ pub fn version() -> anyhow::Result { sock.send(&rbw::protocol::Request { tty: nix::unistd::ttyname(0) .ok() - .and_then(|p| p.to_str().map(|s| s.to_string())), + .and_then(|p| p.to_str().map(std::string::ToString::to_string)), action: rbw::protocol::Action::Version, })?; @@ -126,7 +126,7 @@ fn simple_action(action: rbw::protocol::Action) -> anyhow::Result<()> { sock.send(&rbw::protocol::Request { tty: nix::unistd::ttyname(0) .ok() - .and_then(|p| p.to_str().map(|s| s.to_string())), + .and_then(|p| p.to_str().map(std::string::ToString::to_string)), action, })?; diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs index 9efd966..0068efd 100644 --- a/src/bin/rbw/commands.rs +++ b/src/bin/rbw/commands.rs @@ -26,22 +26,28 @@ impl DecryptedCipher { fn display_short(&self, desc: &str) -> bool { match &self.data { DecryptedData::Login { password, .. } => { - if let Some(password) = password { - println!("{}", password); - true - } else { - eprintln!("entry for '{}' had no password", desc); - false - } + password.as_ref().map_or_else( + || { + eprintln!("entry for '{}' had no password", desc); + false + }, + |password| { + println!("{}", password); + true + }, + ) } DecryptedData::Card { number, .. } => { - if let Some(number) = number { - println!("{}", number); - true - } else { - eprintln!("entry for '{}' had no card number", desc); - false - } + number.as_ref().map_or_else( + || { + eprintln!("entry for '{}' had no card number", desc); + false + }, + |number| { + println!("{}", number); + true + }, + ) } DecryptedData::Identity { title, @@ -65,15 +71,16 @@ impl DecryptedCipher { true } } - DecryptedData::SecureNote {} => { - if let Some(notes) = &self.notes { - println!("{}", notes); - true - } else { + DecryptedData::SecureNote {} => self.notes.as_ref().map_or_else( + || { eprintln!("entry for '{}' had no notes", desc); false - } - } + }, + |notes| { + println!("{}", notes); + true + }, + ), } } @@ -86,18 +93,15 @@ impl DecryptedCipher { .. } => { let mut displayed = self.display_short(desc); - displayed |= - self.display_field("Username", username.as_deref()); - displayed |= - self.display_field("TOTP Secret", totp.as_deref()); + displayed |= display_field("Username", username.as_deref()); + displayed |= display_field("TOTP Secret", totp.as_deref()); if let Some(uris) = uris { for uri in uris { - displayed |= - self.display_field("URI", Some(&uri.uri)); + displayed |= display_field("URI", Some(&uri.uri)); let match_type = uri.match_type.map(|ty| format!("{}", ty)); - displayed |= self.display_field( + displayed |= display_field( "Match type", match_type.as_deref(), ); @@ -105,7 +109,7 @@ impl DecryptedCipher { } for field in &self.fields { - displayed |= self.display_field( + displayed |= display_field( field.name.as_deref().unwrap_or("(null)"), Some(field.value.as_deref().unwrap_or("")), ); @@ -134,10 +138,10 @@ impl DecryptedCipher { println!("Expiration: {}/{}", exp_month, exp_year); displayed = true; } - displayed |= self.display_field("CVV", code.as_deref()); + displayed |= display_field("CVV", code.as_deref()); displayed |= - self.display_field("Name", cardholder_name.as_deref()); - displayed |= self.display_field("Brand", brand.as_deref()); + display_field("Name", cardholder_name.as_deref()); + displayed |= display_field("Brand", brand.as_deref()); if let Some(notes) = &self.notes { if displayed { @@ -164,27 +168,22 @@ impl DecryptedCipher { } => { let mut displayed = self.display_short(desc); + displayed |= display_field("Address", address1.as_deref()); + displayed |= display_field("Address", address2.as_deref()); + displayed |= display_field("Address", address3.as_deref()); + displayed |= display_field("City", city.as_deref()); + displayed |= display_field("State", state.as_deref()); displayed |= - self.display_field("Address", address1.as_deref()); - displayed |= - self.display_field("Address", address2.as_deref()); - displayed |= - self.display_field("Address", address3.as_deref()); - displayed |= self.display_field("City", city.as_deref()); - displayed |= self.display_field("State", state.as_deref()); - displayed |= - self.display_field("Postcode", postal_code.as_deref()); - displayed |= - self.display_field("Country", country.as_deref()); - displayed |= self.display_field("Phone", phone.as_deref()); - displayed |= self.display_field("Email", email.as_deref()); - displayed |= self.display_field("SSN", ssn.as_deref()); + display_field("Postcode", postal_code.as_deref()); + displayed |= display_field("Country", country.as_deref()); + displayed |= display_field("Phone", phone.as_deref()); + displayed |= display_field("Email", email.as_deref()); + displayed |= display_field("SSN", ssn.as_deref()); displayed |= - self.display_field("License", license_number.as_deref()); - displayed |= self - .display_field("Passport", passport_number.as_deref()); + display_field("License", license_number.as_deref()); displayed |= - self.display_field("Username", username.as_deref()); + display_field("Passport", passport_number.as_deref()); + displayed |= display_field("Username", username.as_deref()); if let Some(notes) = &self.notes { if displayed { @@ -199,23 +198,13 @@ impl DecryptedCipher { } } - fn display_field(&self, name: &str, field: Option<&str>) -> bool { - if let Some(field) = field { - println!("{}: {}", name, field); - true - } else { - false - } - } - fn display_name(&self) -> String { match &self.data { DecryptedData::Login { username, .. } => { - if let Some(username) = username { - format!("{}@{}", username, self.name) - } else { - self.name.clone() - } + username.as_ref().map_or_else( + || self.name.clone(), + |username| format!("{}@{}", username, self.name), + ) } _ => self.name.clone(), } @@ -314,6 +303,7 @@ impl DecryptedCipher { #[derive(Debug, Clone)] #[cfg_attr(test, derive(Eq, PartialEq))] +#[allow(clippy::large_enum_variant)] enum DecryptedData { Login { username: Option, @@ -448,7 +438,7 @@ pub fn config_unset(key: &str) -> anyhow::Result<()> { "base_url" => config.base_url = None, "identity_url" => config.identity_url = None, "lock_timeout" => { - config.lock_timeout = rbw::config::default_lock_timeout() + config.lock_timeout = rbw::config::default_lock_timeout(); } "pinentry" => config.pinentry = rbw::config::default_pinentry(), _ => return Err(anyhow::anyhow!("invalid config key: {}", key)), @@ -526,17 +516,18 @@ pub fn list(fields: &[String]) -> anyhow::Result<()> { ListField::Name => cipher.name.clone(), ListField::Id => cipher.id.clone(), ListField::User => match &cipher.data { - DecryptedData::Login { username, .. } => username - .as_ref() - .map(std::string::ToString::to_string) - .unwrap_or_else(|| "".to_string()), + DecryptedData::Login { username, .. } => { + username.as_ref().map_or_else( + || "".to_string(), + std::string::ToString::to_string, + ) + } _ => "".to_string(), }, - ListField::Folder => cipher - .folder - .as_ref() - .map(std::string::ToString::to_string) - .unwrap_or_else(|| "".to_string()), + ListField::Folder => cipher.folder.as_ref().map_or_else( + || "".to_string(), + std::string::ToString::to_string, + ), }) .collect(); println!("{}", values.join("\t")); @@ -557,8 +548,7 @@ pub fn get( let desc = format!( "{}{}", - user.map(|s| format!("{}@", s)) - .unwrap_or_else(|| "".to_string()), + user.map_or_else(|| "".to_string(), |s| format!("{}@", s)), name ); @@ -584,8 +574,7 @@ pub fn code( let desc = format!( "{}{}", - user.map(|s| format!("{}@", s)) - .unwrap_or_else(|| "".to_string()), + user.map_or_else(|| "".to_string(), |s| format!("{}@", s)), name ); @@ -594,7 +583,7 @@ pub fn code( if let DecryptedData::Login { totp, .. } = decrypted.data { if let Some(totp) = totp { - println!("{}", generate_totp(&totp)?) + println!("{}", generate_totp(&totp)?); } else { return Err(anyhow::anyhow!( "entry does not contain a totp secret" @@ -610,7 +599,7 @@ pub fn code( pub fn add( name: &str, username: Option<&str>, - uris: Vec<(String, Option)>, + uris: &[(String, Option)], folder: Option<&str>, ) -> anyhow::Result<()> { unlock()?; @@ -707,7 +696,7 @@ pub fn add( pub fn generate( name: Option<&str>, username: Option<&str>, - uris: Vec<(String, Option)>, + uris: &[(String, Option)], folder: Option<&str>, len: usize, ty: rbw::pwgen::Type, @@ -813,9 +802,7 @@ pub fn edit( let desc = format!( "{}{}", - username - .map(|s| format!("{}@", s)) - .unwrap_or_else(|| "".to_string()), + username.map_or_else(|| "".to_string(), |s| format!("{}@", s)), name ); @@ -874,7 +861,7 @@ pub fn edit( let data = rbw::db::EntryData::Login { username: entry_username.clone(), password, - uris: entry_uris.to_vec(), + uris: entry_uris.clone(), totp: entry_totp.clone(), }; (data, notes, history) @@ -918,9 +905,7 @@ pub fn remove( let desc = format!( "{}{}", - username - .map(|s| format!("{}@", s)) - .unwrap_or_else(|| "".to_string()), + username.map_or_else(|| "".to_string(), |s| format!("{}@", s)), name ); @@ -950,9 +935,7 @@ pub fn history( let desc = format!( "{}{}", - username - .map(|s| format!("{}@", s)) - .unwrap_or_else(|| "".to_string()), + username.map_or_else(|| "".to_string(), |s| format!("{}@", s)), name ); @@ -1017,7 +1000,7 @@ fn ensure_agent_once() -> anyhow::Result<()> { let agent_path = std::env::var("RBW_AGENT"); let agent_path = agent_path .as_ref() - .map(|s| s.as_str()) + .map(std::string::String::as_str) .unwrap_or("rbw-agent"); let status = std::process::Command::new(agent_path) .status() @@ -1045,6 +1028,8 @@ fn check_config() -> anyhow::Result<()> { fn version_or_quit() -> anyhow::Result { crate::actions::version().map_err(|e| { + // https://github.com/rust-lang/rust-clippy/issues/8003 + #[allow(clippy::let_underscore_drop)] let _ = crate::actions::quit(); e }) @@ -1056,26 +1041,23 @@ fn find_entry( username: Option<&str>, folder: Option<&str>, ) -> anyhow::Result<(rbw::db::Entry, DecryptedCipher)> { - match uuid::Uuid::parse_str(name) { - Ok(_) => { - for cipher in &db.entries { - if name == cipher.id { - return Ok((cipher.clone(), decrypt_cipher(cipher)?)); - } + if uuid::Uuid::parse_str(name).is_ok() { + for cipher in &db.entries { + if name == cipher.id { + return Ok((cipher.clone(), decrypt_cipher(cipher)?)); } - Err(anyhow::anyhow!("no entry found")) - } - Err(_) => { - let ciphers: Vec<(rbw::db::Entry, DecryptedCipher)> = db - .entries - .iter() - .cloned() - .map(|entry| { - decrypt_cipher(&entry).map(|decrypted| (entry, decrypted)) - }) - .collect::>()?; - find_entry_raw(&ciphers, name, username, folder) } + Err(anyhow::anyhow!("no entry found")) + } else { + let ciphers: Vec<(rbw::db::Entry, DecryptedCipher)> = db + .entries + .iter() + .cloned() + .map(|entry| { + decrypt_cipher(&entry).map(|decrypted| (entry, decrypted)) + }) + .collect::>()?; + find_entry_raw(&ciphers, name, username, folder) } } @@ -1447,32 +1429,35 @@ fn parse_editor(contents: &str) -> (Option, Option) { fn load_db() -> anyhow::Result { let config = rbw::config::Config::load()?; - if let Some(email) = &config.email { - rbw::db::Db::load(&config.server_name(), email) - .map_err(anyhow::Error::new) - } else { - Err(anyhow::anyhow!("failed to find email address in config")) - } + config.email.as_ref().map_or_else( + || Err(anyhow::anyhow!("failed to find email address in config")), + |email| { + rbw::db::Db::load(&config.server_name(), email) + .map_err(anyhow::Error::new) + }, + ) } fn save_db(db: &rbw::db::Db) -> anyhow::Result<()> { let config = rbw::config::Config::load()?; - if let Some(email) = &config.email { - db.save(&config.server_name(), email) - .map_err(anyhow::Error::new) - } else { - Err(anyhow::anyhow!("failed to find email address in config")) - } + config.email.as_ref().map_or_else( + || Err(anyhow::anyhow!("failed to find email address in config")), + |email| { + db.save(&config.server_name(), email) + .map_err(anyhow::Error::new) + }, + ) } fn remove_db() -> anyhow::Result<()> { let config = rbw::config::Config::load()?; - if let Some(email) = &config.email { - rbw::db::Db::remove(&config.server_name(), email) - .map_err(anyhow::Error::new) - } else { - Err(anyhow::anyhow!("failed to find email address in config")) - } + config.email.as_ref().map_or_else( + || Err(anyhow::anyhow!("failed to find email address in config")), + |email| { + rbw::db::Db::remove(&config.server_name(), email) + .map_err(anyhow::Error::new) + }, + ) } fn parse_totp_secret(secret: &str) -> anyhow::Result> { @@ -1680,3 +1665,13 @@ mod test { ) } } + +fn display_field(name: &str, field: Option<&str>) -> bool { + field.map_or_else( + || false, + |field| { + println!("{}: {}", name, field); + true + }, + ) +} diff --git a/src/bin/rbw/main.rs b/src/bin/rbw/main.rs index 85631c5..5730298 100644 --- a/src/bin/rbw/main.rs +++ b/src/bin/rbw/main.rs @@ -1,4 +1,15 @@ -#![allow(clippy::large_enum_variant)] +#![warn(clippy::cargo)] +#![warn(clippy::pedantic)] +#![warn(clippy::nursery)] +#![warn(clippy::as_conversions)] +#![warn(clippy::get_unwrap)] +#![allow(clippy::cognitive_complexity)] +#![allow(clippy::missing_const_for_fn)] +#![allow(clippy::similar_names)] +#![allow(clippy::struct_excessive_bools)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] +#![allow(clippy::type_complexity)] use anyhow::Context as _; use std::io::Write as _; @@ -319,7 +330,7 @@ fn main(opt: Opt) { } => commands::add( name, user.as_deref(), - uri.iter() + &uri.iter() // XXX not sure what the ui for specifying the match type // should be .map(|uri| (uri.clone(), None)) @@ -351,7 +362,7 @@ fn main(opt: Opt) { commands::generate( name.as_deref(), user.as_deref(), - uri.iter() + &uri.iter() // XXX not sure what the ui for specifying the match type // should be .map(|uri| (uri.clone(), None)) diff --git a/src/config.rs b/src/config.rs index bbc39f7..9b47590 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,9 +19,9 @@ pub struct Config { impl Default for Config { fn default() -> Self { Self { - email: Default::default(), - base_url: Default::default(), - identity_url: Default::default(), + email: None, + base_url: None, + identity_url: None, lock_timeout: default_lock_timeout(), pinentry: default_pinentry(), device_id: default_device_id(), @@ -29,23 +29,28 @@ impl Default for Config { } } +#[must_use] pub fn default_lock_timeout() -> u64 { 3600 } +#[must_use] pub fn default_pinentry() -> String { "pinentry".to_string() } +#[must_use] fn default_device_id() -> String { uuid::Uuid::new_v4().to_hyphenated().to_string() } +#[must_use] fn stub_device_id() -> String { String::from("fix") } impl Config { + #[must_use] pub fn new() -> Self { Self::default() } @@ -138,6 +143,7 @@ impl Config { Ok(()) } + #[must_use] pub fn base_url(&self) -> String { self.base_url.clone().map_or_else( || "https://api.bitwarden.com".to_string(), @@ -145,6 +151,7 @@ impl Config { ) } + #[must_use] pub fn identity_url(&self) -> String { self.identity_url.clone().unwrap_or_else(|| { self.base_url.clone().map_or_else( @@ -154,6 +161,7 @@ impl Config { }) } + #[must_use] pub fn server_name(&self) -> String { self.base_url .clone() diff --git a/src/db.rs b/src/db.rs index 5359321..cc9319e 100644 --- a/src/db.rs +++ b/src/db.rs @@ -106,6 +106,7 @@ impl<'de> serde::Deserialize<'de> for Uri { #[derive( serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq, )] +#[allow(clippy::large_enum_variant)] pub enum EntryData { Login { username: Option, @@ -173,6 +174,7 @@ pub struct Db { } impl Db { + #[must_use] pub fn new() -> Self { Self::default() } @@ -287,6 +289,7 @@ impl Db { Ok(()) } + #[must_use] pub fn needs_login(&self) -> bool { self.access_token.is_none() || self.refresh_token.is_none() diff --git a/src/dirs.rs b/src/dirs.rs index 285a0d5..bc97ed5 100644 --- a/src/dirs.rs +++ b/src/dirs.rs @@ -37,12 +37,14 @@ pub fn make_all() -> Result<()> { Ok(()) } +#[must_use] pub fn config_file() -> std::path::PathBuf { config_dir().join("config.json") } const INVALID_PATH: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS.add(b'/').add(b'%').add(b':'); +#[must_use] pub fn db_file(server: &str, email: &str) -> std::path::PathBuf { let server = percent_encoding::percent_encode(server.as_bytes(), INVALID_PATH) @@ -50,37 +52,45 @@ pub fn db_file(server: &str, email: &str) -> std::path::PathBuf { cache_dir().join(format!("{}:{}.json", server, email)) } +#[must_use] pub fn pid_file() -> std::path::PathBuf { runtime_dir().join("pidfile") } +#[must_use] pub fn agent_stdout_file() -> std::path::PathBuf { data_dir().join("agent.out") } +#[must_use] pub fn agent_stderr_file() -> std::path::PathBuf { data_dir().join("agent.err") } +#[must_use] pub fn socket_file() -> std::path::PathBuf { runtime_dir().join("socket") } +#[must_use] fn config_dir() -> std::path::PathBuf { let project_dirs = directories::ProjectDirs::from("", "", "rbw").unwrap(); project_dirs.config_dir().to_path_buf() } +#[must_use] fn cache_dir() -> std::path::PathBuf { let project_dirs = directories::ProjectDirs::from("", "", "rbw").unwrap(); project_dirs.cache_dir().to_path_buf() } +#[must_use] fn data_dir() -> std::path::PathBuf { let project_dirs = directories::ProjectDirs::from("", "", "rbw").unwrap(); project_dirs.data_dir().to_path_buf() } +#[must_use] fn runtime_dir() -> std::path::PathBuf { let project_dirs = directories::ProjectDirs::from("", "", "rbw").unwrap(); match project_dirs.runtime_dir() { diff --git a/src/edit.rs b/src/edit.rs index 1a831a7..8f4e534 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -30,6 +30,7 @@ pub fn edit(contents: &str, help: &str) -> Result { let editor = std::path::Path::new(&editor); let mut editor_args = vec![]; + #[allow(clippy::single_match)] // more to come match editor.file_name() { Some(editor) => match editor.to_str() { Some("vim" | "nvim") => { diff --git a/src/lib.rs b/src/lib.rs index 4a13e25..db615c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,18 @@ +#![warn(clippy::cargo)] #![warn(clippy::pedantic)] #![warn(clippy::nursery)] -#![allow(clippy::default_trait_access)] -#![allow(clippy::implicit_hasher)] -#![allow(clippy::large_enum_variant)] +#![warn(clippy::as_conversions)] +#![warn(clippy::get_unwrap)] +#![allow(clippy::cognitive_complexity)] #![allow(clippy::missing_const_for_fn)] -#![allow(clippy::missing_errors_doc)] -#![allow(clippy::missing_panics_doc)] -#![allow(clippy::must_use_candidate)] #![allow(clippy::similar_names)] -#![allow(clippy::single_match)] +#![allow(clippy::struct_excessive_bools)] #![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_lines)] #![allow(clippy::type_complexity)] -#![allow(clippy::unused_async)] +// we aren't really documenting apis anyway +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::missing_panics_doc)] pub mod actions; pub mod api; diff --git a/src/locked.rs b/src/locked.rs index 4ddf021..bfa642d 100644 --- a/src/locked.rs +++ b/src/locked.rs @@ -18,10 +18,12 @@ impl Default for Vec { } impl Vec { + #[must_use] pub fn new() -> Self { Self::default() } + #[must_use] pub fn data(&self) -> &[u8] { self.data.as_slice() } @@ -65,10 +67,12 @@ pub struct Password { } impl Password { + #[must_use] pub fn new(password: Vec) -> Self { Self { password } } + #[must_use] pub fn password(&self) -> &[u8] { self.password.data() } @@ -80,14 +84,17 @@ pub struct Keys { } impl Keys { + #[must_use] pub fn new(keys: Vec) -> Self { Self { keys } } + #[must_use] pub fn enc_key(&self) -> &[u8] { &self.keys.data()[0..32] } + #[must_use] pub fn mac_key(&self) -> &[u8] { &self.keys.data()[32..64] } @@ -99,10 +106,12 @@ pub struct PasswordHash { } impl PasswordHash { + #[must_use] pub fn new(hash: Vec) -> Self { Self { hash } } + #[must_use] pub fn hash(&self) -> &[u8] { self.hash.data() } @@ -114,10 +123,12 @@ pub struct PrivateKey { } impl PrivateKey { + #[must_use] pub fn new(private_key: Vec) -> Self { Self { private_key } } + #[must_use] pub fn private_key(&self) -> &[u8] { self.private_key.data() } @@ -130,6 +141,7 @@ pub struct ApiKey { } impl ApiKey { + #[must_use] pub fn new(client_id: Password, client_secret: Password) -> Self { Self { client_id, @@ -137,10 +149,12 @@ impl ApiKey { } } + #[must_use] pub fn client_id(&self) -> &[u8] { self.client_id.password() } + #[must_use] pub fn client_secret(&self) -> &[u8] { self.client_secret.password() } diff --git a/src/pinentry.rs b/src/pinentry.rs index b4d2bb0..ce1227f 100644 --- a/src/pinentry.rs +++ b/src/pinentry.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use std::convert::TryFrom as _; use tokio::io::AsyncWriteExt as _; pub async fn getpin( @@ -163,7 +164,10 @@ fn percent_decode(buf: &mut [u8]) -> usize { if let Some(h) = char::from(buf[read_idx + 1]).to_digit(16) { #[allow(clippy::cast_possible_truncation)] if let Some(l) = char::from(buf[read_idx + 2]).to_digit(16) { - c = h as u8 * 0x10 + l as u8; + // h and l were parsed from a single hex digit, so they + // must be in the range 0-15, so these unwraps are safe + c = u8::try_from(h).unwrap() * 0x10 + + u8::try_from(l).unwrap(); read_idx += 2; } } diff --git a/src/protocol.rs b/src/protocol.rs index 14fa7f9..a10d301 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -3,6 +3,7 @@ // eventually it would be nice to make this a const function so that we could // just get the version from a variable directly, but this is fine for now +#[must_use] pub fn version() -> u32 { let major = env!("CARGO_PKG_VERSION_MAJOR"); let minor = env!("CARGO_PKG_VERSION_MINOR"); diff --git a/src/pwgen.rs b/src/pwgen.rs index 55151e6..a112d73 100644 --- a/src/pwgen.rs +++ b/src/pwgen.rs @@ -15,6 +15,7 @@ pub enum Type { Diceware, } +#[must_use] pub fn pwgen(ty: Type, len: usize) -> String { let mut rng = rand::thread_rng(); @@ -101,6 +102,6 @@ mod test { for c in s.chars() { set.insert(c); } - assert!(set.len() < s.len()) + assert!(set.len() < s.len()); } } -- cgit v1.2.3-54-g00ecf