Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support default browser for CLI #345

Merged
merged 1 commit into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading