Skip to content

Commit

Permalink
feat: support default browser for CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
yuezk committed Apr 15, 2024
1 parent 18ae1c5 commit 1bb02a7
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 11 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ compile-time = "0.2"
serde_urlencoded = "0.7"
md5="0.7"
sha256="1"
open = "5"

# Tauri dependencies
tauri = { version = "1.5" }
Expand Down
1 change: 1 addition & 0 deletions apps/gpauth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ html-escape = "0.2.13"
webkit2gtk = "0.18.2"
tauri = { workspace = true, features = ["http-all"] }
compile-time.workspace = true
open.workspace = true
File renamed without changes.
16 changes: 15 additions & 1 deletion apps/gpauth/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use serde_json::json;
use tauri::{App, AppHandle, RunEvent};
use tempfile::NamedTempFile;

use crate::auth_window::{portal_prelogin, AuthWindow};
use crate::{
auth_window::{portal_prelogin, AuthWindow},
browser_authenticator::BrowserAuthenticator,
};

const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")");

Expand All @@ -37,6 +40,8 @@ struct Cli {
ignore_tls_errors: bool,
#[arg(long)]
clean: bool,
#[arg(long)]
default_browser: bool,
}

impl Cli {
Expand All @@ -56,6 +61,15 @@ impl Cli {
None => portal_prelogin(&self.server, &gp_params).await?,
};

if self.default_browser {
let browser_auth = BrowserAuthenticator::new(&saml_request);
browser_auth.authenticate()?;

info!("Please continue the authentication process in the default browser");

return Ok(());
}

self.saml_request.replace(saml_request);

let app = create_app(self.clone())?;
Expand Down
1 change: 1 addition & 0 deletions apps/gpauth/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod auth_window;
mod browser_authenticator;
mod cli;

#[tokio::main]
Expand Down
45 changes: 42 additions & 3 deletions apps/gpclient/src/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ use gpapi::{
use inquire::{Password, PasswordDisplayMode, Select, Text};
use log::info;
use openconnect::Vpn;
use tokio::{io::AsyncReadExt, net::TcpListener};

use crate::{cli::SharedArgs, GP_CLIENT_LOCK_FILE};
use crate::{cli::SharedArgs, GP_CLIENT_LOCK_FILE, GP_CLIENT_PORT_FILE};

#[derive(Args)]
pub(crate) struct ConnectArgs {
Expand Down Expand Up @@ -60,6 +61,8 @@ pub(crate) struct ConnectArgs {
hidpi: bool,
#[arg(long, help = "Do not reuse the remembered authentication cookie")]
clean: bool,
#[arg(long, help = "Use the default browser to authenticate")]
default_browser: bool,
}

impl ConnectArgs {
Expand Down Expand Up @@ -240,7 +243,9 @@ impl<'a> ConnectHandler<'a> {

match prelogin {
Prelogin::Saml(prelogin) => {
SamlAuthLauncher::new(&self.args.server)
let use_default_browser = prelogin.support_default_browser() && self.args.default_browser;

let cred = SamlAuthLauncher::new(&self.args.server)
.gateway(is_gateway)
.saml_request(prelogin.saml_request())
.user_agent(&self.args.user_agent)
Expand All @@ -250,8 +255,21 @@ impl<'a> ConnectHandler<'a> {
.fix_openssl(self.shared_args.fix_openssl)
.ignore_tls_errors(self.shared_args.ignore_tls_errors)
.clean(self.args.clean)
.default_browser(use_default_browser)
.launch()
.await
.await?;

if let Some(cred) = cred {
return Ok(cred);
}

if !use_default_browser {
// This should never happen
unreachable!("SAML authentication failed without using the default browser");
}

info!("Waiting for the browser authentication to complete...");
wait_credentials().await
}
Prelogin::Standard(prelogin) => {
let prefix = if is_gateway { "Gateway" } else { "Portal" };
Expand All @@ -274,6 +292,27 @@ impl<'a> ConnectHandler<'a> {
}
}

async fn wait_credentials() -> anyhow::Result<Credential> {
// Start a local server to receive the browser authentication data
let listener = TcpListener::bind("127.0.0.1:0").await?;
let port = listener.local_addr()?.port();

// Write the port to a file
fs::write(GP_CLIENT_PORT_FILE, port.to_string())?;

info!("Listening authentication data on port {}", port);
let (mut socket, _) = listener.accept().await?;

info!("Received the browser authentication data from the socket");
let mut data = String::new();
socket.read_to_string(&mut data).await?;

// Remove the port file
fs::remove_file(GP_CLIENT_PORT_FILE)?;

Credential::from_gpcallback(&data)
}

fn write_pid_file() {
let pid = std::process::id();

Expand Down
17 changes: 17 additions & 0 deletions apps/gpclient/src/launch_gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ use gpapi::{
utils::{endpoint::http_endpoint, env_file, shutdown_signal},
};
use log::info;
use tokio::io::AsyncWriteExt;

use crate::GP_CLIENT_PORT_FILE;

#[derive(Args)]
pub(crate) struct LaunchGuiArgs {
Expand Down Expand Up @@ -78,6 +81,11 @@ impl<'a> LaunchGuiHandler<'a> {
}

async fn feed_auth_data(auth_data: &str) -> anyhow::Result<()> {
let _ = tokio::join!(feed_auth_data_gui(auth_data), feed_auth_data_cli(auth_data));
Ok(())
}

async fn feed_auth_data_gui(auth_data: &str) -> anyhow::Result<()> {
let service_endpoint = http_endpoint().await?;

reqwest::Client::default()
Expand All @@ -90,6 +98,15 @@ async fn feed_auth_data(auth_data: &str) -> anyhow::Result<()> {
Ok(())
}

async fn feed_auth_data_cli(auth_data: &str) -> anyhow::Result<()> {
let port = tokio::fs::read_to_string(GP_CLIENT_PORT_FILE).await?;
let mut stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port.trim())).await?;

stream.write_all(auth_data.as_bytes()).await?;

Ok(())
}

async fn try_active_gui() -> anyhow::Result<()> {
let service_endpoint = http_endpoint().await?;

Expand Down
1 change: 1 addition & 0 deletions apps/gpclient/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod disconnect;
mod launch_gui;

pub(crate) const GP_CLIENT_LOCK_FILE: &str = "/var/run/gpclient.lock";
pub(crate) const GP_CLIENT_PORT_FILE: &str = "/var/run/gpclient.port";

#[tokio::main]
async fn main() {
Expand Down
2 changes: 0 additions & 2 deletions crates/gpapi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ sha256.workspace = true

tauri = { workspace = true, optional = true }
clap = { workspace = true, optional = true }
open = { version = "5", optional = true }

[features]
tauri = ["dep:tauri"]
clap = ["dep:clap"]
browser-auth = ["dep:open"]
19 changes: 17 additions & 2 deletions crates/gpapi/src/process/auth_launcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct SamlAuthLauncher<'a> {
fix_openssl: bool,
ignore_tls_errors: bool,
clean: bool,
default_browser: bool,
}

impl<'a> SamlAuthLauncher<'a> {
Expand All @@ -33,6 +34,7 @@ impl<'a> SamlAuthLauncher<'a> {
fix_openssl: false,
ignore_tls_errors: false,
clean: false,
default_browser: false,
}
}

Expand Down Expand Up @@ -81,8 +83,13 @@ impl<'a> SamlAuthLauncher<'a> {
self
}

pub fn default_browser(mut self, default_browser: bool) -> Self {
self.default_browser = default_browser;
self
}

/// Launch the authenticator binary as the current user or SUDO_USER if available.
pub async fn launch(self) -> anyhow::Result<Credential> {
pub async fn launch(self) -> anyhow::Result<Option<Credential>> {
let mut auth_cmd = Command::new(GP_AUTH_BINARY);
auth_cmd.arg(self.server);

Expand Down Expand Up @@ -122,6 +129,10 @@ impl<'a> SamlAuthLauncher<'a> {
auth_cmd.arg("--clean");
}

if self.default_browser {
auth_cmd.arg("--default-browser");
}

let mut non_root_cmd = auth_cmd.into_non_root()?;
let output = non_root_cmd
.kill_on_drop(true)
Expand All @@ -130,12 +141,16 @@ impl<'a> SamlAuthLauncher<'a> {
.wait_with_output()
.await?;

if self.default_browser {
return Ok(None);
}

let Ok(auth_result) = serde_json::from_slice::<SamlAuthResult>(&output.stdout) else {
bail!("Failed to parse auth data")
};

match auth_result {
SamlAuthResult::Success(auth_data) => Ok(Credential::from(auth_data)),
SamlAuthResult::Success(auth_data) => Ok(Some(Credential::from(auth_data))),
SamlAuthResult::Failure(msg) => bail!(msg),
}
}
Expand Down
2 changes: 0 additions & 2 deletions crates/gpapi/src/process/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ pub(crate) mod command_traits;
pub(crate) mod gui_helper_launcher;

pub mod auth_launcher;
#[cfg(feature = "browser-auth")]
pub mod browser_authenticator;
pub mod gui_launcher;
pub mod hip_launcher;
pub mod service_launcher;
Expand Down

0 comments on commit 1bb02a7

Please sign in to comment.