diff options
author | Jesse Luehrs <doy@tozt.net> | 2020-04-19 23:00:47 -0400 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2020-04-19 23:00:47 -0400 |
commit | 6f10a47adc2fb42ffc965b954b9285123a82c094 (patch) | |
tree | 725b2b8cce4fe6e7c0b51f96d3d65a43928d1f2f | |
parent | 7a32c43713514c02f783d3c8e0835229e7f59a83 (diff) | |
download | rbw-6f10a47adc2fb42ffc965b954b9285123a82c094.tar.gz rbw-6f10a47adc2fb42ffc965b954b9285123a82c094.zip |
implement adding into a folder
-rw-r--r-- | src/actions.rs | 55 | ||||
-rw-r--r-- | src/api.rs | 82 | ||||
-rw-r--r-- | src/bin/rbw/commands.rs | 80 | ||||
-rw-r--r-- | src/bin/rbw/main.rs | 12 | ||||
-rw-r--r-- | src/lib.rs | 1 |
5 files changed, 226 insertions, 4 deletions
diff --git a/src/actions.rs b/src/actions.rs index f0d5d21..67a9523 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -78,9 +78,18 @@ pub fn add( password: Option<&str>, notes: Option<&str>, uris: &[String], + folder_id: Option<&str>, ) -> Result<(Option<String>, ())> { with_exchange_refresh_token(access_token, refresh_token, |access_token| { - add_once(access_token, name, username, password, notes, uris) + add_once( + access_token, + name, + username, + password, + notes, + uris, + folder_id, + ) }) } @@ -91,11 +100,20 @@ fn add_once( password: Option<&str>, notes: Option<&str>, uris: &[String], + folder_id: Option<&str>, ) -> Result<()> { let config = crate::config::Config::load()?; let client = crate::api::Client::new(&config.base_url(), &config.identity_url()); - client.add(access_token, name, username, password, notes, uris)?; + client.add( + access_token, + name, + username, + password, + notes, + uris, + folder_id.as_deref(), + )?; Ok(()) } @@ -156,6 +174,39 @@ fn remove_once(access_token: &str, id: &str) -> Result<()> { Ok(()) } +pub fn list_folders( + access_token: &str, + refresh_token: &str, +) -> Result<(Option<String>, Vec<(String, String)>)> { + with_exchange_refresh_token(access_token, refresh_token, |access_token| { + list_folders_once(access_token) + }) +} + +fn list_folders_once(access_token: &str) -> Result<Vec<(String, String)>> { + let config = crate::config::Config::load()?; + let client = + crate::api::Client::new(&config.base_url(), &config.identity_url()); + client.folders(access_token) +} + +pub fn create_folder( + access_token: &str, + refresh_token: &str, + name: &str, +) -> Result<(Option<String>, String)> { + with_exchange_refresh_token(access_token, refresh_token, |access_token| { + create_folder_once(access_token, name) + }) +} + +fn create_folder_once(access_token: &str, name: &str) -> Result<String> { + let config = crate::config::Config::load()?; + let client = + crate::api::Client::new(&config.base_url(), &config.identity_url()); + client.create_folder(access_token, name) +} + fn with_exchange_refresh_token<F, T>( access_token: &str, refresh_token: &str, @@ -141,6 +141,8 @@ struct SyncResPasswordHistory { struct CiphersPostReq { #[serde(rename = "type")] ty: u32, // XXX what are the valid types? + #[serde(rename = "folderId")] + folder_id: Option<String>, name: String, notes: Option<String>, login: CiphersPostReqLogin, @@ -183,6 +185,25 @@ struct CiphersPutReqHistory { password: String, } +#[derive(serde::Deserialize, Debug)] +struct FoldersRes { + #[serde(rename = "Data")] + data: Vec<FoldersResData>, +} + +#[derive(serde::Deserialize, Debug)] +struct FoldersResData { + #[serde(rename = "Id")] + id: String, + #[serde(rename = "Name")] + name: String, +} + +#[derive(serde::Serialize, Debug)] +struct FoldersPostReq { + name: String, +} + #[derive(Debug)] pub struct Client { base_url: String, @@ -299,9 +320,11 @@ impl Client { password: Option<&str>, notes: Option<&str>, uris: &[String], + folder_id: Option<&str>, ) -> Result<()> { let req = CiphersPostReq { ty: 1, + folder_id: folder_id.map(std::string::ToString::to_string), name: name.to_string(), notes: notes.map(std::string::ToString::to_string), login: CiphersPostReqLogin { @@ -396,6 +419,65 @@ impl Client { } } + pub fn folders( + &self, + access_token: &str, + ) -> Result<Vec<(String, String)>> { + let client = reqwest::blocking::Client::new(); + let res = client + .get(&self.api_url("/folders")) + .header("Authorization", format!("Bearer {}", access_token)) + .send() + .context(crate::error::Reqwest)?; + match res.status() { + reqwest::StatusCode::OK => { + let folders_res: FoldersRes = + res.json().context(crate::error::Reqwest)?; + Ok(folders_res + .data + .iter() + .map(|folder| (folder.id.clone(), folder.name.clone())) + .collect()) + } + reqwest::StatusCode::UNAUTHORIZED => { + Err(Error::RequestUnauthorized) + } + _ => Err(Error::RequestFailed { + status: res.status().as_u16(), + }), + } + } + + pub fn create_folder( + &self, + access_token: &str, + name: &str, + ) -> Result<String> { + let req = FoldersPostReq { + name: name.to_string(), + }; + let client = reqwest::blocking::Client::new(); + let res = client + .post(&self.api_url("/folders")) + .header("Authorization", format!("Bearer {}", access_token)) + .json(&req) + .send() + .context(crate::error::Reqwest)?; + match res.status() { + reqwest::StatusCode::OK => { + let folders_res: FoldersResData = + res.json().context(crate::error::Reqwest)?; + Ok(folders_res.id) + } + 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 317d62b..ee30950 100644 --- a/src/bin/rbw/commands.rs +++ b/src/bin/rbw/commands.rs @@ -170,6 +170,7 @@ pub fn add( name: &str, username: Option<&str>, uris: Vec<&str>, + folder: Option<&str>, ) -> anyhow::Result<()> { unlock()?; @@ -177,7 +178,7 @@ pub fn add( let mut db = rbw::db::Db::load(&email)?; // unwrap is safe here because the call to unlock above is guaranteed to // populate these or error - let access_token = db.access_token.as_ref().unwrap(); + let mut access_token = db.access_token.as_ref().unwrap().clone(); let refresh_token = db.refresh_token.as_ref().unwrap(); let name = crate::actions::encrypt(name)?; @@ -200,6 +201,42 @@ pub fn add( .map(|uri| crate::actions::encrypt(&uri)) .collect::<anyhow::Result<_>>()?; + let mut folder_id = None; + if let Some(folder_name) = folder { + let (new_access_token, folders) = + rbw::actions::list_folders(&access_token, &refresh_token)?; + if let Some(new_access_token) = new_access_token { + access_token = new_access_token.clone(); + db.access_token = Some(new_access_token); + db.save(&email).context("failed to save database")?; + } + + let folders: Vec<(String, String)> = folders + .iter() + .cloned() + .map(|(id, name)| Ok((id, crate::actions::decrypt(&name)?))) + .collect::<anyhow::Result<_>>()?; + + for (id, name) in folders { + if name == folder_name { + folder_id = Some(id); + } + } + if folder_id.is_none() { + let (new_access_token, id) = rbw::actions::create_folder( + &access_token, + &refresh_token, + &crate::actions::encrypt(folder_name)?, + )?; + if let Some(new_access_token) = new_access_token { + access_token = new_access_token.clone(); + db.access_token = Some(new_access_token); + db.save(&email).context("failed to save database")?; + } + folder_id = Some(id); + } + } + if let (Some(access_token), ()) = rbw::actions::add( &access_token, &refresh_token, @@ -208,6 +245,7 @@ pub fn add( password.as_deref(), notes.as_deref(), &uris, + folder_id.as_deref(), )? { db.access_token = Some(access_token); db.save(&email).context("failed to save database")?; @@ -222,6 +260,7 @@ pub fn generate( name: Option<&str>, username: Option<&str>, uris: Vec<&str>, + folder: Option<&str>, len: usize, ty: rbw::pwgen::Type, ) -> anyhow::Result<()> { @@ -235,7 +274,7 @@ pub fn generate( let mut db = rbw::db::Db::load(&email)?; // unwrap is safe here because the call to unlock above is guaranteed // to populate these or error - let access_token = db.access_token.as_ref().unwrap(); + let mut access_token = db.access_token.as_ref().unwrap().clone(); let refresh_token = db.refresh_token.as_ref().unwrap(); let name = crate::actions::encrypt(name)?; @@ -248,6 +287,42 @@ pub fn generate( .map(|uri| crate::actions::encrypt(&uri)) .collect::<anyhow::Result<_>>()?; + let mut folder_id = None; + if let Some(folder_name) = folder { + let (new_access_token, folders) = + rbw::actions::list_folders(&access_token, &refresh_token)?; + if let Some(new_access_token) = new_access_token { + access_token = new_access_token.clone(); + db.access_token = Some(new_access_token); + db.save(&email).context("failed to save database")?; + } + + let folders: Vec<(String, String)> = folders + .iter() + .cloned() + .map(|(id, name)| Ok((id, crate::actions::decrypt(&name)?))) + .collect::<anyhow::Result<_>>()?; + + for (id, name) in folders { + if name == folder_name { + folder_id = Some(id); + } + } + if folder_id.is_none() { + let (new_access_token, id) = rbw::actions::create_folder( + &access_token, + &refresh_token, + &crate::actions::encrypt(folder_name)?, + )?; + if let Some(new_access_token) = new_access_token { + access_token = new_access_token.clone(); + db.access_token = Some(new_access_token); + db.save(&email).context("failed to save database")?; + } + folder_id = Some(id); + } + } + if let (Some(access_token), ()) = rbw::actions::add( &access_token, &refresh_token, @@ -256,6 +331,7 @@ pub fn generate( Some(&password), None, &uris, + folder_id.as_deref(), )? { db.access_token = Some(access_token); db.save(&email).context("failed to save database")?; diff --git a/src/bin/rbw/main.rs b/src/bin/rbw/main.rs index c1bdfbd..9bdb1b6 100644 --- a/src/bin/rbw/main.rs +++ b/src/bin/rbw/main.rs @@ -54,6 +54,11 @@ fn main() { .multiple(true) .number_of_values(1) .use_delimiter(false), + ) + .arg( + clap::Arg::with_name("folder") + .long("folder") + .takes_value(true), ), ) .subcommand( @@ -69,6 +74,11 @@ fn main() { .number_of_values(1) .use_delimiter(false), ) + .arg( + clap::Arg::with_name("folder") + .long("folder") + .takes_value(true), + ) .arg(clap::Arg::with_name("no-symbols").long("no-symbols")) .arg( clap::Arg::with_name("only-numbers").long("only-numbers"), @@ -149,6 +159,7 @@ fn main() { .values_of("uri") .map(|it| it.collect()) .unwrap_or_else(|| vec![]), + smatches.value_of("folder"), ) .context("add"), ("generate", Some(smatches)) => { @@ -173,6 +184,7 @@ fn main() { .values_of("uri") .map(|it| it.collect()) .unwrap_or_else(|| vec![]), + smatches.value_of("folder"), len, ty, ) @@ -6,6 +6,7 @@ #![allow(clippy::similar_names)] #![allow(clippy::single_match)] #![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] pub mod actions; pub mod api; |