From cb1595f4fe5356dfcd3e21b1a4ad20f20d33193d Mon Sep 17 00:00:00 2001 From: holly sparkles Date: Sat, 29 Jul 2023 13:43:31 +0200 Subject: [PATCH] refactor: bitwarden operations moved to the `bwutil` module --- Cargo.toml | 4 +++- src/bwutil.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 88 +++++++++++++++++++++++++++------------------------------------------------------------- 3 files changed, 98 insertions(+), 62 deletions(-) create mode 100644 src/bwutil.rs diff --git a/Cargo.toml b/Cargo.toml index e10e353..8e90ff6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,6 @@ edition = "2021" [dependencies] gumdrop = "0.8.1" -anyhow = "1.0.72" \ No newline at end of file +anyhow = "1.0.72" +serde = { version = "1.0.177", features = ["derive"] } +serde_json = "1.0.104" \ No newline at end of file diff --git a/src/bwutil.rs b/src/bwutil.rs new file mode 100644 index 0000000..398089e --- /dev/null +++ b/src/bwutil.rs @@ -0,0 +1,68 @@ +use std::process::{Command, Output, Stdio}; +use serde::Deserialize; +use anyhow::{anyhow, Result, Context}; + +/// Represents a Bitwarden folder. +#[derive(Default, Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BitwardenFolder { + object: String, + id: String, + name: String +} + +/// Gets the user's logged-in status from Bitwarden. +pub fn is_logged_in() -> Result { + let logged_in = Command::new("bw") + .arg("login") + .arg("--check") + .output()?; + + Ok(logged_in.stdout.len() > 0) +} + +/// Gets a session token from Bitwarden. +pub fn get_session_token() -> Result { + let mut token = String::new(); + let mut operation = String::new(); + + // No session token found. check to see if Bitwarden needs a login or is locked + 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_interactive_command("bw", + ["--raw", &operation].to_vec()); + if success.status.success() { + token.push_str(&String::from_utf8(success.stdout)?); + } + + Ok(token) +} + +/// 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 +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; +} diff --git a/src/main.rs b/src/main.rs index ff35c87..f732812 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ -use std::{env, process::{Command, Stdio, Output}}; +use std::env; use gumdrop::Options; use anyhow::Result; +mod bwutil; + /// Represents command-line parameters. #[derive(Debug, Options)] struct Cli { @@ -33,86 +35,50 @@ fn main() -> Result<()> { 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); + let session_token: String = check_session_token(&args)?; + if !session_token.is_empty() { + todo!() } Ok(()) } -/// Gets the active Bitwarden session. The checking process is as follows: -/// 1. Check for `--session` argument -/// 2. Check for `BW_SESSION` environment variable +/// Gets a session token from the active Bitwarden session. The checking process is as follows: +/// 1. Check for `--session` argument and use that. +/// 2. Check for `BW_SESSION` environment variable and use that. /// 3. Check for login status from `bw` -/// 4. Login or unlock based on login status -fn get_session(args: &Cli) -> Result { +/// 4. Login or unlock based on login status and use that. +/// An invalid `BW_SESSION` or `--session` will prompt a login. +fn check_session_token(args: &Cli) -> Result { println!("Getting Bitwarden session..."); - let mut session_key: String = String::new(); + let mut session_token: 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 { + let env_key = env::var(SESSION_ENV_KEY); + match env_key { Ok(key) => { - println!("Existing Bitwarden session found."); + println!("{} is set. Reusing existing session.", SESSION_ENV_KEY); // We found it, set our session key - session_key.push_str(&key); + session_token.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") - } + // We don't have a token to reuse, so get it from Bitwarden + println!("{} is not set. Attempting to login.", SESSION_ENV_KEY); + let token: &String = &bwutil::get_session_token()?; - let success: Output = exec_interactive_command("bw", ["--raw", &operation].to_vec()); - if success.status.success() { - session_key.push_str(&String::from_utf8(success.stdout)?); + if !token.is_empty() { + println!("Successfully unlocked. To re-use this session, run:"); + println!("export {}=\"{}\"", SESSION_ENV_KEY, token); } + session_token.push_str(token) } } } else { - // Session flag is defined, pass it on - session_key.push_str(&args.session); + // Session flag is already defined, pass it on + session_token.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 -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; + Ok(session_token) } -- libgit2 1.7.2