mirror of
				https://github.com/yuezk/GlobalProtect-openconnect.git
				synced 2025-05-20 07:26:58 -04:00 
			
		
		
		
	refactor: refine auth window
This commit is contained in:
		
							
								
								
									
										1
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @@ -8,6 +8,7 @@ | |||||||
|     "prelogon", |     "prelogon", | ||||||
|     "prelogonuserauthcookie", |     "prelogonuserauthcookie", | ||||||
|     "tauri", |     "tauri", | ||||||
|  |     "unlisten", | ||||||
|     "userauthcookie", |     "userauthcookie", | ||||||
|     "vpninfo" |     "vpninfo" | ||||||
|   ], |   ], | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -60,6 +60,7 @@ dependencies = [ | |||||||
|  "tauri", |  "tauri", | ||||||
|  "tauri-build", |  "tauri-build", | ||||||
|  "tauri-plugin-log", |  "tauri-plugin-log", | ||||||
|  |  "tokio", | ||||||
|  "url", |  "url", | ||||||
|  "webkit2gtk", |  "webkit2gtk", | ||||||
| ] | ] | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ env_logger = "0.10" | |||||||
| webkit2gtk = "0.18.2" | webkit2gtk = "0.18.2" | ||||||
| regex = "1" | regex = "1" | ||||||
| url = "2.3" | url = "2.3" | ||||||
|  | tokio = { version = "1.14", features = ["full"] } | ||||||
|  |  | ||||||
| [features] | [features] | ||||||
| # by default Tauri runs in production mode | # by default Tauri runs in production mode | ||||||
|   | |||||||
| @@ -1,20 +1,21 @@ | |||||||
|  | use log::{debug, warn}; | ||||||
| use regex::Regex; | use regex::Regex; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use std::sync::{Arc}; | use std::{sync::Arc, time::Duration}; | ||||||
| use tauri::{AppHandle, Manager, WindowBuilder, WindowEvent::CloseRequested, WindowUrl}; | use tauri::EventHandler; | ||||||
| use url::Url; | use tauri::{AppHandle, Manager, Window, WindowBuilder, WindowEvent::CloseRequested, WindowUrl}; | ||||||
|  | use tokio::sync::{mpsc, Mutex}; | ||||||
|  | use tokio::time::timeout; | ||||||
| use webkit2gtk::{ | use webkit2gtk::{ | ||||||
|     gio::Cancellable, glib::GString, traits::WebViewExt, LoadEvent, URIResponseExt, WebResource, |     gio::Cancellable, glib::GString, traits::WebViewExt, LoadEvent, URIResponseExt, WebResource, | ||||||
|     WebResourceExt, |     WebResourceExt, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const AUTH_WINDOW_LABEL: &str = "auth_window"; | const AUTH_WINDOW_LABEL: &str = "auth_window"; | ||||||
| const AUTH_SUCCESS_EVENT: &str = "auth-success"; |  | ||||||
| const AUTH_ERROR_EVENT: &str = "auth-error"; | const AUTH_ERROR_EVENT: &str = "auth-error"; | ||||||
| const AUTH_CANCEL_EVENT: &str = "auth-cancel"; |  | ||||||
| const AUTH_REQUEST_EVENT: &str = "auth-request"; | const AUTH_REQUEST_EVENT: &str = "auth-request"; | ||||||
|  |  | ||||||
| #[derive(Debug, Deserialize)] | #[derive(Debug, Clone, Deserialize)] | ||||||
| pub(crate) enum SamlBinding { | pub(crate) enum SamlBinding { | ||||||
|     #[serde(rename = "REDIRECT")] |     #[serde(rename = "REDIRECT")] | ||||||
|     Redirect, |     Redirect, | ||||||
| @@ -22,30 +23,33 @@ pub(crate) enum SamlBinding { | |||||||
|     Post, |     Post, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub(crate) struct AuthOptions { | #[derive(Debug, Clone, Deserialize)] | ||||||
|  | pub(crate) struct AuthRequest { | ||||||
|  |     #[serde(alias = "samlBinding")] | ||||||
|     saml_binding: SamlBinding, |     saml_binding: SamlBinding, | ||||||
|     saml_request: String, |  | ||||||
|     user_agent: String, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Deserialize)] |  | ||||||
| struct AuthRequestPayload { |  | ||||||
|     #[serde(alias = "samlRequest")] |     #[serde(alias = "samlRequest")] | ||||||
|     saml_request: String, |     saml_request: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl AuthOptions { | impl AuthRequest { | ||||||
|     pub fn new(saml_binding: SamlBinding, saml_request: String, user_agent: String) -> Self { |     pub fn new(saml_binding: SamlBinding, saml_request: String) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             saml_binding, |             saml_binding, | ||||||
|             saml_request, |             saml_request, | ||||||
|             user_agent, |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl TryFrom<Option<&str>> for AuthRequest { | ||||||
|  |     type Error = serde_json::Error; | ||||||
|  |  | ||||||
|  |     fn try_from(value: Option<&str>) -> Result<Self, Self::Error> { | ||||||
|  |         serde_json::from_str(value.unwrap_or("{}")) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Serialize)] | #[derive(Debug, Clone, Serialize)] | ||||||
| pub struct AuthData { | pub(crate) struct AuthData { | ||||||
|     username: Option<String>, |     username: Option<String>, | ||||||
|     prelogin_cookie: Option<String>, |     prelogin_cookie: Option<String>, | ||||||
|     portal_userauthcookie: Option<String>, |     portal_userauthcookie: Option<String>, | ||||||
| @@ -72,105 +76,136 @@ impl AuthData { | |||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| enum AuthError { | enum AuthError { | ||||||
|     NotFound, |     TokenNotFound, | ||||||
|     Invalid, |     TokenInvalid, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| struct AuthEventEmitter { | enum AuthEvent { | ||||||
|     app_handle: AppHandle, |     Request(AuthRequest), | ||||||
|  |     Success(AuthData), | ||||||
|  |     Error(AuthError), | ||||||
|  |     Cancel, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl AuthEventEmitter { | pub(crate) async fn saml_login( | ||||||
|     fn new(app_handle: AppHandle) -> Self { |     auth_request: AuthRequest, | ||||||
|         Self { app_handle } |     ua: &str, | ||||||
|     } |     app_handle: &AppHandle, | ||||||
|  | ) -> tauri::Result<Option<AuthData>> { | ||||||
|  |     let (event_tx, event_rx) = mpsc::channel::<AuthEvent>(8); | ||||||
|  |     let window = build_window(app_handle, ua)?; | ||||||
|  |     setup_webview(&window, event_tx.clone())?; | ||||||
|  |     let handler_id = setup_window(&window, event_tx); | ||||||
|  |  | ||||||
|     fn emit_success(&self, saml_result: AuthData) { |     match process(&window, event_rx, auth_request).await { | ||||||
|         self.app_handle.emit_all(AUTH_SUCCESS_EVENT, saml_result); |         Ok(auth_data) => { | ||||||
|         if let Some(window) = self.app_handle.get_window(AUTH_WINDOW_LABEL) { |             window.unlisten(handler_id); | ||||||
|             window.close(); |             Ok(auth_data) | ||||||
|  |         } | ||||||
|  |         Err(err) => { | ||||||
|  |             window.unlisten(handler_id); | ||||||
|  |             Err(err) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|     fn emit_error(&self, error: String) { | fn build_window(app_handle: &AppHandle, ua: &str) -> tauri::Result<Window> { | ||||||
|         self.app_handle.emit_all(AUTH_ERROR_EVENT, error); |     let url = WindowUrl::App("auth.html".into()); | ||||||
|     } |     WindowBuilder::new(app_handle, AUTH_WINDOW_LABEL, url) | ||||||
|  |         .visible(false) | ||||||
|     fn emit_cancel(&self) { |  | ||||||
|         self.app_handle.emit_all(AUTH_CANCEL_EVENT, ()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug)] |  | ||||||
| pub(crate) struct AuthWindow { |  | ||||||
|     event_emitter: Arc<AuthEventEmitter>, |  | ||||||
|     app_handle: AppHandle, |  | ||||||
|     saml_binding: SamlBinding, |  | ||||||
|     user_agent: String, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl AuthWindow { |  | ||||||
|     pub fn new(app_handle: AppHandle, saml_binding: SamlBinding, user_agent: String) -> Self { |  | ||||||
|         Self { |  | ||||||
|             event_emitter: Arc::new(AuthEventEmitter::new(app_handle.clone())), |  | ||||||
|             app_handle, |  | ||||||
|             saml_binding, |  | ||||||
|             user_agent, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn process(&self, saml_request: String) -> tauri::Result<()> { |  | ||||||
|         let url = self.window_url(&saml_request)?; |  | ||||||
|         let window = WindowBuilder::new(&self.app_handle, AUTH_WINDOW_LABEL, url) |  | ||||||
|         .title("GlobalProtect Login") |         .title("GlobalProtect Login") | ||||||
|             .user_agent(&self.user_agent) |         .user_agent(ua) | ||||||
|         .always_on_top(true) |         .always_on_top(true) | ||||||
|         .focused(true) |         .focused(true) | ||||||
|         .center() |         .center() | ||||||
|             .build()?; |         .build() | ||||||
|  | } | ||||||
|         let event_emitter = self.event_emitter.clone(); |  | ||||||
|         let is_post = matches!(self.saml_binding, SamlBinding::Post); |  | ||||||
|  |  | ||||||
|  | fn setup_webview(window: &Window, event_tx: mpsc::Sender<AuthEvent>) -> tauri::Result<()> { | ||||||
|     window.with_webview(move |wv| { |     window.with_webview(move |wv| { | ||||||
|         let wv = wv.inner(); |         let wv = wv.inner(); | ||||||
|             // Load SAML request as HTML if POST binding is used |         let event_tx = event_tx.clone(); | ||||||
|             if is_post { |  | ||||||
|                 wv.load_html(&saml_request, None); |  | ||||||
|             } |  | ||||||
|         wv.connect_load_changed(move |wv, event| { |         wv.connect_load_changed(move |wv, event| { | ||||||
|                 if LoadEvent::Finished == event { |             if LoadEvent::Finished != event { | ||||||
|                     if let Some(uri) = wv.uri() { |                 debug!("Skipping load event: {:?}", event); | ||||||
|                         if uri.is_empty() { |  | ||||||
|                             println!("Empty URI"); |  | ||||||
|                             event_emitter.emit_error("Empty URI".to_string()); |  | ||||||
|                 return; |                 return; | ||||||
|                         } else { |  | ||||||
|                             println!("Loaded URI: {}", uri); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             let uri = wv.uri().unwrap_or("".into()); | ||||||
|  |             // Empty URI indicates that an error occurred | ||||||
|  |             if uri.is_empty() { | ||||||
|  |                 warn!("Empty URI"); | ||||||
|  |                 if let Err(err) = event_tx.blocking_send(AuthEvent::Error(AuthError::TokenInvalid)) | ||||||
|  |                 { | ||||||
|  |                     println!("Error sending event: {}", err); | ||||||
|                 } |                 } | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             // TODO, redact URI | ||||||
|  |             debug!("Loaded URI: {}", uri); | ||||||
|  |  | ||||||
|             if let Some(main_res) = wv.main_resource() { |             if let Some(main_res) = wv.main_resource() { | ||||||
|                         AuthResultParser::new(&event_emitter).parse(&main_res); |                 // AuthDataParser::new(&window_tx_clone).parse(&main_res); | ||||||
|                     } |                 parse_auth_data(&main_res, event_tx.clone()); | ||||||
|  |             } else { | ||||||
|  |                 warn!("No main_resource"); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         })?; |  | ||||||
|  |  | ||||||
|         let event_emitter = self.event_emitter.clone(); |         wv.connect_load_failed(|_wv, event, err_msg, err| { | ||||||
|  |             println!("Load failed: {:?}, {}, {:?}", event, err_msg, err); | ||||||
|  |             false | ||||||
|  |         }); | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn setup_window(window: &Window, event_tx: mpsc::Sender<AuthEvent>) -> EventHandler { | ||||||
|  |     let event_tx_clone = event_tx.clone(); | ||||||
|     window.on_window_event(move |event| { |     window.on_window_event(move |event| { | ||||||
|             if let CloseRequested { .. } = event { |         if let CloseRequested { api, .. } = event { | ||||||
|                 event_emitter.emit_cancel(); |             api.prevent_close(); | ||||||
|  |             if let Err(err) = event_tx_clone.blocking_send(AuthEvent::Cancel) { | ||||||
|  |                 println!("Error sending event: {}", err) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|         let window_clone = window.clone(); |     window.open_devtools(); | ||||||
|         window.listen_global(AUTH_REQUEST_EVENT, move |event| { |  | ||||||
|             let auth_request_payload: AuthRequestPayload = serde_json::from_str(event.payload().unwrap()).unwrap(); |  | ||||||
|             let saml_request = auth_request_payload.saml_request; |  | ||||||
|  |  | ||||||
|             window_clone.with_webview(move |wv| { |     window.listen_global(AUTH_REQUEST_EVENT, move |event| { | ||||||
|  |         if let Ok(payload) = TryInto::<AuthRequest>::try_into(event.payload()) { | ||||||
|  |             debug!("---------Received auth request"); | ||||||
|  |  | ||||||
|  |             let event_tx = event_tx.clone(); | ||||||
|  |             let _ = tokio::spawn(async move { | ||||||
|  |                 if let Err(err) = event_tx.send(AuthEvent::Request(payload)).await { | ||||||
|  |                     warn!("Error sending event: {}", err); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async fn process( | ||||||
|  |     window: &Window, | ||||||
|  |     event_rx: mpsc::Receiver<AuthEvent>, | ||||||
|  |     auth_request: AuthRequest, | ||||||
|  | ) -> tauri::Result<Option<AuthData>> { | ||||||
|  |     process_request(window, auth_request)?; | ||||||
|  |  | ||||||
|  |     let (close_tx, close_rx) = mpsc::channel::<()>(1); | ||||||
|  |  | ||||||
|  |     tokio::spawn(show_window_after_timeout(window.clone(), close_rx)); | ||||||
|  |     process_auth_event(&window, event_rx, close_tx).await | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn process_request(window: &Window, auth_request: AuthRequest) -> tauri::Result<()> { | ||||||
|  |     let saml_request = auth_request.saml_request; | ||||||
|  |     let is_post = matches!(auth_request.saml_binding, SamlBinding::Post); | ||||||
|  |  | ||||||
|  |     window.with_webview(move |wv| { | ||||||
|         let wv = wv.inner(); |         let wv = wv.inner(); | ||||||
|         if is_post { |         if is_post { | ||||||
|             // Load SAML request as HTML if POST binding is used |             // Load SAML request as HTML if POST binding is used | ||||||
| @@ -180,75 +215,122 @@ impl AuthWindow { | |||||||
|             // Redirect to SAML request URL if REDIRECT binding is used |             // Redirect to SAML request URL if REDIRECT binding is used | ||||||
|             wv.load_uri(&saml_request); |             wv.load_uri(&saml_request); | ||||||
|         } |         } | ||||||
|             }); |     }) | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         Ok(()) |  | ||||||
| } | } | ||||||
|  |  | ||||||
|     fn window_url(&self, saml_request: &String) -> tauri::Result<WindowUrl> { | async fn show_window_after_timeout(window: Window, mut close_rx: mpsc::Receiver<()>) { | ||||||
|         match self.saml_binding { |     // Show the window after 10 seconds | ||||||
|             SamlBinding::Redirect => match Url::parse(saml_request) { |     let duration = Duration::from_secs(10); | ||||||
|                 Ok(url) => Ok(WindowUrl::External(url)), |     if let Err(_) = timeout(duration, close_rx.recv()).await { | ||||||
|                 Err(err) => Err(tauri::Error::InvalidUrl(err)), |         println!("Final show window"); | ||||||
|             }, |         show_window(&window); | ||||||
|             SamlBinding::Post => Ok(WindowUrl::App("auth.html".into())), |     } else { | ||||||
|  |         println!("Window closed, cancel the final show window"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async fn process_auth_event( | ||||||
|  |     window: &Window, | ||||||
|  |     mut event_rx: mpsc::Receiver<AuthEvent>, | ||||||
|  |     close_tx: mpsc::Sender<()>, | ||||||
|  | ) -> tauri::Result<Option<AuthData>> { | ||||||
|  |     let (cancel_timeout_tx, cancel_timeout_rx) = mpsc::channel::<()>(1); | ||||||
|  |     let cancel_timeout_rx = Arc::new(Mutex::new(cancel_timeout_rx)); | ||||||
|  |  | ||||||
|  |     async fn close_window(window: &Window, close_tx: mpsc::Sender<()>) { | ||||||
|  |         if let Err(err) = window.close() { | ||||||
|  |             println!("Error closing window: {}", err); | ||||||
|  |         } | ||||||
|  |         if let Err(err) = close_tx.send(()).await { | ||||||
|  |             warn!("Error sending the close event: {:?}", err); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     loop { | ||||||
|  |         if let Some(auth_event) = event_rx.recv().await { | ||||||
|  |             match auth_event { | ||||||
|  |                 AuthEvent::Request(auth_request) => { | ||||||
|  |                     println!("Got auth request: {:?}", auth_request); | ||||||
|  |                     process_request(&window, auth_request)?; | ||||||
|  |                 } | ||||||
|  |                 AuthEvent::Success(auth_data) => { | ||||||
|  |                     close_window(window, close_tx).await; | ||||||
|  |                     return Ok(Some(auth_data)); | ||||||
|  |                 } | ||||||
|  |                 AuthEvent::Cancel => { | ||||||
|  |                     close_window(window, close_tx).await; | ||||||
|  |                     return Ok(None); | ||||||
|  |                 } | ||||||
|  |                 AuthEvent::Error(AuthError::TokenInvalid) => { | ||||||
|  |                     if let Err(err) = cancel_timeout_tx.send(()).await { | ||||||
|  |                         println!("Error sending event: {}", err); | ||||||
|  |                     } | ||||||
|  |                     if let Err(err) = | ||||||
|  |                         window.emit_all(AUTH_ERROR_EVENT, "Invalid SAML result".to_string()) | ||||||
|  |                     { | ||||||
|  |                         warn!("Error emitting auth-error event: {:?}", err); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 AuthEvent::Error(AuthError::TokenNotFound) => { | ||||||
|  |                     let cancel_timeout_rx = cancel_timeout_rx.clone(); | ||||||
|  |                     tokio::spawn(handle_token_not_found(window.clone(), cancel_timeout_rx)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| struct AuthResultParser<'a> { | async fn handle_token_not_found(window: Window, cancel_timeout_rx: Arc<Mutex<mpsc::Receiver<()>>>) { | ||||||
|     event_emitter: &'a Arc<AuthEventEmitter>, |     // Tokens not found, show the window in 5 seconds | ||||||
|  |     match cancel_timeout_rx.try_lock() { | ||||||
|  |         Ok(mut cancel_timeout_rx) => { | ||||||
|  |             println!("Scheduling timeout"); | ||||||
|  |             let duration = Duration::from_secs(5); | ||||||
|  |             if let Err(_) = timeout(duration, cancel_timeout_rx.recv()).await { | ||||||
|  |                 println!("Show window after timeout"); | ||||||
|  |                 show_window(&window); | ||||||
|  |             } else { | ||||||
|  |                 println!("Cancel timeout"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Err(_) => { | ||||||
|  |             println!("Timeout already scheduled"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<'a> AuthResultParser<'a> { | fn parse_auth_data(main_res: &WebResource, event_tx: mpsc::Sender<AuthEvent>) { | ||||||
|     fn new(event_emitter: &'a Arc<AuthEventEmitter>) -> Self { |  | ||||||
|         Self { event_emitter } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn parse(&self, main_res: &WebResource) { |  | ||||||
|     if let Some(response) = main_res.response() { |     if let Some(response) = main_res.response() { | ||||||
|             if let Some(saml_result) = read_auth_result_from_response(&response) { |         if let Some(saml_result) = read_auth_data_from_response(&response) { | ||||||
|             // Got SAML result from HTTP headers |             // Got SAML result from HTTP headers | ||||||
|             println!("SAML result: {:?}", saml_result); |             println!("SAML result: {:?}", saml_result); | ||||||
|                 self.event_emitter.emit_success(saml_result); |             send_auth_data(&event_tx, saml_result); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|         let event_emitter = self.event_emitter.clone(); |     let event_tx = event_tx.clone(); | ||||||
|     main_res.data(Cancellable::NONE, move |data| { |     main_res.data(Cancellable::NONE, move |data| { | ||||||
|         if let Ok(data) = data { |         if let Ok(data) = data { | ||||||
|             let html = String::from_utf8_lossy(&data); |             let html = String::from_utf8_lossy(&data); | ||||||
|                 match read_auth_result_from_html(&html) { |             match read_auth_data_from_html(&html) { | ||||||
|                 Ok(saml_result) => { |                 Ok(saml_result) => { | ||||||
|                     // Got SAML result from HTML |                     // Got SAML result from HTML | ||||||
|                     println!("SAML result: {:?}", saml_result); |                     println!("SAML result: {:?}", saml_result); | ||||||
|                         event_emitter.emit_success(saml_result); |                     send_auth_data(&event_tx, saml_result); | ||||||
|                         return; |  | ||||||
|                 } |                 } | ||||||
|                     Err(AuthError::Invalid) => { |                 Err(err) => { | ||||||
|                         // Invalid SAML result |                     println!("Auth error: {:?}", err); | ||||||
|                         println!("Invalid SAML result"); |                     if let Err(err) = event_tx.blocking_send(AuthEvent::Error(err)) { | ||||||
|                         event_emitter.emit_error("Invalid SAML result".to_string()) |                         println!("Error sending event: {}", err) | ||||||
|                     } |                     } | ||||||
|                     Err(AuthError::NotFound) => { |  | ||||||
|                         let has_form = html.contains("</form>"); |  | ||||||
|                         if has_form { |  | ||||||
|                             // SAML form found |  | ||||||
|                             println!("SAML form found"); |  | ||||||
|                         } else { |  | ||||||
|                             // No SAML form found |  | ||||||
|                             println!("No SAML form found"); |  | ||||||
|                 } |                 } | ||||||
|                     }, |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| } |  | ||||||
|  |  | ||||||
| fn read_auth_result_from_response(response: &webkit2gtk::URIResponse) -> Option<AuthData> { | fn read_auth_data_from_response(response: &webkit2gtk::URIResponse) -> Option<AuthData> { | ||||||
|     response.http_headers().and_then(|mut headers| { |     response.http_headers().and_then(|mut headers| { | ||||||
|         let saml_result = AuthData::new( |         let saml_result = AuthData::new( | ||||||
|             headers.get("saml-username").map(GString::into), |             headers.get("saml-username").map(GString::into), | ||||||
| @@ -264,14 +346,13 @@ fn read_auth_result_from_response(response: &webkit2gtk::URIResponse) -> Option< | |||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| fn read_auth_result_from_html(html: &str) -> Result<AuthData, AuthError> { | fn read_auth_data_from_html(html: &str) -> Result<AuthData, AuthError> { | ||||||
|     let saml_auth_status = parse_xml_tag(html, "saml-auth-status"); |     let saml_auth_status = parse_xml_tag(html, "saml-auth-status"); | ||||||
|  |  | ||||||
|  |  | ||||||
|     match saml_auth_status { |     match saml_auth_status { | ||||||
|         Some(status) if status == "1" => extract_auth_data(html).ok_or(AuthError::Invalid), |         Some(status) if status == "1" => extract_auth_data(html).ok_or(AuthError::TokenInvalid), | ||||||
|         Some(status) if status == "-1" => Err(AuthError::Invalid), |         Some(status) if status == "-1" => Err(AuthError::TokenInvalid), | ||||||
|         _ => Err(AuthError::NotFound), |         _ => Err(AuthError::TokenNotFound), | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -295,3 +376,22 @@ fn parse_xml_tag(html: &str, tag: &str) -> Option<String> { | |||||||
|         .and_then(|captures| captures.get(1)) |         .and_then(|captures| captures.get(1)) | ||||||
|         .map(|m| m.as_str().to_string()) |         .map(|m| m.as_str().to_string()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn send_auth_data(event_tx: &mpsc::Sender<AuthEvent>, saml_result: AuthData) { | ||||||
|  |     if let Err(err) = event_tx.blocking_send(AuthEvent::Success(saml_result)) { | ||||||
|  |         println!("Error sending event: {}", err) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn show_window(window: &Window) { | ||||||
|  |     match window.is_visible() { | ||||||
|  |         Ok(true) => { | ||||||
|  |             println!("Window is already visible"); | ||||||
|  |         } | ||||||
|  |         _ => { | ||||||
|  |             if let Err(err) = window.show() { | ||||||
|  |                 println!("Error showing window: {}", err); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
|     windows_subsystem = "windows" |     windows_subsystem = "windows" | ||||||
| )] | )] | ||||||
|  |  | ||||||
| use auth::{SamlBinding, AuthWindow}; | use auth::{AuthData, AuthRequest, SamlBinding}; | ||||||
| use env_logger::Env; | use env_logger::Env; | ||||||
| use gpcommon::{Client, ServerApiError, VpnStatus}; | use gpcommon::{Client, ServerApiError, VpnStatus}; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| @@ -37,13 +37,9 @@ async fn saml_login( | |||||||
|     binding: SamlBinding, |     binding: SamlBinding, | ||||||
|     request: String, |     request: String, | ||||||
|     app_handle: AppHandle, |     app_handle: AppHandle, | ||||||
| ) -> tauri::Result<()> { | ) -> tauri::Result<Option<AuthData>> { | ||||||
|     let auth_window = AuthWindow::new(app_handle, binding, String::from("PAN GlobalProtect")); |     let ua = "PAN GlobalProtect"; | ||||||
|     if let Err(err) = auth_window.process(request) { |     auth::saml_login(AuthRequest::new(binding, request), ua, &app_handle).await | ||||||
|         println!("Error processing auth window: {}", err); |  | ||||||
|         return Err(err); |  | ||||||
|     } |  | ||||||
|     Ok(()) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Serialize)] | #[derive(Debug, Clone, Serialize)] | ||||||
|   | |||||||
| @@ -41,7 +41,10 @@ export default function App() { | |||||||
|     authService.onAuthError(async () => { |     authService.onAuthError(async () => { | ||||||
|       const preloginResponse = await portalService.prelogin(portalAddress); |       const preloginResponse = await portalService.prelogin(portalAddress); | ||||||
|       // Retry SAML login when auth error occurs |       // Retry SAML login when auth error occurs | ||||||
|       authService.emitAuthRequest(preloginResponse.samlAuthRequest!); |       authService.emitAuthRequest({ | ||||||
|  |         samlBinding: preloginResponse.samlAuthMethod!, | ||||||
|  |         samlRequest: preloginResponse.samlAuthRequest!, | ||||||
|  |       }); | ||||||
|     }); |     }); | ||||||
|     authService.onAuthCancel(() => {}); |     authService.onAuthCancel(() => {}); | ||||||
|   }, [portalAddress]); |   }, [portalAddress]); | ||||||
| @@ -74,7 +77,11 @@ export default function App() { | |||||||
|  |  | ||||||
|       if (portalService.isSamlAuth(response)) { |       if (portalService.isSamlAuth(response)) { | ||||||
|         const { samlAuthMethod, samlAuthRequest } = response; |         const { samlAuthMethod, samlAuthRequest } = response; | ||||||
|         await authService.samlLogin(samlAuthMethod, samlAuthRequest); |         const authData = await authService.samlLogin( | ||||||
|  |           samlAuthMethod, | ||||||
|  |           samlAuthRequest | ||||||
|  |         ); | ||||||
|  |         console.log("authData", authData); | ||||||
|       } else if (portalService.isPasswordAuth(response)) { |       } else if (portalService.isPasswordAuth(response)) { | ||||||
|         setPasswordAuthOpen(true); |         setPasswordAuthOpen(true); | ||||||
|         setPasswordAuth({ |         setPasswordAuth({ | ||||||
|   | |||||||
| @@ -42,11 +42,17 @@ class AuthService { | |||||||
|  |  | ||||||
|   // binding: "POST" | "REDIRECT" |   // binding: "POST" | "REDIRECT" | ||||||
|   async samlLogin(binding: string, request: string) { |   async samlLogin(binding: string, request: string) { | ||||||
|     return invokeCommand("saml_login", { binding, request }); |     return invokeCommand<AuthData>("saml_login", { binding, request }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   emitAuthRequest(authRequest: string) { |   emitAuthRequest({ | ||||||
|     emit("auth-request", { samlRequest: authRequest }); |     samlBinding, | ||||||
|  |     samlRequest, | ||||||
|  |   }: { | ||||||
|  |     samlBinding: string; | ||||||
|  |     samlRequest: string; | ||||||
|  |   }) { | ||||||
|  |     emit("auth-request", { samlBinding, samlRequest }); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user