diff options
author | Jesse Luehrs <doy@tozt.net> | 2021-02-21 21:57:38 -0500 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2021-02-21 23:37:43 -0500 |
commit | 9b5bc94b5de8c7c1e226887f5e986ecef5967d06 (patch) | |
tree | 8113636907b2e2ef6fd6ccbb1553d3d83963ec4f /src | |
parent | 0d1ef66412244aa35cde321961006e9ee7735cdf (diff) | |
download | rbw-9b5bc94b5de8c7c1e226887f5e986ecef5967d06.tar.gz rbw-9b5bc94b5de8c7c1e226887f5e986ecef5967d06.zip |
persist uri match type when editing
Diffstat (limited to 'src')
-rw-r--r-- | src/api.rs | 50 | ||||
-rw-r--r-- | src/bin/rbw/commands.rs | 50 | ||||
-rw-r--r-- | src/bin/rbw/main.rs | 12 | ||||
-rw-r--r-- | src/db.rs | 87 | ||||
-rw-r--r-- | src/error.rs | 3 |
5 files changed, 187 insertions, 15 deletions
@@ -4,6 +4,41 @@ use crate::json::{ DeserializeJsonWithPath as _, DeserializeJsonWithPathAsync as _, }; +#[derive( + serde_repr::Serialize_repr, + serde_repr::Deserialize_repr, + Debug, + Copy, + Clone, + PartialEq, + Eq, +)] +#[repr(u8)] +pub enum UriMatchType { + Domain = 0, + Host = 1, + StartsWith = 2, + Exact = 3, + RegularExpression = 4, + Never = 5, +} + +impl std::fmt::Display for UriMatchType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[allow(clippy::enum_glob_use)] + use UriMatchType::*; + let s = match self { + Domain => "domain", + Host => "host", + StartsWith => "starts_with", + Exact => "exact", + RegularExpression => "regular_expression", + Never => "never", + }; + write!(f, "{}", s) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum TwoFactorProviderType { Authenticator = 0, @@ -245,7 +280,12 @@ impl SyncResCipher { std::vec::Vec::new, |uris| { uris.iter() - .filter_map(|uri| uri.uri.clone()) + .filter_map(|uri| { + uri.uri.clone().map(|s| crate::db::Uri { + uri: s, + match_type: uri.match_type, + }) + }) .collect() }, ), @@ -351,6 +391,8 @@ struct CipherLogin { struct CipherLoginUri { #[serde(rename = "Uri")] uri: Option<String>, + #[serde(rename = "Match")] + match_type: Option<UriMatchType>, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] @@ -648,7 +690,8 @@ impl Client { Some( uris.iter() .map(|s| CipherLoginUri { - uri: Some(s.to_string()), + uri: Some(s.uri.to_string()), + match_type: s.match_type, }) .collect(), ) @@ -780,7 +823,8 @@ impl Client { Some( uris.iter() .map(|s| CipherLoginUri { - uri: Some(s.to_string()), + uri: Some(s.uri.to_string()), + match_type: s.match_type, }) .collect(), ) diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs index 49c44a4..3f11174 100644 --- a/src/bin/rbw/commands.rs +++ b/src/bin/rbw/commands.rs @@ -93,7 +93,14 @@ impl DecryptedCipher { if let Some(uris) = uris { for uri in uris { - displayed |= self.display_field("URI", Some(uri)); + displayed |= + self.display_field("URI", Some(&uri.uri)); + let match_type = + uri.match_type.map(|ty| format!("{}", ty)); + displayed |= self.display_field( + "Match type", + match_type.as_deref(), + ); } } @@ -312,7 +319,7 @@ enum DecryptedData { username: Option<String>, password: Option<String>, totp: Option<String>, - uris: Option<Vec<String>>, + uris: Option<Vec<DecryptedUri>>, }, Card { cardholder_name: Option<String>, @@ -358,6 +365,13 @@ struct DecryptedHistoryEntry { password: String, } +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +struct DecryptedUri { + uri: String, + match_type: Option<rbw::api::UriMatchType>, +} + enum ListField { Name, Id, @@ -587,7 +601,7 @@ pub fn code( pub fn add( name: &str, username: Option<&str>, - uris: Vec<String>, + uris: Vec<(String, Option<rbw::api::UriMatchType>)>, folder: Option<&str>, ) -> anyhow::Result<()> { unlock()?; @@ -613,9 +627,14 @@ pub fn add( let notes = notes .map(|notes| crate::actions::encrypt(¬es, None)) .transpose()?; - let uris: Vec<String> = uris + let uris: Vec<_> = uris .iter() - .map(|uri| crate::actions::encrypt(&uri, None)) + .map(|uri| { + Ok(rbw::db::Uri { + uri: crate::actions::encrypt(&uri.0, None)?, + match_type: uri.1, + }) + }) .collect::<anyhow::Result<_>>()?; let mut folder_id = None; @@ -679,7 +698,7 @@ pub fn add( pub fn generate( name: Option<&str>, username: Option<&str>, - uris: Vec<String>, + uris: Vec<(String, Option<rbw::api::UriMatchType>)>, folder: Option<&str>, len: usize, ty: rbw::pwgen::Type, @@ -701,9 +720,14 @@ pub fn generate( .map(|username| crate::actions::encrypt(username, None)) .transpose()?; let password = crate::actions::encrypt(&password, None)?; - let uris: Vec<String> = uris + let uris: Vec<_> = uris .iter() - .map(|uri| crate::actions::encrypt(&uri, None)) + .map(|uri| { + Ok(rbw::db::Uri { + uri: crate::actions::encrypt(&uri.0, None)?, + match_type: uri.1, + }) + }) .collect::<anyhow::Result<_>>()?; let mut folder_id = None; @@ -1221,7 +1245,15 @@ fn decrypt_cipher(entry: &rbw::db::Entry) -> anyhow::Result<DecryptedCipher> { uris: uris .iter() .map(|s| { - decrypt_field("uri", Some(s), entry.org_id.as_deref()) + decrypt_field( + "uri", + Some(&s.uri), + entry.org_id.as_deref(), + ) + .map(|uri| DecryptedUri { + uri, + match_type: s.match_type, + }) }) .collect(), }, diff --git a/src/bin/rbw/main.rs b/src/bin/rbw/main.rs index decdeb4..badff67 100644 --- a/src/bin/rbw/main.rs +++ b/src/bin/rbw/main.rs @@ -299,7 +299,11 @@ fn main(opt: Opt) { } => commands::add( &name, user.as_deref(), - uri.to_vec(), + uri.iter() + // XXX not sure what the ui for specifying the match type + // should be + .map(|uri| (uri.clone(), None)) + .collect::<Vec<_>>(), folder.as_deref(), ), Opt::Generate { @@ -327,7 +331,11 @@ fn main(opt: Opt) { commands::generate( name.as_deref(), user.as_deref(), - uri.to_vec(), + uri.iter() + // XXX not sure what the ui for specifying the match type + // should be + .map(|uri| (uri.clone(), None)) + .collect::<Vec<_>>(), folder.as_deref(), *len, ty, @@ -18,6 +18,91 @@ pub struct Entry { pub history: Vec<HistoryEntry>, } +#[derive(serde::Serialize, Debug, Clone, Eq, PartialEq)] +pub struct Uri { + pub uri: String, + pub match_type: Option<crate::api::UriMatchType>, +} + +// backwards compatibility +impl<'de> serde::Deserialize<'de> for Uri { + fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + struct StringOrUri; + impl<'de> serde::de::Visitor<'de> for StringOrUri { + type Value = Uri; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("uri") + } + + fn visit_str<E>( + self, + value: &str, + ) -> std::result::Result<Self::Value, E> + where + E: serde::de::Error, + { + Ok(Uri { + uri: value.to_string(), + match_type: None, + }) + } + + fn visit_map<M>( + self, + mut map: M, + ) -> std::result::Result<Self::Value, M::Error> + where + M: serde::de::MapAccess<'de>, + { + let mut uri = None; + let mut match_type = None; + while let Some(key) = map.next_key()? { + match key { + "uri" => { + if uri.is_some() { + return Err( + serde::de::Error::duplicate_field("uri"), + ); + } + uri = Some(map.next_value()?); + } + "match_type" => { + if match_type.is_some() { + return Err( + serde::de::Error::duplicate_field( + "match_type", + ), + ); + } + match_type = map.next_value()?; + } + _ => { + return Err(serde::de::Error::unknown_field( + key, + &["uri", "match_type"], + )) + } + } + } + + uri.map_or_else( + || Err(serde::de::Error::missing_field("uri")), + |uri| Ok(Self::Value { uri, match_type }), + ) + } + } + + deserializer.deserialize_any(StringOrUri) + } +} + #[derive( serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq, )] @@ -26,7 +111,7 @@ pub enum EntryData { username: Option<String>, password: Option<String>, totp: Option<String>, - uris: Vec<String>, + uris: Vec<Uri>, }, Card { cardholder_name: Option<String>, diff --git a/src/error.rs b/src/error.rs index 7201ce0..6e8e6da 100644 --- a/src/error.rs +++ b/src/error.rs @@ -96,6 +96,9 @@ pub enum Error { #[snafu(display("openssl error"))] OpenSSL { source: openssl::error::ErrorStack }, + #[snafu(display("failed to parse match type {}", s))] + ParseMatchType { s: String }, + #[snafu(display("pbkdf2 requires at least 1 iteration (got 0)"))] Pbkdf2ZeroIterations, |