mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-04-02 18:31:50 -04:00
feat: support default browser for CLI (#345)
This commit is contained in:
parent
18ae1c5fa5
commit
d94d730a44
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1439,7 +1439,6 @@ dependencies = [
|
|||||||
"dotenvy_macro",
|
"dotenvy_macro",
|
||||||
"log",
|
"log",
|
||||||
"md5",
|
"md5",
|
||||||
"open",
|
|
||||||
"redact-engine",
|
"redact-engine",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@ -1471,6 +1470,7 @@ dependencies = [
|
|||||||
"gpapi",
|
"gpapi",
|
||||||
"html-escape",
|
"html-escape",
|
||||||
"log",
|
"log",
|
||||||
|
"open",
|
||||||
"regex",
|
"regex",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
|
@ -44,6 +44,7 @@ compile-time = "0.2"
|
|||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
md5="0.7"
|
md5="0.7"
|
||||||
sha256="1"
|
sha256="1"
|
||||||
|
open = "5"
|
||||||
|
|
||||||
# Tauri dependencies
|
# Tauri dependencies
|
||||||
tauri = { version = "1.5" }
|
tauri = { version = "1.5" }
|
||||||
|
@ -22,3 +22,4 @@ html-escape = "0.2.13"
|
|||||||
webkit2gtk = "0.18.2"
|
webkit2gtk = "0.18.2"
|
||||||
tauri = { workspace = true, features = ["http-all"] }
|
tauri = { workspace = true, features = ["http-all"] }
|
||||||
compile-time.workspace = true
|
compile-time.workspace = true
|
||||||
|
open.workspace = true
|
||||||
|
@ -11,7 +11,10 @@ use serde_json::json;
|
|||||||
use tauri::{App, AppHandle, RunEvent};
|
use tauri::{App, AppHandle, RunEvent};
|
||||||
use tempfile::NamedTempFile;
|
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!(), ")");
|
const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")");
|
||||||
|
|
||||||
@ -37,6 +40,8 @@ struct Cli {
|
|||||||
ignore_tls_errors: bool,
|
ignore_tls_errors: bool,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
clean: bool,
|
clean: bool,
|
||||||
|
#[arg(long)]
|
||||||
|
default_browser: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
@ -56,6 +61,15 @@ impl Cli {
|
|||||||
None => portal_prelogin(&self.server, &gp_params).await?,
|
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);
|
self.saml_request.replace(saml_request);
|
||||||
|
|
||||||
let app = create_app(self.clone())?;
|
let app = create_app(self.clone())?;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
mod auth_window;
|
mod auth_window;
|
||||||
|
mod browser_authenticator;
|
||||||
mod cli;
|
mod cli;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -19,8 +19,9 @@ use gpapi::{
|
|||||||
use inquire::{Password, PasswordDisplayMode, Select, Text};
|
use inquire::{Password, PasswordDisplayMode, Select, Text};
|
||||||
use log::info;
|
use log::info;
|
||||||
use openconnect::Vpn;
|
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)]
|
#[derive(Args)]
|
||||||
pub(crate) struct ConnectArgs {
|
pub(crate) struct ConnectArgs {
|
||||||
@ -60,6 +61,8 @@ pub(crate) struct ConnectArgs {
|
|||||||
hidpi: bool,
|
hidpi: bool,
|
||||||
#[arg(long, help = "Do not reuse the remembered authentication cookie")]
|
#[arg(long, help = "Do not reuse the remembered authentication cookie")]
|
||||||
clean: bool,
|
clean: bool,
|
||||||
|
#[arg(long, help = "Use the default browser to authenticate")]
|
||||||
|
default_browser: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectArgs {
|
impl ConnectArgs {
|
||||||
@ -240,7 +243,9 @@ impl<'a> ConnectHandler<'a> {
|
|||||||
|
|
||||||
match prelogin {
|
match prelogin {
|
||||||
Prelogin::Saml(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)
|
.gateway(is_gateway)
|
||||||
.saml_request(prelogin.saml_request())
|
.saml_request(prelogin.saml_request())
|
||||||
.user_agent(&self.args.user_agent)
|
.user_agent(&self.args.user_agent)
|
||||||
@ -250,8 +255,21 @@ impl<'a> ConnectHandler<'a> {
|
|||||||
.fix_openssl(self.shared_args.fix_openssl)
|
.fix_openssl(self.shared_args.fix_openssl)
|
||||||
.ignore_tls_errors(self.shared_args.ignore_tls_errors)
|
.ignore_tls_errors(self.shared_args.ignore_tls_errors)
|
||||||
.clean(self.args.clean)
|
.clean(self.args.clean)
|
||||||
|
.default_browser(use_default_browser)
|
||||||
.launch()
|
.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) => {
|
Prelogin::Standard(prelogin) => {
|
||||||
let prefix = if is_gateway { "Gateway" } else { "Portal" };
|
let prefix = if is_gateway { "Gateway" } else { "Portal" };
|
||||||
@ -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() {
|
fn write_pid_file() {
|
||||||
let pid = std::process::id();
|
let pid = std::process::id();
|
||||||
|
|
||||||
|
@ -7,6 +7,9 @@ use gpapi::{
|
|||||||
utils::{endpoint::http_endpoint, env_file, shutdown_signal},
|
utils::{endpoint::http_endpoint, env_file, shutdown_signal},
|
||||||
};
|
};
|
||||||
use log::info;
|
use log::info;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
use crate::GP_CLIENT_PORT_FILE;
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub(crate) struct LaunchGuiArgs {
|
pub(crate) struct LaunchGuiArgs {
|
||||||
@ -78,6 +81,11 @@ impl<'a> LaunchGuiHandler<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn feed_auth_data(auth_data: &str) -> anyhow::Result<()> {
|
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?;
|
let service_endpoint = http_endpoint().await?;
|
||||||
|
|
||||||
reqwest::Client::default()
|
reqwest::Client::default()
|
||||||
@ -90,6 +98,15 @@ async fn feed_auth_data(auth_data: &str) -> anyhow::Result<()> {
|
|||||||
Ok(())
|
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<()> {
|
async fn try_active_gui() -> anyhow::Result<()> {
|
||||||
let service_endpoint = http_endpoint().await?;
|
let service_endpoint = http_endpoint().await?;
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ mod disconnect;
|
|||||||
mod launch_gui;
|
mod launch_gui;
|
||||||
|
|
||||||
pub(crate) const GP_CLIENT_LOCK_FILE: &str = "/var/run/gpclient.lock";
|
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]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -31,9 +31,7 @@ sha256.workspace = true
|
|||||||
|
|
||||||
tauri = { workspace = true, optional = true }
|
tauri = { workspace = true, optional = true }
|
||||||
clap = { workspace = true, optional = true }
|
clap = { workspace = true, optional = true }
|
||||||
open = { version = "5", optional = true }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tauri = ["dep:tauri"]
|
tauri = ["dep:tauri"]
|
||||||
clap = ["dep:clap"]
|
clap = ["dep:clap"]
|
||||||
browser-auth = ["dep:open"]
|
|
||||||
|
@ -18,6 +18,7 @@ pub struct SamlAuthLauncher<'a> {
|
|||||||
fix_openssl: bool,
|
fix_openssl: bool,
|
||||||
ignore_tls_errors: bool,
|
ignore_tls_errors: bool,
|
||||||
clean: bool,
|
clean: bool,
|
||||||
|
default_browser: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SamlAuthLauncher<'a> {
|
impl<'a> SamlAuthLauncher<'a> {
|
||||||
@ -33,6 +34,7 @@ impl<'a> SamlAuthLauncher<'a> {
|
|||||||
fix_openssl: false,
|
fix_openssl: false,
|
||||||
ignore_tls_errors: false,
|
ignore_tls_errors: false,
|
||||||
clean: false,
|
clean: false,
|
||||||
|
default_browser: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,8 +83,13 @@ impl<'a> SamlAuthLauncher<'a> {
|
|||||||
self
|
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.
|
/// 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);
|
let mut auth_cmd = Command::new(GP_AUTH_BINARY);
|
||||||
auth_cmd.arg(self.server);
|
auth_cmd.arg(self.server);
|
||||||
|
|
||||||
@ -122,6 +129,10 @@ impl<'a> SamlAuthLauncher<'a> {
|
|||||||
auth_cmd.arg("--clean");
|
auth_cmd.arg("--clean");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.default_browser {
|
||||||
|
auth_cmd.arg("--default-browser");
|
||||||
|
}
|
||||||
|
|
||||||
let mut non_root_cmd = auth_cmd.into_non_root()?;
|
let mut non_root_cmd = auth_cmd.into_non_root()?;
|
||||||
let output = non_root_cmd
|
let output = non_root_cmd
|
||||||
.kill_on_drop(true)
|
.kill_on_drop(true)
|
||||||
@ -130,12 +141,16 @@ impl<'a> SamlAuthLauncher<'a> {
|
|||||||
.wait_with_output()
|
.wait_with_output()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if self.default_browser {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
let Ok(auth_result) = serde_json::from_slice::<SamlAuthResult>(&output.stdout) else {
|
let Ok(auth_result) = serde_json::from_slice::<SamlAuthResult>(&output.stdout) else {
|
||||||
bail!("Failed to parse auth data")
|
bail!("Failed to parse auth data")
|
||||||
};
|
};
|
||||||
|
|
||||||
match auth_result {
|
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),
|
SamlAuthResult::Failure(msg) => bail!(msg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@ pub(crate) mod command_traits;
|
|||||||
pub(crate) mod gui_helper_launcher;
|
pub(crate) mod gui_helper_launcher;
|
||||||
|
|
||||||
pub mod auth_launcher;
|
pub mod auth_launcher;
|
||||||
#[cfg(feature = "browser-auth")]
|
|
||||||
pub mod browser_authenticator;
|
|
||||||
pub mod gui_launcher;
|
pub mod gui_launcher;
|
||||||
pub mod hip_launcher;
|
pub mod hip_launcher;
|
||||||
pub mod service_launcher;
|
pub mod service_launcher;
|
||||||
|
Loading…
Reference in New Issue
Block a user