From 988296d6c9e053d632ee5610ba3432a02776b132 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sun, 19 Apr 2020 05:10:23 -0400 Subject: track password history --- Cargo.lock | 1 + Cargo.toml | 1 + src/actions.rs | 14 ++++++++++++-- src/api.rs | 40 ++++++++++++++++++++++++++++++++++++++++ src/bin/rbw/commands.rs | 31 +++++++++++++++++++++++++++++++ src/db.rs | 9 +++++++++ src/lib.rs | 1 + 7 files changed, 95 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9545da..e0ce3e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1147,6 +1147,7 @@ dependencies = [ "env_logger", "hkdf", "hmac", + "humantime", "log", "nix", "pbkdf2", diff --git a/Cargo.toml b/Cargo.toml index aeb27b9..4e3ac4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ directories = "*" env_logger = "*" hkdf = "*" hmac = "*" +humantime = "*" log = "*" nix = "*" pbkdf2 = "*" diff --git a/src/actions.rs b/src/actions.rs index 4dbe4ca..0a5bee4 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -105,9 +105,10 @@ pub fn edit( username: Option<&str>, password: Option<&str>, notes: Option<&str>, + history: &[crate::db::HistoryEntry], ) -> Result<(Option, ())> { with_exchange_refresh_token(access_token, refresh_token, |access_token| { - edit_once(access_token, id, name, username, password, notes) + edit_once(access_token, id, name, username, password, notes, history) }) } @@ -118,11 +119,20 @@ fn edit_once( username: Option<&str>, password: Option<&str>, notes: Option<&str>, + history: &[crate::db::HistoryEntry], ) -> Result<()> { let config = crate::config::Config::load()?; let client = crate::api::Client::new(&config.base_url(), &config.identity_url()); - client.edit(access_token, id, name, username, password, notes)?; + client.edit( + access_token, + id, + name, + username, + password, + notes, + history, + )?; Ok(()) } diff --git a/src/api.rs b/src/api.rs index d5511c5..efceda8 100644 --- a/src/api.rs +++ b/src/api.rs @@ -106,6 +106,8 @@ struct CiphersPutReq { name: String, notes: Option, login: CiphersPutReqLogin, + #[serde(rename = "passwordHistory")] + password_history: Vec, } #[derive(serde::Serialize, Debug)] @@ -114,6 +116,14 @@ struct CiphersPutReqLogin { password: Option, } +#[derive(serde::Serialize, Debug)] +struct CiphersPutReqHistory { + #[serde(rename = "LastUsedDate")] + last_used_date: String, + #[serde(rename = "Password")] + password: String, +} + #[derive(serde::Deserialize, Debug)] struct CiphersRes { #[serde(rename = "FolderId")] @@ -181,16 +191,30 @@ struct SyncResCipher { login: SyncResLogin, #[serde(rename = "Notes")] notes: Option, + #[serde(rename = "PasswordHistory")] + password_history: Option>, } impl SyncResCipher { fn to_entry(&self) -> crate::db::Entry { + let history = if let Some(history) = &self.password_history { + history + .iter() + .map(|entry| crate::db::HistoryEntry { + last_used_date: entry.last_used_date.clone(), + password: entry.password.clone(), + }) + .collect() + } else { + vec![] + }; crate::db::Entry { id: self.id.clone(), name: self.name.clone(), username: self.login.username.clone(), password: self.login.password.clone(), notes: self.notes.clone(), + history, } } } @@ -203,6 +227,14 @@ struct SyncResLogin { password: Option, } +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +struct SyncResPasswordHistory { + #[serde(rename = "LastUsedDate")] + last_used_date: String, + #[serde(rename = "Password")] + password: String, +} + #[derive(Debug)] pub struct Client { base_url: String, @@ -359,6 +391,7 @@ impl Client { username: Option<&str>, password: Option<&str>, notes: Option<&str>, + history: &[crate::db::HistoryEntry], ) -> Result<()> { let req = CiphersPutReq { ty: 1, @@ -368,6 +401,13 @@ impl Client { username: username.map(std::string::ToString::to_string), password: password.map(std::string::ToString::to_string), }, + password_history: history + .iter() + .map(|entry| CiphersPutReqHistory { + last_used_date: entry.last_used_date.clone(), + password: entry.password.clone(), + }) + .collect(), }; let client = reqwest::blocking::Client::new(); let res = client diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs index 1b6f9ee..7152a59 100644 --- a/src/bin/rbw/commands.rs +++ b/src/bin/rbw/commands.rs @@ -8,6 +8,14 @@ struct DecryptedCipher { username: Option, password: Option, notes: Option, + history: Vec, +} + +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +struct DecryptedHistoryEntry { + last_used_date: String, + password: String, } const HELP: &str = r#" @@ -231,6 +239,15 @@ pub fn edit(name: &str, username: Option<&str>) -> anyhow::Result<()> { let notes = notes .map(|notes| crate::actions::encrypt(¬es)) .transpose()?; + let mut history = entry.history.clone(); + let new_history_entry = rbw::db::HistoryEntry { + last_used_date: format!( + "{}", + humantime::format_rfc3339(std::time::SystemTime::now()) + ), + password: entry.password.unwrap_or_else(String::new), + }; + history.insert(0, new_history_entry); if let (Some(access_token), ()) = rbw::actions::edit( &access_token, @@ -240,6 +257,7 @@ pub fn edit(name: &str, username: Option<&str>) -> anyhow::Result<()> { entry.username.as_deref(), password.as_deref(), notes.as_deref(), + &history, )? { db.access_token = Some(access_token); db.save(&email).context("failed to save database")?; @@ -453,12 +471,23 @@ fn decrypt_cipher(entry: &rbw::db::Entry) -> anyhow::Result { None } }; + let history = entry + .history + .iter() + .map(|entry| { + Ok(DecryptedHistoryEntry { + last_used_date: entry.last_used_date.clone(), + password: crate::actions::decrypt(&entry.password)?, + }) + }) + .collect::>()?; Ok(DecryptedCipher { id: entry.id.clone(), name: crate::actions::decrypt(&entry.name)?, username, password, notes, + history, }) } @@ -581,6 +610,7 @@ mod test { .map(|_| "this is the encrypted username".to_string()), password: None, notes: None, + history: vec![], }, DecryptedCipher { id: "irrelevant".to_string(), @@ -588,6 +618,7 @@ mod test { username: username.map(std::string::ToString::to_string), password: None, notes: None, + history: vec![], }, ) } diff --git a/src/db.rs b/src/db.rs index 54f8179..9a02598 100644 --- a/src/db.rs +++ b/src/db.rs @@ -12,6 +12,15 @@ pub struct Entry { pub username: Option, pub password: Option, pub notes: Option, + pub history: Vec, +} + +#[derive( + serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq, +)] +pub struct HistoryEntry { + pub last_used_date: String, + pub password: String, } #[derive(serde::Serialize, serde::Deserialize, Default, Debug)] diff --git a/src/lib.rs b/src/lib.rs index 12c8c6e..ef69e49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ #![allow(clippy::must_use_candidate)] #![allow(clippy::similar_names)] #![allow(clippy::single_match)] +#![allow(clippy::too_many_arguments)] pub mod actions; pub mod api; -- cgit v1.2.3-54-g00ecf