refactor: refine the SAML login workflow

This commit is contained in:
Kevin Yue 2023-05-28 18:56:25 +08:00
parent 89eb42ceac
commit 75db9e162f
2 changed files with 71 additions and 24 deletions

View File

@ -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");
}

View File

@ -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)]