mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-05-20 07:26:58 -04:00
Refactor using Tauri (#278)
This commit is contained in:
180
crates/gpapi/src/portal/config.rs
Normal file
180
crates/gpapi/src/portal/config.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
use anyhow::ensure;
|
||||
use log::info;
|
||||
use reqwest::Client;
|
||||
use roxmltree::Document;
|
||||
use serde::Serialize;
|
||||
use specta::Type;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
credential::{AuthCookieCredential, Credential},
|
||||
gateway::{parse_gateways, Gateway},
|
||||
gp_params::GpParams,
|
||||
utils::{normalize_server, xml},
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PortalConfig {
|
||||
portal: String,
|
||||
auth_cookie: AuthCookieCredential,
|
||||
gateways: Vec<Gateway>,
|
||||
config_digest: Option<String>,
|
||||
}
|
||||
|
||||
impl PortalConfig {
|
||||
pub fn new(
|
||||
portal: String,
|
||||
auth_cookie: AuthCookieCredential,
|
||||
gateways: Vec<Gateway>,
|
||||
config_digest: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
portal,
|
||||
auth_cookie,
|
||||
gateways,
|
||||
config_digest,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn portal(&self) -> &str {
|
||||
&self.portal
|
||||
}
|
||||
|
||||
pub fn gateways(&self) -> Vec<&Gateway> {
|
||||
self.gateways.iter().collect()
|
||||
}
|
||||
|
||||
pub fn auth_cookie(&self) -> &AuthCookieCredential {
|
||||
&self.auth_cookie
|
||||
}
|
||||
|
||||
/// In-place sort the gateways by region
|
||||
pub fn sort_gateways(&mut self, region: &str) {
|
||||
let preferred_gateway = self.find_preferred_gateway(region);
|
||||
let preferred_gateway_index = self
|
||||
.gateways()
|
||||
.iter()
|
||||
.position(|gateway| gateway.name == preferred_gateway.name)
|
||||
.unwrap();
|
||||
|
||||
// Move the preferred gateway to the front of the list
|
||||
self.gateways.swap(0, preferred_gateway_index);
|
||||
}
|
||||
|
||||
/// Find a gateway by name or address
|
||||
pub fn find_gateway(&self, name_or_address: &str) -> Option<&Gateway> {
|
||||
self
|
||||
.gateways
|
||||
.iter()
|
||||
.find(|gateway| gateway.name == name_or_address || gateway.address == name_or_address)
|
||||
}
|
||||
|
||||
/// Find the preferred gateway for the given region
|
||||
/// Iterates over the gateways and find the first one that
|
||||
/// has the lowest priority for the given region.
|
||||
/// If no gateway is found, returns the gateway with the lowest priority.
|
||||
pub fn find_preferred_gateway(&self, region: &str) -> &Gateway {
|
||||
let mut preferred_gateway: Option<&Gateway> = None;
|
||||
let mut lowest_region_priority = u32::MAX;
|
||||
|
||||
for gateway in &self.gateways {
|
||||
for rule in &gateway.priority_rules {
|
||||
if (rule.name == region || rule.name == "Any") && rule.priority < lowest_region_priority {
|
||||
preferred_gateway = Some(gateway);
|
||||
lowest_region_priority = rule.priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no gateway is found, return the gateway with the lowest priority
|
||||
preferred_gateway.unwrap_or_else(|| {
|
||||
self
|
||||
.gateways
|
||||
.iter()
|
||||
.min_by_key(|gateway| gateway.priority)
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PortalConfigError {
|
||||
#[error("Empty response, retrying can help")]
|
||||
EmptyResponse,
|
||||
#[error("Empty auth cookie, retrying can help")]
|
||||
EmptyAuthCookie,
|
||||
#[error("Invalid auth cookie, retrying can help")]
|
||||
InvalidAuthCookie,
|
||||
#[error("Empty gateways, retrying can help")]
|
||||
EmptyGateways,
|
||||
}
|
||||
|
||||
pub async fn retrieve_config(
|
||||
portal: &str,
|
||||
cred: &Credential,
|
||||
gp_params: &GpParams,
|
||||
) -> anyhow::Result<PortalConfig> {
|
||||
let portal = normalize_server(portal)?;
|
||||
let server = remove_url_scheme(&portal);
|
||||
|
||||
let url = format!("{}/global-protect/getconfig.esp", portal);
|
||||
let client = Client::builder()
|
||||
.user_agent(gp_params.user_agent())
|
||||
.build()?;
|
||||
|
||||
let mut params = cred.to_params();
|
||||
let extra_params = gp_params.to_params();
|
||||
|
||||
params.extend(extra_params);
|
||||
params.insert("server", &server);
|
||||
params.insert("host", &server);
|
||||
|
||||
info!("Portal config, user_agent: {}", gp_params.user_agent());
|
||||
|
||||
let res_xml = client
|
||||
.post(&url)
|
||||
.form(¶ms)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
ensure!(!res_xml.is_empty(), PortalConfigError::EmptyResponse);
|
||||
|
||||
let doc = Document::parse(&res_xml)?;
|
||||
let gateways = parse_gateways(&doc).ok_or_else(|| anyhow::anyhow!("Failed to parse gateways"))?;
|
||||
|
||||
let user_auth_cookie = xml::get_child_text(&doc, "portal-userauthcookie").unwrap_or_default();
|
||||
let prelogon_user_auth_cookie =
|
||||
xml::get_child_text(&doc, "portal-prelogonuserauthcookie").unwrap_or_default();
|
||||
let config_digest = xml::get_child_text(&doc, "config-digest");
|
||||
|
||||
ensure!(
|
||||
!user_auth_cookie.is_empty() && !prelogon_user_auth_cookie.is_empty(),
|
||||
PortalConfigError::EmptyAuthCookie
|
||||
);
|
||||
|
||||
ensure!(
|
||||
user_auth_cookie != "empty" && prelogon_user_auth_cookie != "empty",
|
||||
PortalConfigError::InvalidAuthCookie
|
||||
);
|
||||
|
||||
ensure!(!gateways.is_empty(), PortalConfigError::EmptyGateways);
|
||||
|
||||
Ok(PortalConfig::new(
|
||||
server.to_string(),
|
||||
AuthCookieCredential::new(
|
||||
cred.username(),
|
||||
&user_auth_cookie,
|
||||
&prelogon_user_auth_cookie,
|
||||
),
|
||||
gateways,
|
||||
config_digest,
|
||||
))
|
||||
}
|
||||
|
||||
fn remove_url_scheme(s: &str) -> String {
|
||||
s.replace("http://", "").replace("https://", "")
|
||||
}
|
5
crates/gpapi/src/portal/mod.rs
Normal file
5
crates/gpapi/src/portal/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod config;
|
||||
mod prelogin;
|
||||
|
||||
pub use config::*;
|
||||
pub use prelogin::*;
|
129
crates/gpapi/src/portal/prelogin.rs
Normal file
129
crates/gpapi/src/portal/prelogin.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use anyhow::bail;
|
||||
use log::{info, trace};
|
||||
use reqwest::Client;
|
||||
use roxmltree::Document;
|
||||
use serde::Serialize;
|
||||
use specta::Type;
|
||||
|
||||
use crate::utils::{base64, normalize_server, xml};
|
||||
|
||||
#[derive(Debug, Serialize, Type, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SamlPrelogin {
|
||||
region: String,
|
||||
saml_request: String,
|
||||
}
|
||||
|
||||
impl SamlPrelogin {
|
||||
pub fn region(&self) -> &str {
|
||||
&self.region
|
||||
}
|
||||
|
||||
pub fn saml_request(&self) -> &str {
|
||||
&self.saml_request
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Type, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StandardPrelogin {
|
||||
region: String,
|
||||
auth_message: String,
|
||||
label_username: String,
|
||||
label_password: String,
|
||||
}
|
||||
|
||||
impl StandardPrelogin {
|
||||
pub fn region(&self) -> &str {
|
||||
&self.region
|
||||
}
|
||||
|
||||
pub fn auth_message(&self) -> &str {
|
||||
&self.auth_message
|
||||
}
|
||||
|
||||
pub fn label_username(&self) -> &str {
|
||||
&self.label_username
|
||||
}
|
||||
|
||||
pub fn label_password(&self) -> &str {
|
||||
&self.label_password
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Type, Clone)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum Prelogin {
|
||||
Saml(SamlPrelogin),
|
||||
Standard(StandardPrelogin),
|
||||
}
|
||||
|
||||
impl Prelogin {
|
||||
pub fn region(&self) -> &str {
|
||||
match self {
|
||||
Prelogin::Saml(saml) => saml.region(),
|
||||
Prelogin::Standard(standard) => standard.region(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn prelogin(portal: &str, user_agent: &str) -> anyhow::Result<Prelogin> {
|
||||
info!("Portal prelogin, user_agent: {}", user_agent);
|
||||
|
||||
let portal = normalize_server(portal)?;
|
||||
let prelogin_url = format!("{}/global-protect/prelogin.esp", portal);
|
||||
let client = Client::builder().user_agent(user_agent).build()?;
|
||||
|
||||
let res_xml = client
|
||||
.get(&prelogin_url)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
trace!("Prelogin response: {}", res_xml);
|
||||
let doc = Document::parse(&res_xml)?;
|
||||
|
||||
let status = xml::get_child_text(&doc, "status")
|
||||
.ok_or_else(|| anyhow::anyhow!("Prelogin response does not contain status element"))?;
|
||||
// Check the status of the prelogin response
|
||||
if status.to_uppercase() != "SUCCESS" {
|
||||
let msg = xml::get_child_text(&doc, "msg").unwrap_or(String::from("Unknown error"));
|
||||
bail!("Prelogin failed: {}", msg)
|
||||
}
|
||||
|
||||
let region = xml::get_child_text(&doc, "region")
|
||||
.ok_or_else(|| anyhow::anyhow!("Prelogin response does not contain region element"))?;
|
||||
|
||||
let saml_method = xml::get_child_text(&doc, "saml-auth-method");
|
||||
let saml_request = xml::get_child_text(&doc, "saml-request");
|
||||
// Check if the prelogin response is SAML
|
||||
if saml_method.is_some() && saml_request.is_some() {
|
||||
let saml_request = base64::decode_to_string(&saml_request.unwrap())?;
|
||||
let saml_prelogin = SamlPrelogin {
|
||||
region,
|
||||
saml_request,
|
||||
};
|
||||
|
||||
return Ok(Prelogin::Saml(saml_prelogin));
|
||||
}
|
||||
|
||||
let label_username = xml::get_child_text(&doc, "username-label");
|
||||
let label_password = xml::get_child_text(&doc, "password-label");
|
||||
// Check if the prelogin response is standard login
|
||||
if label_username.is_some() && label_password.is_some() {
|
||||
let auth_message = xml::get_child_text(&doc, "authentication-message")
|
||||
.unwrap_or(String::from("Please enter the login credentials"));
|
||||
let standard_prelogin = StandardPrelogin {
|
||||
region,
|
||||
auth_message,
|
||||
label_username: label_username.unwrap(),
|
||||
label_password: label_password.unwrap(),
|
||||
};
|
||||
|
||||
return Ok(Prelogin::Standard(standard_prelogin));
|
||||
}
|
||||
|
||||
bail!("Invalid prelogin response");
|
||||
}
|
Reference in New Issue
Block a user