From 3765d4f2edc5933f61fd46ab130e366774556cf5 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sat, 18 Apr 2020 04:24:26 -0400 Subject: implement remove --- src/actions.rs | 24 ++++++++++++++++++++++++ src/api.rs | 19 +++++++++++++++++++ src/bin/rbw/commands.rs | 32 ++++++++++++++++++++++++++++++-- src/bin/rbw/main.rs | 15 ++++++++++++--- src/db.rs | 1 + 5 files changed, 86 insertions(+), 5 deletions(-) diff --git a/src/actions.rs b/src/actions.rs index ad99b5f..60cc61d 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -102,6 +102,30 @@ fn add_once( Ok(()) } +pub fn remove( + access_token: &str, + refresh_token: &str, + id: &str, +) -> Result> { + match remove_once(access_token, id) { + Ok(()) => Ok(None), + Err(crate::error::Error::RequestUnauthorized) => { + let access_token = exchange_refresh_token(refresh_token)?; + remove_once(&access_token, id)?; + Ok(Some(access_token)) + } + Err(e) => Err(e), + } +} + +fn remove_once(access_token: &str, id: &str) -> Result<()> { + let config = crate::config::Config::load()?; + let client = + crate::api::Client::new(&config.base_url(), &config.identity_url()); + client.remove(access_token, id)?; + Ok(()) +} + fn exchange_refresh_token(refresh_token: &str) -> Result { let config = crate::config::Config::load()?; let client = diff --git a/src/api.rs b/src/api.rs index 9588b4c..f40ff6d 100644 --- a/src/api.rs +++ b/src/api.rs @@ -159,6 +159,7 @@ struct SyncResCipher { impl SyncResCipher { fn to_entry(&self) -> crate::db::Entry { crate::db::Entry { + id: self.id.clone(), name: self.name.clone(), username: self.login.username.clone(), password: self.login.password.clone(), @@ -310,6 +311,24 @@ impl Client { } } + pub fn remove(&self, access_token: &str, id: &str) -> Result<()> { + let client = reqwest::blocking::Client::new(); + let res = client + .delete(&self.api_url(&format!("/ciphers/{}", id))) + .header("Authorization", format!("Bearer {}", access_token)) + .send() + .context(crate::error::Reqwest)?; + match res.status() { + reqwest::StatusCode::OK => Ok(()), + reqwest::StatusCode::UNAUTHORIZED => { + Err(Error::RequestUnauthorized) + } + _ => Err(Error::RequestFailed { + status: res.status().as_u16(), + }), + } + } + pub fn exchange_refresh_token( &self, refresh_token: &str, diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs index 30c5d1e..f3977d8 100644 --- a/src/bin/rbw/commands.rs +++ b/src/bin/rbw/commands.rs @@ -2,6 +2,7 @@ use anyhow::Context as _; #[derive(Debug, Clone)] struct DecryptedCipher { + id: String, name: String, username: Option, password: Option, @@ -211,10 +212,36 @@ pub fn edit() -> anyhow::Result<()> { todo!() } -pub fn remove() -> anyhow::Result<()> { +pub fn remove(name: &str, username: Option<&str>) -> anyhow::Result<()> { unlock()?; - todo!() + let email = config_email()?; + let mut db = rbw::db::Db::load(&email) + .context("failed to load password database")?; + let access_token = db.access_token.as_ref().unwrap(); + let refresh_token = db.refresh_token.as_ref().unwrap(); + + let desc = format!( + "{}{}", + username + .map(|s| format!("{}@", s)) + .unwrap_or_else(|| "".to_string()), + name + ); + + let entry = find_entry(&db, name, username) + .with_context(|| format!("couldn't find entry for '{}'", desc))?; + + if let Some(access_token) = + rbw::actions::remove(&access_token, &refresh_token, &entry.id)? + { + db.access_token = Some(access_token); + db.save(&email).context("failed to save database")?; + } + + crate::actions::sync()?; + + Ok(()) } pub fn lock() -> anyhow::Result<()> { @@ -308,6 +335,7 @@ fn find_entry( fn decrypt_cipher(entry: rbw::db::Entry) -> anyhow::Result { Ok(DecryptedCipher { + id: entry.id.clone(), name: crate::actions::decrypt(&entry.name)?, username: entry .username diff --git a/src/bin/rbw/main.rs b/src/bin/rbw/main.rs index 136322a..45f1985 100644 --- a/src/bin/rbw/main.rs +++ b/src/bin/rbw/main.rs @@ -30,7 +30,7 @@ fn main() { .subcommand( clap::SubCommand::with_name("add") .arg(clap::Arg::with_name("name").required(true)) - .arg(clap::Arg::with_name("user").required(true)), + .arg(clap::Arg::with_name("user")), ) .subcommand( clap::SubCommand::with_name("generate") @@ -54,7 +54,11 @@ fn main() { ])), ) .subcommand(clap::SubCommand::with_name("edit")) - .subcommand(clap::SubCommand::with_name("remove")) + .subcommand( + clap::SubCommand::with_name("remove") + .arg(clap::Arg::with_name("name").required(true)) + .arg(clap::Arg::with_name("user")), + ) .subcommand(clap::SubCommand::with_name("lock")) .subcommand(clap::SubCommand::with_name("purge")) .subcommand(clap::SubCommand::with_name("stop-agent")) @@ -119,7 +123,12 @@ fn main() { } } ("edit", Some(_)) => commands::edit().context("edit"), - ("remove", Some(_)) => commands::remove().context("remove"), + // this unwrap is safe because name is marked .required(true) + ("remove", Some(smatches)) => commands::remove( + smatches.value_of("name").unwrap(), + smatches.value_of("user"), + ) + .context("remove"), ("lock", Some(_)) => commands::lock().context("lock"), ("purge", Some(_)) => commands::purge().context("purge"), ("stop-agent", Some(_)) => { diff --git a/src/db.rs b/src/db.rs index 6e73852..4ac6e9c 100644 --- a/src/db.rs +++ b/src/db.rs @@ -5,6 +5,7 @@ use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct Entry { + pub id: String, pub name: String, pub username: Option, pub password: Option, -- cgit v1.2.3-54-g00ecf