From be058fdd9645d6c8b081ee4da98f1e3a7f7e58b6 Mon Sep 17 00:00:00 2001 From: Reactor Scram Date: Mon, 23 Sep 2024 09:06:26 -0500 Subject: [PATCH] test(rust/gui-client/auth): manual test for auto-sign-in with invalid token (#6792) Synthetic replication for #6791. The diff for the fix will probably be short, so I wanted this diff for the test to be reviewed separately. In your normal terminal: `cargo build -p firezone-gui-client -p gui-smoke-test` With sudo / admin powers: `./target/debug/gui-smoke-test.exe --manual-tests` Some customers _must_ have hit this, it's so easy to trigger. I can't add it to the CI smoke test because there's no portal in CI during the smoke test, unless we use Staging. --- rust/Cargo.lock | 1 + rust/gui-client/src-common/src/auth.rs | 25 ++++++++++++--- rust/gui-client/src-tauri/src/client.rs | 4 +++ .../src-tauri/src/client/debug_commands.rs | 2 ++ rust/gui-client/src-tauri/src/client/gui.rs | 15 +++++++-- rust/headless-client/src/main.rs | 2 +- rust/tests/gui-smoke-test/Cargo.toml | 1 + rust/tests/gui-smoke-test/src/main.rs | 32 +++++++++++++++++++ 8 files changed, 74 insertions(+), 8 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 90da15305..386010859 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -3184,6 +3184,7 @@ name = "gui-smoke-test" version = "0.1.0" dependencies = [ "anyhow", + "clap", "subprocess", "tracing", "tracing-subscriber", diff --git a/rust/gui-client/src-common/src/auth.rs b/rust/gui-client/src-common/src/auth.rs index d1b55f4a1..7ddce92f3 100644 --- a/rust/gui-client/src-common/src/auth.rs +++ b/rust/gui-client/src-common/src/auth.rs @@ -169,17 +169,22 @@ impl Auth { ); let token = SecretString::from(token); + self.save_session(&resp.actor_name, &token)?; + self.state = State::SignedIn(Session { + actor_name: resp.actor_name, + }); + Ok(SecretString::from(token)) + } + + fn save_session(&self, actor_name: &str, token: &SecretString) -> Result<(), Error> { // This MUST be the only place the GUI can call `set_password`, since // the actor name is also saved here. self.token_store.set_password(token.expose_secret())?; let path = actor_name_path()?; std::fs::create_dir_all(path.parent().ok_or(Error::ActorNamePathWrong)?) .map_err(Error::CreateDirAll)?; - std::fs::write(path, resp.actor_name.as_bytes()).map_err(Error::WriteActorName)?; - self.state = State::SignedIn(Session { - actor_name: resp.actor_name, - }); - Ok(SecretString::from(token)) + std::fs::write(path, actor_name.as_bytes()).map_err(Error::WriteActorName)?; + Ok(()) } /// Returns the token if we are signed in @@ -259,6 +264,16 @@ fn secure_equality(a: &SecretString, b: &SecretString) -> bool { a.ct_eq(b).into() } +pub fn replicate_6791() -> Result<()> { + tracing::warn!("Debugging issue #6791, pretending to be signed in with a bad token"); + let this = Auth::new()?; + this.save_session( + "Jane Doe", + &SecretString::from("obviously invalid token for testing #6791".to_string()), + )?; + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust/gui-client/src-tauri/src/client.rs b/rust/gui-client/src-tauri/src/client.rs index 64292a654..c7003c711 100644 --- a/rust/gui-client/src-tauri/src/client.rs +++ b/rust/gui-client/src-tauri/src/client.rs @@ -158,6 +158,10 @@ struct Cli { #[arg(long, hide = true)] panic: bool, + /// Quit gracefully after a given number of seconds + #[arg(long, hide = true)] + quit_after: Option, + /// If true, slow down I/O operations to test how the GUI handles slow I/O #[arg(long, hide = true)] inject_faults: bool, diff --git a/rust/gui-client/src-tauri/src/client/debug_commands.rs b/rust/gui-client/src-tauri/src/client/debug_commands.rs index c7163f782..3dcd8e459 100644 --- a/rust/gui-client/src-tauri/src/client/debug_commands.rs +++ b/rust/gui-client/src-tauri/src/client/debug_commands.rs @@ -5,6 +5,7 @@ use anyhow::Result; #[derive(clap::Subcommand)] pub(crate) enum Cmd { + Replicate6791, SetAutostart(SetAutostartArgs), } @@ -26,6 +27,7 @@ pub(crate) struct StoreTokenArgs { pub fn run(cmd: Cmd) -> Result<()> { match cmd { + Cmd::Replicate6791 => firezone_gui_client_common::auth::replicate_6791(), Cmd::SetAutostart(SetAutostartArgs { enabled }) => set_autostart(enabled), } } diff --git a/rust/gui-client/src-tauri/src/client/gui.rs b/rust/gui-client/src-tauri/src/client/gui.rs index 6fc5ec1f8..7c537c755 100644 --- a/rust/gui-client/src-tauri/src/client/gui.rs +++ b/rust/gui-client/src-tauri/src/client/gui.rs @@ -223,16 +223,27 @@ pub(crate) fn run( let ctlr_tx = ctlr_tx.clone(); tokio::spawn(async move { let delay = 5; - tracing::info!( + tracing::warn!( "Will crash / error / panic on purpose in {delay} seconds to test error handling." ); tokio::time::sleep(Duration::from_secs(delay)).await; - tracing::info!("Crashing / erroring / panicking on purpose"); + tracing::warn!("Crashing / erroring / panicking on purpose"); ctlr_tx.send(ControllerRequest::Fail(failure)).await?; Ok::<_, anyhow::Error>(()) }); } + if let Some(delay) = cli.quit_after { + let ctlr_tx = ctlr_tx.clone(); + tokio::spawn(async move { + tracing::warn!("Will quit gracefully in {delay} seconds."); + tokio::time::sleep(Duration::from_secs(delay)).await; + tracing::warn!("Quitting gracefully due to `--quit-after`"); + ctlr_tx.send(ControllerRequest::SystemTrayMenu(firezone_gui_client_common::system_tray::Event::Quit)).await?; + Ok::<_, anyhow::Error>(()) + }); + } + assert_eq!( firezone_bin_shared::BUNDLE_ID, app.handle().config().tauri.bundle.identifier, diff --git a/rust/headless-client/src/main.rs b/rust/headless-client/src/main.rs index 895a700d2..3f236968e 100644 --- a/rust/headless-client/src/main.rs +++ b/rust/headless-client/src/main.rs @@ -35,7 +35,7 @@ mod platform; use platform::default_token_path; /// Command-line args for the headless Client -#[derive(clap::Parser)] +#[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { // Needed to preserve CLI arg compatibility diff --git a/rust/tests/gui-smoke-test/Cargo.toml b/rust/tests/gui-smoke-test/Cargo.toml index b6a81b6b5..3291e8c13 100644 --- a/rust/tests/gui-smoke-test/Cargo.toml +++ b/rust/tests/gui-smoke-test/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] anyhow = { version = "1.0" } +clap = { version = "4.5", features = ["derive"] } subprocess = "0.2.9" tracing = { workspace = true } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } diff --git a/rust/tests/gui-smoke-test/src/main.rs b/rust/tests/gui-smoke-test/src/main.rs index 2b65b33a2..4dd0172be 100644 --- a/rust/tests/gui-smoke-test/src/main.rs +++ b/rust/tests/gui-smoke-test/src/main.rs @@ -3,6 +3,7 @@ // Starts up the IPC service and GUI app and lets them run for a bit use anyhow::{bail, Context as _, Result}; +use clap::Parser; use std::{ ffi::OsStr, path::{Path, PathBuf}, @@ -21,8 +22,19 @@ const EXE_EXTENSION: &str = ""; #[cfg(target_os = "windows")] const EXE_EXTENSION: &str = "exe"; +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + /// Run tests that can't run in CI, like tests that need access to the staging network. + #[arg(long)] + manual_tests: bool, +} + fn main() -> Result<()> { tracing_subscriber::fmt::init(); + tracing::info!("Started logging"); + let cli = Cli::try_parse()?; + let app = App::new()?; dump_syms()?; @@ -46,6 +58,26 @@ fn main() -> Result<()> { app.check_crash_dump()?; + if cli.manual_tests { + manual_tests(&app)?; + } + + Ok(()) +} + +fn manual_tests(app: &App) -> Result<()> { + // Replicate #6791 + app.gui_command(&["debug", "replicate6791"])? + .popen()? + .wait()?; + + let mut ipc_service = ipc_service_command().arg("run-smoke-test").popen()?; + let mut gui = app.gui_command(&["--quit-after", "10"])?.popen()?; + + // Expect exit codes of 0 + gui.wait()?.fz_exit_ok().context("GUI process")?; + ipc_service.wait()?.fz_exit_ok().context("IPC service")?; + Ok(()) }