mirror of
				https://github.com/yuezk/GlobalProtect-openconnect.git
				synced 2025-05-20 07:26:58 -04:00 
			
		
		
		
	refactor: refine the SAML login workflow
This commit is contained in:
		@@ -6,10 +6,10 @@ use tauri::EventHandler;
 | 
				
			|||||||
use tauri::{AppHandle, Manager, Window, WindowBuilder, WindowEvent::CloseRequested, WindowUrl};
 | 
					use tauri::{AppHandle, Manager, Window, WindowBuilder, WindowEvent::CloseRequested, WindowUrl};
 | 
				
			||||||
use tokio::sync::{mpsc, Mutex};
 | 
					use tokio::sync::{mpsc, Mutex};
 | 
				
			||||||
use tokio::time::timeout;
 | 
					use tokio::time::timeout;
 | 
				
			||||||
use webkit2gtk::{
 | 
					use webkit2gtk::gio::Cancellable;
 | 
				
			||||||
    gio::Cancellable, glib::GString, traits::WebViewExt, LoadEvent, URIResponseExt, WebResource,
 | 
					use webkit2gtk::glib::GString;
 | 
				
			||||||
    WebResourceExt,
 | 
					use webkit2gtk::traits::{URIResponseExt, WebViewExt};
 | 
				
			||||||
};
 | 
					use webkit2gtk::{CookieManagerExt, LoadEvent, WebContextExt, WebResource, WebResourceExt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const AUTH_WINDOW_LABEL: &str = "auth_window";
 | 
					const AUTH_WINDOW_LABEL: &str = "auth_window";
 | 
				
			||||||
const AUTH_ERROR_EVENT: &str = "auth-error";
 | 
					const AUTH_ERROR_EVENT: &str = "auth-error";
 | 
				
			||||||
@@ -96,11 +96,14 @@ enum AuthEvent {
 | 
				
			|||||||
pub(crate) async fn saml_login(
 | 
					pub(crate) async fn saml_login(
 | 
				
			||||||
    auth_request: AuthRequest,
 | 
					    auth_request: AuthRequest,
 | 
				
			||||||
    ua: &str,
 | 
					    ua: &str,
 | 
				
			||||||
 | 
					    clear_cookies: bool,
 | 
				
			||||||
    app_handle: &AppHandle,
 | 
					    app_handle: &AppHandle,
 | 
				
			||||||
) -> tauri::Result<Option<AuthData>> {
 | 
					) -> tauri::Result<Option<AuthData>> {
 | 
				
			||||||
 | 
					    info!("Starting SAML login");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let (event_tx, event_rx) = mpsc::channel::<AuthEvent>(8);
 | 
					    let (event_tx, event_rx) = mpsc::channel::<AuthEvent>(8);
 | 
				
			||||||
    let window = build_window(app_handle, ua)?;
 | 
					    let window = build_window(app_handle, ua)?;
 | 
				
			||||||
    setup_webview(&window, event_tx.clone())?;
 | 
					    setup_webview(&window, clear_cookies, event_tx.clone())?;
 | 
				
			||||||
    let handler = setup_window(&window, event_tx);
 | 
					    let handler = setup_window(&window, event_tx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let result = process(&window, auth_request, event_rx).await;
 | 
					    let result = process(&window, auth_request, event_rx).await;
 | 
				
			||||||
@@ -121,11 +124,19 @@ fn build_window(app_handle: &AppHandle, ua: &str) -> tauri::Result<Window> {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Setup webview events
 | 
					// Setup webview events
 | 
				
			||||||
fn setup_webview(window: &Window, event_tx: mpsc::Sender<AuthEvent>) -> tauri::Result<()> {
 | 
					fn setup_webview(
 | 
				
			||||||
 | 
					    window: &Window,
 | 
				
			||||||
 | 
					    clear_cookies: bool,
 | 
				
			||||||
 | 
					    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();
 | 
				
			||||||
        let event_tx = event_tx.clone();
 | 
					        let event_tx = event_tx.clone();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if clear_cookies {
 | 
				
			||||||
 | 
					            clear_webview_cookies(&wv);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        wv.connect_load_changed(move |wv, event| {
 | 
					        wv.connect_load_changed(move |wv, event| {
 | 
				
			||||||
            if LoadEvent::Finished != event {
 | 
					            if LoadEvent::Finished != event {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
@@ -172,8 +183,6 @@ fn setup_window(window: &Window, event_tx: mpsc::Sender<AuthEvent>) -> EventHand
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    window.listen_global(AUTH_REQUEST_EVENT, move |event| {
 | 
					    window.listen_global(AUTH_REQUEST_EVENT, move |event| {
 | 
				
			||||||
        if let Ok(payload) = TryInto::<AuthRequest>::try_into(event.payload()) {
 | 
					        if let Ok(payload) = TryInto::<AuthRequest>::try_into(event.payload()) {
 | 
				
			||||||
            debug!("---------Received auth request");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let event_tx = event_tx.clone();
 | 
					            let event_tx = event_tx.clone();
 | 
				
			||||||
            let _ = tokio::spawn(async move {
 | 
					            let _ = tokio::spawn(async move {
 | 
				
			||||||
                if let Err(err) = event_tx.send(AuthEvent::Request(payload)).await {
 | 
					                if let Err(err) = event_tx.send(AuthEvent::Request(payload)).await {
 | 
				
			||||||
@@ -189,6 +198,8 @@ async fn process(
 | 
				
			|||||||
    auth_request: AuthRequest,
 | 
					    auth_request: AuthRequest,
 | 
				
			||||||
    event_rx: mpsc::Receiver<AuthEvent>,
 | 
					    event_rx: mpsc::Receiver<AuthEvent>,
 | 
				
			||||||
) -> tauri::Result<Option<AuthData>> {
 | 
					) -> tauri::Result<Option<AuthData>> {
 | 
				
			||||||
 | 
					    info!("Processing auth request: {:?}", auth_request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    process_request(window, auth_request)?;
 | 
					    process_request(window, auth_request)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let handle = tokio::spawn(show_window_after_timeout(window.clone()));
 | 
					    let handle = tokio::spawn(show_window_after_timeout(window.clone()));
 | 
				
			||||||
@@ -205,9 +216,11 @@ fn process_request(window: &Window, auth_request: AuthRequest) -> tauri::Result<
 | 
				
			|||||||
        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
 | 
				
			||||||
 | 
					            info!("Loading SAML request as HTML");
 | 
				
			||||||
            wv.load_html(&saml_request, None);
 | 
					            wv.load_html(&saml_request, None);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // Redirect to SAML request URL if REDIRECT binding is used
 | 
					            // Redirect to SAML request URL if REDIRECT binding is used
 | 
				
			||||||
 | 
					            info!("Redirecting to SAML request URL");
 | 
				
			||||||
            wv.load_uri(&saml_request);
 | 
					            wv.load_uri(&saml_request);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
@@ -215,7 +228,10 @@ fn process_request(window: &Window, auth_request: AuthRequest) -> tauri::Result<
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
async fn show_window_after_timeout(window: Window) {
 | 
					async fn show_window_after_timeout(window: Window) {
 | 
				
			||||||
    tokio::time::sleep(Duration::from_secs(FALLBACK_SHOW_WINDOW_TIMEOUT)).await;
 | 
					    tokio::time::sleep(Duration::from_secs(FALLBACK_SHOW_WINDOW_TIMEOUT)).await;
 | 
				
			||||||
    info!("Showing window after timeout expired: {} seconds", FALLBACK_SHOW_WINDOW_TIMEOUT);
 | 
					    info!(
 | 
				
			||||||
 | 
					        "Showing window after timeout ({:?} seconds)",
 | 
				
			||||||
 | 
					        FALLBACK_SHOW_WINDOW_TIMEOUT
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    show_window(&window);
 | 
					    show_window(&window);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -223,6 +239,8 @@ async fn process_auth_event(
 | 
				
			|||||||
    window: &Window,
 | 
					    window: &Window,
 | 
				
			||||||
    mut event_rx: mpsc::Receiver<AuthEvent>,
 | 
					    mut event_rx: mpsc::Receiver<AuthEvent>,
 | 
				
			||||||
) -> Option<AuthData> {
 | 
					) -> Option<AuthData> {
 | 
				
			||||||
 | 
					    info!("Processing auth event...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let (cancel_timeout_tx, cancel_timeout_rx) = mpsc::channel::<()>(1);
 | 
					    let (cancel_timeout_tx, cancel_timeout_rx) = mpsc::channel::<()>(1);
 | 
				
			||||||
    let cancel_timeout_rx = Arc::new(Mutex::new(cancel_timeout_rx));
 | 
					    let cancel_timeout_rx = Arc::new(Mutex::new(cancel_timeout_rx));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -236,10 +254,12 @@ async fn process_auth_event(
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                AuthEvent::Success(auth_data) => {
 | 
					                AuthEvent::Success(auth_data) => {
 | 
				
			||||||
 | 
					                    info!("Got auth data successfully, closing window");
 | 
				
			||||||
                    close_window(window);
 | 
					                    close_window(window);
 | 
				
			||||||
                    return Some(auth_data);
 | 
					                    return Some(auth_data);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                AuthEvent::Cancel => {
 | 
					                AuthEvent::Cancel => {
 | 
				
			||||||
 | 
					                    info!("User cancelled the authentication process, closing window");
 | 
				
			||||||
                    close_window(window);
 | 
					                    close_window(window);
 | 
				
			||||||
                    return None;
 | 
					                    return None;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -255,6 +275,16 @@ async fn process_auth_event(
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                AuthEvent::Error(AuthError::TokenNotFound) => {
 | 
					                AuthEvent::Error(AuthError::TokenNotFound) => {
 | 
				
			||||||
 | 
					                    let window_visible = window.is_visible().unwrap_or(false);
 | 
				
			||||||
 | 
					                    if window_visible {
 | 
				
			||||||
 | 
					                        continue;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    info!(
 | 
				
			||||||
 | 
					                        "Token not found, showing window in {} seconds",
 | 
				
			||||||
 | 
					                        SHOW_WINDOW_TIMEOUT
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    let cancel_timeout_rx = cancel_timeout_rx.clone();
 | 
					                    let cancel_timeout_rx = cancel_timeout_rx.clone();
 | 
				
			||||||
                    tokio::spawn(handle_token_not_found(window.clone(), cancel_timeout_rx));
 | 
					                    tokio::spawn(handle_token_not_found(window.clone(), cancel_timeout_rx));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -269,17 +299,16 @@ async fn process_auth_event(
 | 
				
			|||||||
async fn handle_token_not_found(window: Window, cancel_timeout_rx: Arc<Mutex<mpsc::Receiver<()>>>) {
 | 
					async fn handle_token_not_found(window: Window, cancel_timeout_rx: Arc<Mutex<mpsc::Receiver<()>>>) {
 | 
				
			||||||
    match cancel_timeout_rx.try_lock() {
 | 
					    match cancel_timeout_rx.try_lock() {
 | 
				
			||||||
        Ok(mut cancel_timeout_rx) => {
 | 
					        Ok(mut cancel_timeout_rx) => {
 | 
				
			||||||
            debug!("Scheduling timeout to show window");
 | 
					 | 
				
			||||||
            let duration = Duration::from_secs(SHOW_WINDOW_TIMEOUT);
 | 
					            let duration = Duration::from_secs(SHOW_WINDOW_TIMEOUT);
 | 
				
			||||||
            if let Err(_) = timeout(duration, cancel_timeout_rx.recv()).await {
 | 
					            if let Err(_) = timeout(duration, cancel_timeout_rx.recv()).await {
 | 
				
			||||||
                info!("Timeout expired, showing window");
 | 
					                info!("Timeout expired, showing window");
 | 
				
			||||||
                show_window(&window);
 | 
					                show_window(&window);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                debug!("Showing window timeout cancelled");
 | 
					                info!("Showing window timeout cancelled");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Err(_) => {
 | 
					        Err(_) => {
 | 
				
			||||||
            debug!("Timeout already scheduled, skipping");
 | 
					            debug!("Window will be shown by another task, skipping");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -289,7 +318,7 @@ async fn handle_token_not_found(window: Window, cancel_timeout_rx: Arc<Mutex<mps
 | 
				
			|||||||
fn parse_auth_data(main_res: &WebResource, event_tx: mpsc::Sender<AuthEvent>) {
 | 
					fn parse_auth_data(main_res: &WebResource, event_tx: mpsc::Sender<AuthEvent>) {
 | 
				
			||||||
    if let Some(response) = main_res.response() {
 | 
					    if let Some(response) = main_res.response() {
 | 
				
			||||||
        if let Some(auth_data) = read_auth_data_from_response(&response) {
 | 
					        if let Some(auth_data) = read_auth_data_from_response(&response) {
 | 
				
			||||||
            info!("Got auth data from HTTP headers: {:?}", auth_data);
 | 
					            debug!("Got auth data from HTTP headers: {:?}", auth_data);
 | 
				
			||||||
            send_auth_data(&event_tx, auth_data);
 | 
					            send_auth_data(&event_tx, auth_data);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -301,7 +330,7 @@ fn parse_auth_data(main_res: &WebResource, event_tx: mpsc::Sender<AuthEvent>) {
 | 
				
			|||||||
            let html = String::from_utf8_lossy(&data);
 | 
					            let html = String::from_utf8_lossy(&data);
 | 
				
			||||||
            match read_auth_data_from_html(&html) {
 | 
					            match read_auth_data_from_html(&html) {
 | 
				
			||||||
                Ok(auth_data) => {
 | 
					                Ok(auth_data) => {
 | 
				
			||||||
                    info!("Got auth data from HTML: {:?}", auth_data);
 | 
					                    debug!("Got auth data from HTML: {:?}", auth_data);
 | 
				
			||||||
                    send_auth_data(&event_tx, auth_data);
 | 
					                    send_auth_data(&event_tx, auth_data);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                Err(err) => {
 | 
					                Err(err) => {
 | 
				
			||||||
@@ -372,16 +401,15 @@ fn send_auth_data(event_tx: &mpsc::Sender<AuthEvent>, auth_data: AuthData) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn show_window(window: &Window) {
 | 
					fn show_window(window: &Window) {
 | 
				
			||||||
    match window.is_visible() {
 | 
					    let visible = window.is_visible().unwrap_or(false);
 | 
				
			||||||
        Ok(true) => {
 | 
					    if visible {
 | 
				
			||||||
            debug!("Window is already visible");
 | 
					        debug!("Window is already visible, skipping");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
        _ => {
 | 
					
 | 
				
			||||||
    if let Err(err) = window.show() {
 | 
					    if let Err(err) = window.show() {
 | 
				
			||||||
        warn!("Error showing window: {}", err);
 | 
					        warn!("Error showing window: {}", err);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn close_window(window: &Window) {
 | 
					fn close_window(window: &Window) {
 | 
				
			||||||
@@ -389,3 +417,15 @@ fn close_window(window: &Window) {
 | 
				
			|||||||
        warn!("Error closing window: {}", err);
 | 
					        warn!("Error closing window: {}", err);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn clear_webview_cookies(wv: &webkit2gtk::WebView) {
 | 
				
			||||||
 | 
					    if let Some(context) = wv.context() {
 | 
				
			||||||
 | 
					        if let Some(cookie_manager) = context.cookie_manager() {
 | 
				
			||||||
 | 
					            #[allow(deprecated)]
 | 
				
			||||||
 | 
					            cookie_manager.delete_all_cookies();
 | 
				
			||||||
 | 
					            info!("Cookies cleared");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    warn!("No cookie manager found");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,7 +40,14 @@ async fn saml_login(
 | 
				
			|||||||
    app_handle: AppHandle,
 | 
					    app_handle: AppHandle,
 | 
				
			||||||
) -> tauri::Result<Option<AuthData>> {
 | 
					) -> tauri::Result<Option<AuthData>> {
 | 
				
			||||||
    let ua = "PAN GlobalProtect";
 | 
					    let ua = "PAN GlobalProtect";
 | 
				
			||||||
    auth::saml_login(AuthRequest::new(binding, request), ua, &app_handle).await
 | 
					    let clear_cookies = false;
 | 
				
			||||||
 | 
					    auth::saml_login(
 | 
				
			||||||
 | 
					        AuthRequest::new(binding, request),
 | 
				
			||||||
 | 
					        ua,
 | 
				
			||||||
 | 
					        clear_cookies,
 | 
				
			||||||
 | 
					        &app_handle,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .await
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Serialize)]
 | 
					#[derive(Debug, Clone, Serialize)]
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user