From d82c208953f9ec7e04bba01ecb587584169d18b5 Mon Sep 17 00:00:00 2001 From: holly sparkles Date: Fri, 28 Jul 2023 22:47:04 +0200 Subject: [PATCH] feat: add retrieving and creating bitwarden sessions --- src/main.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 92be148..31f9632 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,8 @@ +use std::{env, process::{Command, Stdio, Output}}; use gumdrop::Options; +use anyhow::Result; +/// Represents command-line parameters. #[derive(Debug, Options)] struct Cli { #[options(help = "print help message and exit")] @@ -13,12 +16,103 @@ struct Cli { #[options(help = "custom field name where the key passphrase is stored", meta = "PASS", default = "passphrase")] passphrase: String, #[options(help = "session to use to log in to bitwarden-cli")] - session: Option, + session: String, #[options(help = "print version and exit")] version: bool, } -fn main() { +/// Environment variable housing an existing Bitwarden session. +const SESSION_ENV_KEY: &str = "BW_SESSION"; + +fn main() -> Result<()> { let args: Cli = Cli::parse_args_default_or_exit(); - println!("{:#?}", args); + if args.version { + let name = env!("CARGO_PKG_NAME"); + let version = env!("CARGO_PKG_VERSION"); + println!("{} {}", name, version); + return Ok(()); + } + + let session: String = get_session(&args)?; + if !session.is_empty() { + println!("Successfully unlocked. To re-use this session, run:"); + println!("export {}=\"{}\"", SESSION_ENV_KEY, session); + } + + Ok(()) +} + +/// Gets the active Bitwarden session. The checking process is as follows: +/// 1. Check for `--session` argument +/// 2. Check for `BW_SESSION` environment variable +/// 3. Check for login status from `bw` +/// 4. Login or unlock based on login status +fn get_session(args: &Cli) -> Result { + println!("Getting Bitwarden session..."); + let mut session_key: String = String::new(); + // Get session flag from the user + if args.session.trim().is_empty() { + // No session flag, check for an environment variable + let key = env::var(SESSION_ENV_KEY); + match key { + Ok(key) => { + println!("Existing Bitwarden session found."); + // We found it, set our session key + session_key.push_str(&key); + }, + Err(_) => { + // No session key found. check to see if Bitwarden is unlocked + let logged_in = Command::new("bw") + .arg("login") + .arg("--check") + .arg("--quiet") + .output()?; + + let mut operation = String::new(); + // Check for a status code + if !logged_in.status.success() { + // No success, use login + println!("Not logged in."); + operation.push_str("login"); + } + else { + // Success, use unlock + println!("Vault is locked."); + operation.push_str("unlock") + } + + let success: Output = exec_interactive_command("bw", ["--raw", &operation].to_vec()); + if success.status.success() { + session_key.push_str(&String::from_utf8(success.stdout)?); + } + } + } + } + else { + // Session flag is defined, pass it on + session_key.push_str(&args.session); + } + + Ok(session_key) +} + +/// Execute an interactive command. +/// +/// The resulting output will be returned as `Output`. This is a modified version of: +/// +/// https://users.rust-lang.org/t/command-if-a-child-process-is-asking-for-input-how-to-forward-the-question-to-the-user/37490/3 +pub fn exec_interactive_command(cmd: &str, args: Vec<&str>) -> Output { + let cli_command = match Command::new(cmd) + .args(&args) + .stdin(Stdio::inherit()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + { + Err(err) => panic!("Error spawning: {}", err), + Ok(process) => process, + }; + + let output = cli_command.wait_with_output().unwrap(); + return output; } -- libgit2 1.7.2