index : bitwarden-ssh-agent.git

ascending towards madness

author holly sparkles <sparkles@holly.sh> 2023-07-29 11:43:31.0 +00:00:00
committer holly sparkles <sparkles@holly.sh> 2023-07-29 11:43:31.0 +00:00:00
commit
cb1595f4fe5356dfcd3e21b1a4ad20f20d33193d [patch]
tree
05c34b634b653494d166c41af206ed733037b03e
parent
9f2c2a9525a8659b873624ee6ae648e7cf3d25ca
download
cb1595f4fe5356dfcd3e21b1a4ad20f20d33193d.tar.gz

refactor: bitwarden operations moved to the `bwutil` module



Diff

 Cargo.toml    |  4 ++-
 src/bwutil.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/main.rs   | 88 ++++++++++++++++++------------------------------------------
 3 files changed, 98 insertions(+), 62 deletions(-)

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<bool> {
	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<String> {
	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<String> {
/// 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<String> {
	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)
}