mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-04-02 18:31:50 -04:00
Support SSO using default browser
This commit is contained in:
parent
0b55a80317
commit
c3bd7aeb93
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -11,8 +11,10 @@
|
|||||||
"dotenvy",
|
"dotenvy",
|
||||||
"getconfig",
|
"getconfig",
|
||||||
"globalprotect",
|
"globalprotect",
|
||||||
|
"globalprotectcallback",
|
||||||
"gpapi",
|
"gpapi",
|
||||||
"gpauth",
|
"gpauth",
|
||||||
|
"gpcallback",
|
||||||
"gpclient",
|
"gpclient",
|
||||||
"gpcommon",
|
"gpcommon",
|
||||||
"gpgui",
|
"gpgui",
|
||||||
@ -50,5 +52,6 @@
|
|||||||
"wmctrl",
|
"wmctrl",
|
||||||
"XAUTHORITY",
|
"XAUTHORITY",
|
||||||
"yuezk"
|
"yuezk"
|
||||||
]
|
],
|
||||||
|
"rust-analyzer.cargo.features": "all",
|
||||||
}
|
}
|
||||||
|
47
Cargo.lock
generated
47
Cargo.lock
generated
@ -1423,7 +1423,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpapi"
|
name = "gpapi"
|
||||||
version = "2.0.0-beta4"
|
version = "2.0.0-beta5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
@ -1431,6 +1431,7 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"dotenvy_macro",
|
"dotenvy_macro",
|
||||||
"log",
|
"log",
|
||||||
|
"open",
|
||||||
"redact-engine",
|
"redact-engine",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@ -1451,7 +1452,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpauth"
|
name = "gpauth"
|
||||||
version = "2.0.0-beta4"
|
version = "2.0.0-beta5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@ -1471,7 +1472,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpclient"
|
name = "gpclient"
|
||||||
version = "2.0.0-beta4"
|
version = "2.0.0-beta5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@ -1492,7 +1493,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpservice"
|
name = "gpservice"
|
||||||
version = "2.0.0-beta4"
|
version = "2.0.0-beta5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@ -1963,6 +1964,15 @@ version = "2.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-docker"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-terminal"
|
name = "is-terminal"
|
||||||
version = "0.4.10"
|
version = "0.4.10"
|
||||||
@ -1974,6 +1984,16 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-wsl"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
|
||||||
|
dependencies = [
|
||||||
|
"is-docker",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_executable"
|
name = "is_executable"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -2445,9 +2465,20 @@ version = "0.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "open"
|
||||||
|
version = "5.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349"
|
||||||
|
dependencies = [
|
||||||
|
"is-wsl",
|
||||||
|
"libc",
|
||||||
|
"pathdiff",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openconnect"
|
name = "openconnect"
|
||||||
version = "2.0.0-beta4"
|
version = "2.0.0-beta5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"is_executable",
|
"is_executable",
|
||||||
@ -2574,6 +2605,12 @@ version = "1.0.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathdiff"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
|
@ -4,7 +4,7 @@ resolver = "2"
|
|||||||
members = ["crates/*", "apps/gpclient", "apps/gpservice", "apps/gpauth"]
|
members = ["crates/*", "apps/gpclient", "apps/gpservice", "apps/gpauth"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "2.0.0-beta4"
|
version = "2.0.0-beta5"
|
||||||
authors = ["Kevin Yue <k3vinyue@gmail.com>"]
|
authors = ["Kevin Yue <k3vinyue@gmail.com>"]
|
||||||
homepage = "https://github.com/yuezk/GlobalProtect-openconnect"
|
homepage = "https://github.com/yuezk/GlobalProtect-openconnect"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
@ -11,6 +11,7 @@ A GUI for GlobalProtect VPN, based on OpenConnect, supports the SSO authenticati
|
|||||||
- [x] Better Linux support
|
- [x] Better Linux support
|
||||||
- [x] Support both CLI and GUI
|
- [x] Support both CLI and GUI
|
||||||
- [x] Support both SSO and non-SSO authentication
|
- [x] Support both SSO and non-SSO authentication
|
||||||
|
- [x] Support authentication using default browser
|
||||||
- [x] Support multiple portals
|
- [x] Support multiple portals
|
||||||
- [x] Support gateway selection
|
- [x] Support gateway selection
|
||||||
- [x] Support auto-connect on startup
|
- [x] Support auto-connect on startup
|
||||||
|
@ -10,7 +10,12 @@ use log::info;
|
|||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub(crate) struct LaunchGuiArgs {
|
pub(crate) struct LaunchGuiArgs {
|
||||||
#[clap(long, help = "Launch the GUI minimized")]
|
#[arg(
|
||||||
|
required = false,
|
||||||
|
help = "The authentication data, used for the default browser authentication"
|
||||||
|
)]
|
||||||
|
auth_data: Option<String>,
|
||||||
|
#[arg(long, help = "Launch the GUI minimized")]
|
||||||
minimized: bool,
|
minimized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +35,12 @@ impl<'a> LaunchGuiHandler<'a> {
|
|||||||
anyhow::bail!("`launch-gui` cannot be run as root");
|
anyhow::bail!("`launch-gui` cannot be run as root");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let auth_data = self.args.auth_data.as_deref().unwrap_or_default();
|
||||||
|
if !auth_data.is_empty() {
|
||||||
|
// Process the authentication data, its format is `globalprotectcallback:<data>`
|
||||||
|
return feed_auth_data(auth_data).await;
|
||||||
|
}
|
||||||
|
|
||||||
if try_active_gui().await.is_ok() {
|
if try_active_gui().await.is_ok() {
|
||||||
info!("The GUI is already running");
|
info!("The GUI is already running");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -66,6 +77,19 @@ impl<'a> LaunchGuiHandler<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn feed_auth_data(auth_data: &str) -> anyhow::Result<()> {
|
||||||
|
let service_endpoint = http_endpoint().await?;
|
||||||
|
|
||||||
|
reqwest::Client::default()
|
||||||
|
.post(format!("{}/auth-data", service_endpoint))
|
||||||
|
.json(&auth_data)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?;
|
||||||
|
|
||||||
|
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?;
|
||||||
|
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
|
||||||
<policyconfig>
|
|
||||||
<vendor>GlobalProtect-openconnect</vendor>
|
|
||||||
<vendor_url>https://github.com/yuezk/GlobalProtect-openconnect</vendor_url>
|
|
||||||
<icon_name>gpgui</icon_name>
|
|
||||||
<action id="com.yuezk.gpservice">
|
|
||||||
<description>Run GPService as root</description>
|
|
||||||
<message>Authentication is required to run the GPService as root</message>
|
|
||||||
<defaults>
|
|
||||||
<allow_any>yes</allow_any>
|
|
||||||
<allow_inactive>yes</allow_inactive>
|
|
||||||
<allow_active>yes</allow_active>
|
|
||||||
</defaults>
|
|
||||||
<annotate key="org.freedesktop.policykit.exec.path">/home/kevin/Documents/repos/gp/target/debug/gpservice</annotate>
|
|
||||||
<annotate key="org.freedesktop.policykit.exec.argv1">--with-gui</annotate>
|
|
||||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
|
||||||
</action>
|
|
||||||
</policyconfig>
|
|
@ -21,6 +21,13 @@ pub(crate) async fn active_gui(State(ctx): State<Arc<WsServerContext>>) -> impl
|
|||||||
ctx.send_event(WsEvent::ActiveGui).await;
|
ctx.send_event(WsEvent::ActiveGui).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn auth_data(
|
||||||
|
State(ctx): State<Arc<WsServerContext>>,
|
||||||
|
body: String,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
ctx.send_event(WsEvent::AuthData(body)).await;
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn ws_handler(
|
pub(crate) async fn ws_handler(
|
||||||
ws: WebSocketUpgrade,
|
ws: WebSocketUpgrade,
|
||||||
State(ctx): State<Arc<WsServerContext>>,
|
State(ctx): State<Arc<WsServerContext>>,
|
||||||
|
@ -8,6 +8,7 @@ pub(crate) fn routes(ctx: Arc<WsServerContext>) -> Router {
|
|||||||
Router::new()
|
Router::new()
|
||||||
.route("/health", get(handlers::health))
|
.route("/health", get(handlers::health))
|
||||||
.route("/active-gui", post(handlers::active_gui))
|
.route("/active-gui", post(handlers::active_gui))
|
||||||
|
.route("/auth-data", post(handlers::auth_data))
|
||||||
.route("/ws", get(handlers::ws_handler))
|
.route("/ws", get(handlers::ws_handler))
|
||||||
.with_state(ctx)
|
.with_state(ctx)
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,9 @@ uzers.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"]
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use anyhow::bail;
|
||||||
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@ -37,6 +39,32 @@ impl SamlAuthData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_html(html: &str) -> anyhow::Result<SamlAuthData> {
|
||||||
|
match parse_xml_tag(html, "saml-auth-status") {
|
||||||
|
Some(saml_status) if saml_status == "1" => {
|
||||||
|
let username = parse_xml_tag(html, "saml-username");
|
||||||
|
let prelogin_cookie = parse_xml_tag(html, "prelogin-cookie");
|
||||||
|
let portal_userauthcookie = parse_xml_tag(html, "portal-userauthcookie");
|
||||||
|
|
||||||
|
if SamlAuthData::check(&username, &prelogin_cookie, &portal_userauthcookie) {
|
||||||
|
return Ok(SamlAuthData::new(
|
||||||
|
username.unwrap(),
|
||||||
|
prelogin_cookie,
|
||||||
|
portal_userauthcookie,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Found invalid auth data in HTML");
|
||||||
|
}
|
||||||
|
Some(status) => {
|
||||||
|
bail!("Found invalid SAML status {} in HTML", status);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
bail!("No auth data found in HTML");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn username(&self) -> &str {
|
pub fn username(&self) -> &str {
|
||||||
&self.username
|
&self.username
|
||||||
}
|
}
|
||||||
@ -61,3 +89,10 @@ impl SamlAuthData {
|
|||||||
username_valid && (prelogin_cookie_valid || portal_userauthcookie_valid)
|
username_valid && (prelogin_cookie_valid || portal_userauthcookie_valid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_xml_tag(html: &str, tag: &str) -> Option<String> {
|
||||||
|
let re = Regex::new(&format!("<{}>(.*)</{}>", tag, tag)).unwrap();
|
||||||
|
re.captures(html)
|
||||||
|
.and_then(|captures| captures.get(1))
|
||||||
|
.map(|m| m.as_str().to_string())
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
|
|
||||||
use crate::auth::SamlAuthData;
|
use crate::{auth::SamlAuthData, utils::base64::decode_to_string};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Type, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Type, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -151,6 +151,17 @@ pub enum Credential {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Credential {
|
impl Credential {
|
||||||
|
/// Create a credential from a globalprotectcallback:<base64 encoded string>
|
||||||
|
pub fn parse_gpcallback(auth_data: &str) -> anyhow::Result<Self> {
|
||||||
|
// Remove the surrounding quotes
|
||||||
|
let auth_data = auth_data.trim_matches('"');
|
||||||
|
let auth_data = auth_data.trim_start_matches("globalprotectcallback:");
|
||||||
|
let auth_data = decode_to_string(auth_data)?;
|
||||||
|
let auth_data = SamlAuthData::parse_html(&auth_data)?;
|
||||||
|
|
||||||
|
Self::try_from(auth_data)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn username(&self) -> &str {
|
pub fn username(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Credential::Password(cred) => cred.username(),
|
Credential::Password(cred) => cred.username(),
|
||||||
|
@ -50,6 +50,7 @@ pub struct GpParams {
|
|||||||
client_version: Option<String>,
|
client_version: Option<String>,
|
||||||
computer: String,
|
computer: String,
|
||||||
ignore_tls_errors: bool,
|
ignore_tls_errors: bool,
|
||||||
|
prefer_default_browser: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpParams {
|
impl GpParams {
|
||||||
@ -69,6 +70,10 @@ impl GpParams {
|
|||||||
self.ignore_tls_errors
|
self.ignore_tls_errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prefer_default_browser(&self) -> bool {
|
||||||
|
self.prefer_default_browser
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn to_params(&self) -> HashMap<&str, &str> {
|
pub(crate) fn to_params(&self) -> HashMap<&str, &str> {
|
||||||
let mut params: HashMap<&str, &str> = HashMap::new();
|
let mut params: HashMap<&str, &str> = HashMap::new();
|
||||||
let client_os = self.client_os.as_str();
|
let client_os = self.client_os.as_str();
|
||||||
@ -88,9 +93,10 @@ impl GpParams {
|
|||||||
params.insert("os-version", os_version);
|
params.insert("os-version", os_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(client_version) = &self.client_version {
|
// NOTE: Do not include clientgpversion for now
|
||||||
params.insert("clientgpversion", client_version);
|
// if let Some(client_version) = &self.client_version {
|
||||||
}
|
// params.insert("clientgpversion", client_version);
|
||||||
|
// }
|
||||||
|
|
||||||
params
|
params
|
||||||
}
|
}
|
||||||
@ -103,6 +109,7 @@ pub struct GpParamsBuilder {
|
|||||||
client_version: Option<String>,
|
client_version: Option<String>,
|
||||||
computer: String,
|
computer: String,
|
||||||
ignore_tls_errors: bool,
|
ignore_tls_errors: bool,
|
||||||
|
prefer_default_browser: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpParamsBuilder {
|
impl GpParamsBuilder {
|
||||||
@ -114,6 +121,7 @@ impl GpParamsBuilder {
|
|||||||
client_version: Default::default(),
|
client_version: Default::default(),
|
||||||
computer: whoami::hostname(),
|
computer: whoami::hostname(),
|
||||||
ignore_tls_errors: false,
|
ignore_tls_errors: false,
|
||||||
|
prefer_default_browser: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +155,11 @@ impl GpParamsBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prefer_default_browser(&mut self, prefer_default_browser: bool) -> &mut Self {
|
||||||
|
self.prefer_default_browser = prefer_default_browser;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(&self) -> GpParams {
|
pub fn build(&self) -> GpParams {
|
||||||
GpParams {
|
GpParams {
|
||||||
user_agent: self.user_agent.clone(),
|
user_agent: self.user_agent.clone(),
|
||||||
@ -155,6 +168,7 @@ impl GpParamsBuilder {
|
|||||||
client_version: self.client_version.clone(),
|
client_version: self.client_version.clone(),
|
||||||
computer: self.computer.clone(),
|
computer: self.computer.clone(),
|
||||||
ignore_tls_errors: self.ignore_tls_errors,
|
ignore_tls_errors: self.ignore_tls_errors,
|
||||||
|
prefer_default_browser: self.prefer_default_browser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ const REQUIRED_PARAMS: [&str; 8] = [
|
|||||||
pub struct SamlPrelogin {
|
pub struct SamlPrelogin {
|
||||||
region: String,
|
region: String,
|
||||||
saml_request: String,
|
saml_request: String,
|
||||||
|
support_default_browser: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SamlPrelogin {
|
impl SamlPrelogin {
|
||||||
@ -36,6 +37,10 @@ impl SamlPrelogin {
|
|||||||
pub fn saml_request(&self) -> &str {
|
pub fn saml_request(&self) -> &str {
|
||||||
&self.saml_request
|
&self.saml_request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn support_default_browser(&self) -> bool {
|
||||||
|
self.support_default_browser
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Type, Clone)]
|
#[derive(Debug, Serialize, Type, Clone)]
|
||||||
@ -86,14 +91,14 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prel
|
|||||||
info!("Portal prelogin, user_agent: {}", user_agent);
|
info!("Portal prelogin, user_agent: {}", user_agent);
|
||||||
|
|
||||||
let portal = normalize_server(portal)?;
|
let portal = normalize_server(portal)?;
|
||||||
let prelogin_url = format!(
|
let prelogin_url = format!("{}/global-protect/prelogin.esp", portal);
|
||||||
"{}/global-protect/prelogin.esp?kerberos-support=yes",
|
|
||||||
portal
|
|
||||||
);
|
|
||||||
let mut params = gp_params.to_params();
|
let mut params = gp_params.to_params();
|
||||||
|
|
||||||
params.insert("tmp", "tmp");
|
params.insert("tmp", "tmp");
|
||||||
params.insert("default-browser", "0");
|
|
||||||
params.insert("cas-support", "yes");
|
params.insert("cas-support", "yes");
|
||||||
|
if gp_params.prefer_default_browser() {
|
||||||
|
params.insert("default-browser", "1");
|
||||||
|
}
|
||||||
|
|
||||||
params.retain(|k, _| {
|
params.retain(|k, _| {
|
||||||
REQUIRED_PARAMS
|
REQUIRED_PARAMS
|
||||||
@ -125,12 +130,18 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prel
|
|||||||
|
|
||||||
let saml_method = xml::get_child_text(&doc, "saml-auth-method");
|
let saml_method = xml::get_child_text(&doc, "saml-auth-method");
|
||||||
let saml_request = xml::get_child_text(&doc, "saml-request");
|
let saml_request = xml::get_child_text(&doc, "saml-request");
|
||||||
|
let saml_default_browser = xml::get_child_text(&doc, "saml-default-browser");
|
||||||
// Check if the prelogin response is SAML
|
// Check if the prelogin response is SAML
|
||||||
if saml_method.is_some() && saml_request.is_some() {
|
if saml_method.is_some() && saml_request.is_some() {
|
||||||
let saml_request = base64::decode_to_string(&saml_request.unwrap())?;
|
let saml_request = base64::decode_to_string(&saml_request.unwrap())?;
|
||||||
|
let support_default_browser = saml_default_browser
|
||||||
|
.map(|s| s.to_lowercase() == "yes")
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
let saml_prelogin = SamlPrelogin {
|
let saml_prelogin = SamlPrelogin {
|
||||||
region,
|
region,
|
||||||
saml_request,
|
saml_request,
|
||||||
|
support_default_browser,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(Prelogin::Saml(saml_prelogin));
|
return Ok(Prelogin::Saml(saml_prelogin));
|
||||||
|
34
crates/gpapi/src/process/browser_authenticator.rs
Normal file
34
crates/gpapi/src/process/browser_authenticator.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use std::{env::temp_dir, io::Write};
|
||||||
|
|
||||||
|
pub struct BrowserAuthenticator<'a> {
|
||||||
|
auth_request: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BrowserAuthenticator<'_> {
|
||||||
|
pub fn new(auth_request: &str) -> BrowserAuthenticator {
|
||||||
|
BrowserAuthenticator { auth_request }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn authenticate(&self) -> anyhow::Result<()> {
|
||||||
|
if self.auth_request.starts_with("http") {
|
||||||
|
open::that_detached(self.auth_request)?;
|
||||||
|
} else {
|
||||||
|
let html_file = temp_dir().join("gpauth.html");
|
||||||
|
let mut file = std::fs::File::create(&html_file)?;
|
||||||
|
|
||||||
|
file.write_all(self.auth_request.as_bytes())?;
|
||||||
|
|
||||||
|
open::that_detached(html_file)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for BrowserAuthenticator<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Cleanup the temporary file
|
||||||
|
let html_file = temp_dir().join("gpauth.html");
|
||||||
|
let _ = std::fs::remove_file(html_file);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
pub(crate) mod command_traits;
|
pub(crate) mod command_traits;
|
||||||
|
|
||||||
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 service_launcher;
|
pub mod service_launcher;
|
||||||
|
@ -7,4 +7,6 @@ use super::vpn_state::VpnState;
|
|||||||
pub enum WsEvent {
|
pub enum WsEvent {
|
||||||
VpnState(VpnState),
|
VpnState(VpnState),
|
||||||
ActiveGui,
|
ActiveGui,
|
||||||
|
/// External authentication data
|
||||||
|
AuthData(String),
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user