mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-04-02 18:31:50 -04:00
refactor: refine the SAML login workflow
This commit is contained in:
parent
89eb42ceac
commit
75db9e162f
@ -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,20 +401,31 @@ 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) {
|
||||||
if let Err(err) = window.close() {
|
if let Err(err) = window.close() {
|
||||||
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)]
|
||||||
|
Loading…
Reference in New Issue
Block a user