chore(formatting): rustfmt
Diff
src/bwutil.rs | 136 +++++++++++++++++---------------
src/main.rs | 251 +++++++++++++++++++++++++++++------------------------------
src/util.rs | 45 ++++++-----
3 files changed, 228 insertions(+), 204 deletions(-)
@@ -1,118 +1,132 @@
use std::process::{Command, Output, Stdio};
use anyhow::{anyhow, Context, Result};
use colored::*;
use serde::Deserialize;
use anyhow::{anyhow, Result, Context};
use std::process::{Command, Output, Stdio};
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BitwardenFolder {
pub id: String,
pub name: String
pub id: String,
pub name: String,
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BitwardenItem {
pub id: String,
pub name: String,
pub attachments: Option<Vec<BitwardenAttachment>>,
pub fields: Option<Vec<BitwardenFieldItem>>
pub id: String,
pub name: String,
pub attachments: Option<Vec<BitwardenAttachment>>,
pub fields: Option<Vec<BitwardenFieldItem>>,
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BitwardenFieldItem {
pub name: String,
pub value: Option<String>
pub name: String,
pub value: Option<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BitwardenAttachment {
pub id: String,
pub file_name: String
pub id: String,
pub file_name: String,
}
pub fn is_logged_in() -> Result<bool> {
let logged_in = Command::new("bw")
.arg("login")
.arg("--check")
.output()?;
let logged_in = Command::new("bw").arg("login").arg("--check").output()?;
Ok(logged_in.stdout.len() > 0)
Ok(logged_in.stdout.len() > 0)
}
pub fn get_session_token() -> Result<String> {
let mut token = String::new();
let mut operation = String::new();
if is_logged_in()? {
println!("Vault is locked.");
operation.push_str("unlock")
let mut token = String::new();
let mut operation = String::new();
}
else {
println!("You are not logged in.");
operation.push_str("login")
}
if is_logged_in()? {
println!("Vault is locked.");
operation.push_str("unlock")
} else {
println!("You are not logged in.");
operation.push_str("login")
}
let success: Output = exec_piped_command("bw",
["--raw", &operation].to_vec());
if success.status.success() {
token.push_str(&String::from_utf8(success.stdout)?);
}
let success: Output = exec_piped_command("bw", ["--raw", &operation].to_vec());
if success.status.success() {
token.push_str(&String::from_utf8(success.stdout)?);
}
Ok(token)
Ok(token)
}
pub fn exec_folder_search(session: &str, folder_name: &str) -> Result<Vec<BitwardenFolder>> {
let folders: Output = exec_piped_command("bw",
["list", "folders", "--search", &folder_name, "--session", &session].to_vec());
let result: String = String::from_utf8_lossy(&folders.stdout).to_string();
if result.is_empty() {
Err(anyhow!("Could not authenticate. This could mean your session has expired.".red()))
}
else {
Ok(serde_json::from_str(&result).with_context(|| "Could not deserialize folder search results.")?)
}
let folders: Output = exec_piped_command(
"bw",
[
"list",
"folders",
"--search",
&folder_name,
"--session",
&session,
]
.to_vec(),
);
let result: String = String::from_utf8_lossy(&folders.stdout).to_string();
if result.is_empty() {
Err(anyhow!(
"Could not authenticate. This could mean your session has expired.".red()
))
} else {
Ok(serde_json::from_str(&result)
.with_context(|| "Could not deserialize folder search results.")?)
}
}
pub fn exec_list_folder_items(session: &str, folder_id: &str) -> Result<Vec<BitwardenItem>> {
let items = exec_piped_command("bw",
["list", "items", "--folderid", &folder_id, "--session", &session].to_vec());
let result = String::from_utf8_lossy(&items.stdout).to_string();
if result.is_empty() {
Err(anyhow!("Could not authenticate. This could mean your session has expired.".red()))
}
else {
Ok(serde_json::from_str(&result).with_context(|| "Could not deserialize item search results.")?)
}
let items = exec_piped_command(
"bw",
[
"list",
"items",
"--folderid",
&folder_id,
"--session",
&session,
]
.to_vec(),
);
let result = String::from_utf8_lossy(&items.stdout).to_string();
if result.is_empty() {
Err(anyhow!(
"Could not authenticate. This could mean your session has expired.".red()
))
} else {
Ok(serde_json::from_str(&result)
.with_context(|| "Could not deserialize item search results.")?)
}
}
fn exec_piped_command(cmd: &str, args: Vec<&str>) -> Output {
let cli_command = match Command::new(cmd)
.args(&args)
.stdout(Stdio::piped())
.spawn()
{
let cli_command = match Command::new(cmd).args(&args).stdout(Stdio::piped()).spawn() {
Err(err) => panic!("Error spawning: {}", err),
Ok(process) => process,
};
let output = cli_command.wait_with_output().unwrap();
let output = cli_command.wait_with_output().unwrap();
return output;
}
@@ -17,95 +17,95 @@ const BW_FIELD_KEY_PASSPHRASE: &str = "BW_KEY_PASSPHRASE";
macro_rules! debug_println {
($($arg:tt)*) => {
if let Ok(enabled) = env::var(BW_SSH_DEBUG_ENV_KEY) {
if bool::from_str(&enabled).unwrap_or(false) {
println!("{}",format!($($arg)*).yellow());
}
}
if let Ok(enabled) = env::var(BW_SSH_DEBUG_ENV_KEY) {
if bool::from_str(&enabled).unwrap_or(false) {
println!("{}",format!($($arg)*).yellow());
}
}
}
}
macro_rules! warn_println {
($($arg:tt)*) => {
println!("{}",format!($($arg)*).yellow());
println!("{}",format!($($arg)*).yellow());
}
}
macro_rules! info_println {
($($arg:tt)*) => {
println!("{}",format!($($arg)*).green());
println!("{}",format!($($arg)*).green());
}
}
fn main() -> Result<()> {
let unlock_passphrase = env::var(BW_FIELD_KEY_PASSPHRASE);
if unlock_passphrase.is_ok() {
println!("{}", unlock_passphrase.unwrap());
return Ok(())
}
let unlock_passphrase = env::var(BW_FIELD_KEY_PASSPHRASE);
if unlock_passphrase.is_ok() {
println!("{}", unlock_passphrase.unwrap());
return Ok(())
}
let args: util::Cli = util::Cli::parse_args_default_or_exit();
env::set_var(BW_SSH_DEBUG_ENV_KEY, OsString::from(args.debug.to_string()));
if args.version {
println!("{}", &util::get_version_string()?);
return Ok(());
}
let session_token: String = check_session_token(&args)?;
if !session_token.is_empty() {
let folders = bwutil::exec_folder_search(&session_token, &args.folder)?;
debug_println!("Found {} folder(s) named `{}`", folders.len().to_string().cyan(), args.folder.cyan());
for folder in folders {
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());
for item in folder_items {
if let Some(fields) = &item.fields {
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());
}
}
}
}
}
}
Ok(())
env::set_var(BW_SSH_DEBUG_ENV_KEY, OsString::from(args.debug.to_string()));
if args.version {
println!("{}", &util::get_version_string()?);
return Ok(());
}
let session_token: String = check_session_token(&args)?;
if !session_token.is_empty() {
let folders = bwutil::exec_folder_search(&session_token, &args.folder)?;
debug_println!("Found {} folder(s) named `{}`", folders.len().to_string().cyan(), args.folder.cyan());
for folder in folders {
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());
for item in folder_items {
if let Some(fields) = &item.fields {
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());
}
}
}
}
}
}
Ok(())
}
@@ -115,75 +115,74 @@ fn main() -> Result<()> {
fn check_session_token(args: &util::Cli) -> Result<String> {
info_println!("Getting Bitwarden session...");
let mut session_token: String = String::new();
if args.session.trim().is_empty() {
let env_key = env::var(SESSION_ENV_KEY);
match env_key {
Ok(key) => {
info_println!("{} is set. Reusing existing session.", SESSION_ENV_KEY);
session_token.push_str(&key);
},
Err(_) => {
warn_println!("{} is not set. Attempting to login.", SESSION_ENV_KEY);
let token: &String = &bwutil::get_session_token()?;
if !token.is_empty() {
info_println!("Successfully unlocked. To re-use this session, run:");
info_println!("export {}=\"{}\"", SESSION_ENV_KEY, token);
}
session_token.push_str(token)
}
}
}
else {
session_token.push_str(&args.session);
}
Ok(session_token)
info_println!("Getting Bitwarden session...");
let mut session_token: String = String::new();
if args.session.trim().is_empty() {
let env_key = env::var(SESSION_ENV_KEY);
match env_key {
Ok(key) => {
info_println!("{} is set. Reusing existing session.", SESSION_ENV_KEY);
session_token.push_str(&key);
},
Err(_) => {
warn_println!("{} is not set. Attempting to login.", SESSION_ENV_KEY);
let token: &String = &bwutil::get_session_token()?;
if !token.is_empty() {
info_println!("Successfully unlocked. To re-use this session, run:");
info_println!("export {}=\"{}\"", SESSION_ENV_KEY, token);
}
session_token.push_str(token)
}
}
}
else {
session_token.push_str(&args.session);
}
Ok(session_token)
}
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());
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("--raw")
.arg("--session")
.arg(session_token)
.arg("get")
.arg("attachment")
.arg(attachment_id)
.arg("--itemid")
.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)
)?;
.with_context(||
format!("Error running command:\n `bw get attachment {} --itemid {} --raw --session **REDACTED**`", attachment_id, item.id)
)?;
let app_path = env::current_exe()?;
let app_path = app_path.to_string_lossy().to_string();
let app_path = env::current_exe()?.to_string_lossy().to_string();
let ssh_command = Command::new("ssh-add")
.env("SSH_ASKPASS", &app_path)
.env(BW_FIELD_KEY_PASSPHRASE, passphrase)
.env("SSH_ASKPASS", &app_path)
.env(BW_FIELD_KEY_PASSPHRASE, passphrase)
.arg("-")
.stdin(Stdio::from(bw_command.stdout.unwrap())) .spawn()
.with_context(||
format!("Error running command:\n `SSH_ASKPASS=\"{}\" {}=\"{}\"ssh-add -`", app_path, BW_FIELD_KEY_PASSPHRASE, passphrase)
)?;
.with_context(||
format!("Error running command:\n `SSH_ASKPASS=\"{}\" {}=\"{}\"ssh-add -`", app_path, BW_FIELD_KEY_PASSPHRASE, passphrase)
)?;
let output = ssh_command.wait_with_output().unwrap();
let result = String::from_utf8(output.stdout).unwrap();
if !result.is_empty() {
println!("{:?}", result);
}
if !result.is_empty() {
println!("{:?}", result);
}
Ok(())
}
\ No newline at end of file
@@ -1,29 +1,40 @@
use std::env;
use anyhow::Result;
use gumdrop::Options;
use std::env;
#[derive(Debug, Options)]
pub struct Cli {
#[options(help = "print help message and exit")]
#[options(help = "print help message and exit")]
pub help: bool,
#[options(help = "show debug output")]
pub debug: bool,
#[options(help = "folder name to use to search for SSH keys", default = "ssh-agent")]
pub folder: String,
#[options(help = "custom field name where the private key filename is stored", meta = "NAME", default = "private")]
pub key: String,
#[options(help = "custom field name where the key passphrase is stored", meta = "PASS", default = "passphrase")]
pub passphrase: String,
#[options(help = "session to use to log in to bitwarden-cli")]
pub session: String,
#[options(help = "print version information and exit")]
#[options(help = "show debug output")]
pub debug: bool,
#[options(
help = "folder name to use to search for SSH keys",
default = "ssh-agent"
)]
pub folder: String,
#[options(
help = "custom field name where the private key filename is stored",
meta = "NAME",
default = "private"
)]
pub key: String,
#[options(
help = "custom field name where the key passphrase is stored",
meta = "PASS",
default = "passphrase"
)]
pub passphrase: String,
#[options(help = "session to use to log in to bitwarden-cli")]
pub session: String,
#[options(help = "print version information and exit")]
pub version: bool,
}
pub fn get_version_string() -> Result<String> {
let name = env!("CARGO_PKG_NAME");
let version = env!("CARGO_PKG_VERSION");
return Ok(format!("{} {}", name, version))
}
\ No newline at end of file
let name = env!("CARGO_PKG_NAME");
let version = env!("CARGO_PKG_VERSION");
return Ok(format!("{} {}", name, version));
}