diff --git a/Cargo.lock b/Cargo.lock index 9c9c7233..cb5d35a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1459,6 +1459,7 @@ dependencies = [ "url", "urlencoding", "uzers", + "which", "whoami", ] @@ -1676,6 +1677,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "html-escape" version = "0.2.13" @@ -2153,9 +2163,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libredox" @@ -3234,9 +3244,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.4.1", "errno", @@ -4798,6 +4808,18 @@ dependencies = [ "windows-metadata", ] +[[package]] +name = "which" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075" +dependencies = [ + "either", + "home", + "rustix", + "winsafe", +] + [[package]] name = "whoami" version = "1.5.1" @@ -5104,6 +5126,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wry" version = "0.24.7" diff --git a/Cargo.toml b/Cargo.toml index fa2cd0ab..de710c15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ compile-time = "0.2" serde_urlencoded = "0.7" md5="0.7" sha256="1" +which="6" # Tauri dependencies tauri = { version = "1.5" } diff --git a/apps/gpauth/src/cli.rs b/apps/gpauth/src/cli.rs index 3d64f6c8..96121d5d 100644 --- a/apps/gpauth/src/cli.rs +++ b/apps/gpauth/src/cli.rs @@ -40,6 +40,8 @@ struct Cli { clean: bool, #[arg(long)] default_browser: bool, + #[arg(long)] + external_browser: Option, } impl Cli { @@ -59,8 +61,15 @@ impl Cli { None => portal_prelogin(&self.server, &gp_params).await?, }; - if self.default_browser { - let browser_auth = BrowserAuthenticator::new(&saml_request); + let browser_auth = if let Some(external_browser) = &self.external_browser { + Some(BrowserAuthenticator::new_with_browser(&saml_request, external_browser)) + } else if self.default_browser { + Some(BrowserAuthenticator::new(&saml_request)) + } else { + None + }; + + if let Some(browser_auth) = browser_auth { browser_auth.authenticate()?; info!("Please continue the authentication process in the default browser"); diff --git a/apps/gpclient/src/connect.rs b/apps/gpclient/src/connect.rs index 5dd51233..5a95ab68 100644 --- a/apps/gpclient/src/connect.rs +++ b/apps/gpclient/src/connect.rs @@ -86,7 +86,7 @@ pub(crate) struct ConnectArgs { #[arg(long)] os_version: Option, - #[arg(long, help="Disable DTLS and ESP")] + #[arg(long, help = "Disable DTLS and ESP")] no_dtls: bool, #[arg(long, help = "The HiDPI mode, useful for high resolution screens")] @@ -97,6 +97,12 @@ pub(crate) struct ConnectArgs { #[arg(long, help = "Use the default browser to authenticate")] default_browser: bool, + + #[arg( + long, + help = "Use the specified browser to authenticate, e.g., firefox, chromium, chrome, or the path to the browser" + )] + external_browser: Option, } impl ConnectArgs { @@ -326,6 +332,11 @@ impl<'a> ConnectHandler<'a> { match prelogin { Prelogin::Saml(prelogin) => { let use_default_browser = prelogin.support_default_browser() && self.args.default_browser; + let external_browser = if prelogin.support_default_browser() { + self.args.external_browser.as_deref() + } else { + None + }; let cred = SamlAuthLauncher::new(&self.args.server) .gateway(is_gateway) @@ -338,6 +349,7 @@ impl<'a> ConnectHandler<'a> { .ignore_tls_errors(self.shared_args.ignore_tls_errors) .clean(self.args.clean) .default_browser(use_default_browser) + .external_browser(external_browser) .launch() .await?; diff --git a/crates/gpapi/Cargo.toml b/crates/gpapi/Cargo.toml index 8b2994d6..65ea23e4 100644 --- a/crates/gpapi/Cargo.toml +++ b/crates/gpapi/Cargo.toml @@ -30,6 +30,7 @@ uzers.workspace = true serde_urlencoded.workspace = true md5.workspace = true sha256.workspace = true +which.workspace = true tauri = { workspace = true, optional = true } clap = { workspace = true, optional = true } diff --git a/crates/gpapi/src/process/auth_launcher.rs b/crates/gpapi/src/process/auth_launcher.rs index 837c91e0..ce41ee1c 100644 --- a/crates/gpapi/src/process/auth_launcher.rs +++ b/crates/gpapi/src/process/auth_launcher.rs @@ -19,6 +19,7 @@ pub struct SamlAuthLauncher<'a> { ignore_tls_errors: bool, clean: bool, default_browser: bool, + external_browser: Option<&'a str>, } impl<'a> SamlAuthLauncher<'a> { @@ -35,6 +36,7 @@ impl<'a> SamlAuthLauncher<'a> { ignore_tls_errors: false, clean: false, default_browser: false, + external_browser: None, } } @@ -88,6 +90,11 @@ impl<'a> SamlAuthLauncher<'a> { self } + pub fn external_browser(mut self, external_browser: Option<&'a str>) -> Self { + self.external_browser = external_browser; + self + } + /// Launch the authenticator binary as the current user or SUDO_USER if available. pub async fn launch(self) -> anyhow::Result> { let mut auth_cmd = Command::new(GP_AUTH_BINARY); @@ -133,6 +140,10 @@ impl<'a> SamlAuthLauncher<'a> { auth_cmd.arg("--default-browser"); } + if let Some(external_browser) = self.external_browser { + auth_cmd.arg("--external-browser").arg(external_browser); + } + let mut non_root_cmd = auth_cmd.into_non_root()?; let output = non_root_cmd .kill_on_drop(true) diff --git a/crates/gpapi/src/process/browser_authenticator.rs b/crates/gpapi/src/process/browser_authenticator.rs index c62e788a..ffdf0d87 100644 --- a/crates/gpapi/src/process/browser_authenticator.rs +++ b/crates/gpapi/src/process/browser_authenticator.rs @@ -1,20 +1,31 @@ -use std::{env::temp_dir, fs, io::Write, os::unix::fs::PermissionsExt}; +use std::{borrow::Cow, env::temp_dir, fs, io::Write, os::unix::fs::PermissionsExt}; use anyhow::bail; -use log::warn; +use log::{info, warn}; pub struct BrowserAuthenticator<'a> { auth_request: &'a str, + browser: Option<&'a str>, } impl BrowserAuthenticator<'_> { pub fn new(auth_request: &str) -> BrowserAuthenticator { - BrowserAuthenticator { auth_request } + BrowserAuthenticator { + auth_request, + browser: None, + } + } + + pub fn new_with_browser<'a>(auth_request: &'a str, browser: &'a str) -> BrowserAuthenticator<'a> { + BrowserAuthenticator { + auth_request, + browser: Some(browser), + } } pub fn authenticate(&self) -> anyhow::Result<()> { - if self.auth_request.starts_with("http") { - open::that_detached(self.auth_request)?; + let path = if self.auth_request.starts_with("http") { + Cow::Borrowed(self.auth_request) } else { let html_file = temp_dir().join("gpauth.html"); @@ -31,9 +42,31 @@ impl BrowserAuthenticator<'_> { file.set_permissions(fs::Permissions::from_mode(0o600))?; file.write_all(self.auth_request.as_bytes())?; - open::that_detached(html_file)?; + Cow::Owned(html_file.to_string_lossy().to_string()) + }; + + if let Some(browser) = self.browser { + let app = find_browser_path(browser); + + info!("Launching browser: {}", app); + open::with_detached(path.as_ref(), app)?; + } else { + info!("Launching the default browser..."); + open::that_detached(path.as_ref())?; } Ok(()) } } + +fn find_browser_path(browser: &str) -> String { + if browser == "chrome" { + which::which("google-chrome-stable") + .or_else(|_| which::which("google-chrome")) + .or_else(|_| which::which("chromium")) + .map(|path| path.to_string_lossy().to_string()) + .unwrap_or_else(|_| browser.to_string()) + } else { + browser.into() + } +}