index : bitwarden-ssh-agent.git

ascending towards madness

author holly sparkles <sparkles@holly.sh> 2023-08-04 10:41:09.0 +00:00:00
committer holly sparkles <sparkles@holly.sh> 2023-08-04 10:41:09.0 +00:00:00
commit
30a388f7946bf12148d2be956e69034013dcf78e [patch]
tree
7153d74145804e5ca661f294fa64b07472a49eed
parent
ce8e03cfb8b9a1dd32d11c7246ad9512936c0d45
download
30a388f7946bf12148d2be956e69034013dcf78e.tar.gz

refactor!: optimize loops BREAKING CHANGE: the signature for `register_key` has changed - `item_id` replaced with `item` for debug purposes - searching for items now uses `iter()` with `find()` and `filter()`



Diff

 src/bwutil.rs |  2 +-
 src/main.rs   | 51 +++++++++++++++++++++++++++++++--------------------
 2 files changed, 32 insertions(+), 21 deletions(-)

diff --git a/src/bwutil.rs b/src/bwutil.rs
index 0254ae9..468cddd 100644
--- a/src/bwutil.rs
+++ b/src/bwutil.rs
@@ -26,7 +26,7 @@ pub struct BitwardenItem {
#[serde(rename_all = "camelCase")]
pub struct BitwardenFieldItem {
	pub name: String,
	pub value: String
	pub value: Option<String>
}

/// Represents a file attachment in Bitwarden.
diff --git a/src/main.rs b/src/main.rs
index 27a8356..dc93488 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,6 +3,7 @@ use std::str::FromStr;
use gumdrop::Options;
use anyhow::{Result, Context};
use colored::*;
use crate::bwutil::BitwardenItem;

mod util;
mod bwutil;
@@ -59,8 +60,13 @@ fn main() -> Result<()> {
		debug_println!("Found {} folder(s) named `{}`", folders.len().to_string().cyan(), args.folder.cyan());
		
		// Retrieve items from each folder since there may be multiple folders with the same name.
		// Pre-filter the items by checking for the presence of custom fields AND at least one attachment.
		for folder in folders {
			let folder_items = bwutil::exec_list_folder_items(&session_token, &folder.id)?;
			let folder_items: Vec<BitwardenItem> = bwutil::exec_list_folder_items(&session_token, &folder.id)?;
			let folder_items: Vec<&BitwardenItem> = folder_items
				.iter()
				.filter(|&item| item.fields.is_some() && item.attachments.is_some())
				.collect();
			
			debug_println!("Found {} item(s) in folder `{}` id({}) with at least one custom field and attachment", folder_items.len().to_string().cyan(), folder.name.cyan(), folder.id.cyan());
			
@@ -72,22 +78,24 @@ fn main() -> Result<()> {
				//		The field name is not case sensitive in order to be more user-friendly.
				// - attachment (required) an attachment with the name specified in `private`.
				if let Some(fields) = &item.fields {
					let mut key_filename = String::new();
					let mut key_passphrase = String::new();
					for field in fields {
						if field.name.to_lowercase().eq(&args.key.to_lowercase()) {
							key_filename.push_str(&field.value);
						}
						if field.name.to_lowercase().eq(&args.passphrase.to_lowercase()) {
							key_passphrase.push_str(&field.value);
						}
					}

					if let Some(attachments) = &item.attachments {
						for attachment in attachments {
							if attachment.file_name.eq(&key_filename) {
								let _key = register_key(&item.id, &attachment.id, &key_passphrase, &session_token)?;
							}
					let key_filename: String = fields
						.iter()
						.find(|field| field.name.to_lowercase().eq(&args.key.to_lowercase()) && field.value.is_some())
						.map(|field| field.value.as_ref().unwrap().clone())
						.unwrap_or_default();

					if !&key_filename.is_empty() {
						let key_passphrase: String = fields
							.iter()
							.find(|field| field.name.to_lowercase().eq(&args.passphrase.to_lowercase()) && field.value.is_some())
							.map(|field| field.value.as_ref().unwrap().clone())
							.unwrap_or_default();

						if let Some(attachments) = &item.attachments {
							attachments
							.iter()
							.filter(|attachment| attachment.file_name.eq(&key_filename))
							.for_each(|attachment| register_key(&item, &attachment.id, &key_passphrase, &session_token).unwrap());
						}
					}
				}
@@ -138,20 +146,23 @@ fn check_session_token(args: &util::Cli) -> Result<String> {
	Ok(session_token)
}

fn register_key(item_id: &str, attachment_id: &str, passphrase: &str, session_token: &str) -> Result<()> {
/// Registers a key with `ssh-agent`.
/// First retrieves the attachment from `bitwarden-cli` and then passes it to `ssh-agent`.
fn register_key(item: &BitwardenItem, attachment_id: &str, passphrase: &str, session_token: &str) -> Result<()> {
	debug_println!("Item `{}` id({}) meets all requirements. Adding to `{}`", item.name.cyan(), item.id.to_string().cyan(), "ssh-agent".cyan());
    let bw_command = Command::new("bw")
		.arg("get")
		.arg("attachment")
		.arg(attachment_id)
		.arg("--itemid")
		.arg(item_id)
		.arg(&item.id)
		.arg("--raw")
		.arg("--session")
		.arg(session_token)
        .stdout(Stdio::piped())
        .spawn()
		.with_context(|| 
			format!("Error running command:\n  `bw get attachment {} --itemid {} --raw --session **REDACTED**`", attachment_id, item_id)
			format!("Error running command:\n  `bw get attachment {} --itemid {} --raw --session **REDACTED**`", attachment_id, item.id)
		)?;

	let app_path = env::current_exe()?;