aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2020-04-19 23:00:47 -0400
committerJesse Luehrs <doy@tozt.net>2020-04-19 23:00:47 -0400
commit6f10a47adc2fb42ffc965b954b9285123a82c094 (patch)
tree725b2b8cce4fe6e7c0b51f96d3d65a43928d1f2f
parent7a32c43713514c02f783d3c8e0835229e7f59a83 (diff)
downloadrbw-6f10a47adc2fb42ffc965b954b9285123a82c094.tar.gz
rbw-6f10a47adc2fb42ffc965b954b9285123a82c094.zip
implement adding into a folder
-rw-r--r--src/actions.rs55
-rw-r--r--src/api.rs82
-rw-r--r--src/bin/rbw/commands.rs80
-rw-r--r--src/bin/rbw/main.rs12
-rw-r--r--src/lib.rs1
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,
diff --git a/src/api.rs b/src/api.rs
index aa36f47..8fe0d68 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -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,
)
diff --git a/src/lib.rs b/src/lib.rs
index ef69e49..ac99032 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;