aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--Cargo.lock12
-rw-r--r--Cargo.toml1
-rw-r--r--src/api.rs50
-rw-r--r--src/bin/rbw/commands.rs50
-rw-r--r--src/bin/rbw/main.rs12
-rw-r--r--src/db.rs87
-rw-r--r--src/error.rs3
8 files changed, 202 insertions, 15 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6dd5e7a..a025c4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@
### Fixed
* Stop hardcoding /tmp when using the fallback runtime directory (#37, pschmitt)
+* Fix `rbw edit` clearing the match detection setting for websites associated
+ with the edited password (#34, AdmiralNemo)
## [0.5.2] - 2020-12-02
diff --git a/Cargo.lock b/Cargo.lock
index 4b0b3c0..fc2939f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1253,6 +1253,7 @@ dependencies = [
"serde",
"serde_json",
"serde_path_to_error",
+ "serde_repr",
"snafu",
"structopt",
"tempfile",
@@ -1484,6 +1485,17 @@ dependencies = [
]
[[package]]
+name = "serde_repr"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "serde_urlencoded"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 8db854c..c65dbcd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,6 +38,7 @@ ring = "0.16"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_path_to_error = "0.1"
+serde_repr = "0.1"
snafu = "0.6"
structopt = { version = "0.3", features = ["paw", "wrap_help"] }
tempfile = "3.2"
diff --git a/src/api.rs b/src/api.rs
index ca92e25..40fe422 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -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(&notes, 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,
diff --git a/src/db.rs b/src/db.rs
index d519f00..067f36f 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -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,