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