mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-05-20 07:26:58 -04:00
feat: add the settings window
This commit is contained in:
@@ -181,7 +181,7 @@ fn setup_window(window: &Window, event_tx: mpsc::Sender<AuthEvent>) -> EventHand
|
||||
window.listen_global(AUTH_REQUEST_EVENT, move |event| {
|
||||
if let Ok(payload) = TryInto::<AuthRequest>::try_into(event.payload()) {
|
||||
let event_tx = event_tx.clone();
|
||||
send_auth_event(event_tx.clone(), AuthEvent::Request(payload));
|
||||
send_auth_event(event_tx, AuthEvent::Request(payload));
|
||||
} else {
|
||||
warn!("Invalid auth request payload");
|
||||
}
|
||||
@@ -198,7 +198,7 @@ async fn process(
|
||||
process_request(window, auth_request)?;
|
||||
|
||||
let handle = tokio::spawn(show_window_after_timeout(window.clone()));
|
||||
let auth_data = monitor_events(&window, event_rx).await;
|
||||
let auth_data = monitor_events(window, event_rx).await;
|
||||
|
||||
if !handle.is_finished() {
|
||||
handle.abort();
|
||||
@@ -254,12 +254,12 @@ async fn monitor_auth_event(window: &Window, mut event_rx: mpsc::Receiver<AuthEv
|
||||
if let Some(auth_event) = event_rx.recv().await {
|
||||
match auth_event {
|
||||
AuthEvent::Request(auth_request) => {
|
||||
attempt_times = attempt_times + 1;
|
||||
attempt_times += 1;
|
||||
info!(
|
||||
"Got auth request from auth-request event, attempt #{}",
|
||||
attempt_times
|
||||
);
|
||||
if let Err(err) = process_request(&window, auth_request) {
|
||||
if let Err(err) = process_request(window, auth_request) {
|
||||
warn!("Error processing auth request: {}", err);
|
||||
}
|
||||
}
|
||||
@@ -316,7 +316,7 @@ async fn monitor_window_close_event(window: &Window) {
|
||||
if matches!(event, WindowEvent::CloseRequested { .. }) {
|
||||
if let Ok(mut close_tx_locked) = close_tx.try_lock() {
|
||||
if let Some(close_tx) = close_tx_locked.take() {
|
||||
if let Err(_) = close_tx.send(()) {
|
||||
if close_tx.send(()).is_err() {
|
||||
println!("Error sending close event");
|
||||
}
|
||||
}
|
||||
@@ -352,14 +352,23 @@ async fn handle_token_not_found(window: Window, cancel_timeout_rx: Arc<Mutex<mps
|
||||
/// and send it to the event channel
|
||||
fn parse_auth_data(main_res: &WebResource, auth_event_tx: mpsc::Sender<AuthEvent>) {
|
||||
if let Some(response) = main_res.response() {
|
||||
if let Some(auth_data) = read_auth_data_from_response(&response) {
|
||||
debug!("Got auth data from HTTP headers: {:?}", auth_data);
|
||||
send_auth_data(auth_event_tx, auth_data);
|
||||
return;
|
||||
match read_auth_data_from_response(&response) {
|
||||
Ok(auth_data) => {
|
||||
debug!("Got auth data from HTTP headers: {:?}", auth_data);
|
||||
send_auth_data(auth_event_tx, auth_data);
|
||||
return;
|
||||
}
|
||||
Err(AuthError::TokenInvalid) => {
|
||||
debug!("Received invalid token from HTTP headers");
|
||||
send_auth_error(auth_event_tx, AuthError::TokenInvalid);
|
||||
return;
|
||||
}
|
||||
Err(AuthError::TokenNotFound) => {
|
||||
debug!("Token not found in HTTP headers, trying to read from HTML");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let auth_event_tx = auth_event_tx.clone();
|
||||
main_res.data(Cancellable::NONE, move |data| {
|
||||
if let Ok(data) = data {
|
||||
let html = String::from_utf8_lossy(&data);
|
||||
@@ -378,20 +387,27 @@ fn parse_auth_data(main_res: &WebResource, auth_event_tx: mpsc::Sender<AuthEvent
|
||||
}
|
||||
|
||||
/// Read the authentication data from the response headers
|
||||
fn read_auth_data_from_response(response: &webkit2gtk::URIResponse) -> Option<AuthData> {
|
||||
response.http_headers().and_then(|mut headers| {
|
||||
let auth_data = AuthData::new(
|
||||
headers.get("saml-username").map(GString::into),
|
||||
headers.get("prelogin-cookie").map(GString::into),
|
||||
headers.get("portal-userauthcookie").map(GString::into),
|
||||
);
|
||||
fn read_auth_data_from_response(response: &webkit2gtk::URIResponse) -> Result<AuthData, AuthError> {
|
||||
response
|
||||
.http_headers()
|
||||
.map_or(Err(AuthError::TokenNotFound), |mut headers| {
|
||||
let saml_status: Option<String> = headers.get("saml-auth-status").map(GString::into);
|
||||
if saml_status == Some("-1".to_string()) {
|
||||
return Err(AuthError::TokenInvalid);
|
||||
}
|
||||
|
||||
if auth_data.check() {
|
||||
Some(auth_data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
let auth_data = AuthData::new(
|
||||
headers.get("saml-username").map(GString::into),
|
||||
headers.get("prelogin-cookie").map(GString::into),
|
||||
headers.get("portal-userauthcookie").map(GString::into),
|
||||
);
|
||||
|
||||
if auth_data.check() {
|
||||
Ok(auth_data)
|
||||
} else {
|
||||
Err(AuthError::TokenNotFound)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Read the authentication data from the HTML content
|
||||
@@ -441,7 +457,7 @@ fn send_auth_error(auth_event_tx: mpsc::Sender<AuthEvent>, err: AuthError) {
|
||||
}
|
||||
|
||||
fn send_auth_event(auth_event_tx: mpsc::Sender<AuthEvent>, auth_event: AuthEvent) {
|
||||
let _ = tauri::async_runtime::spawn(async move {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
if let Err(err) = auth_event_tx.send(auth_event).await {
|
||||
warn!("Error sending event: {}", err);
|
||||
}
|
||||
|
@@ -1,7 +1,12 @@
|
||||
use crate::auth::{self, AuthData, AuthRequest, SamlBinding, SamlLoginParams};
|
||||
use crate::{
|
||||
auth::{self, AuthData, AuthRequest, SamlBinding, SamlLoginParams},
|
||||
utils::get_openssl_conf,
|
||||
utils::get_openssl_conf_path,
|
||||
};
|
||||
use gpcommon::{Client, ServerApiError, VpnStatus};
|
||||
use std::sync::Arc;
|
||||
use tauri::{AppHandle, State};
|
||||
use tokio::fs;
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn service_online<'a>(client: State<'a, Arc<Client>>) -> Result<bool, ()> {
|
||||
@@ -47,3 +52,22 @@ pub(crate) async fn saml_login(
|
||||
};
|
||||
auth::saml_login(params).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn os_version() -> String {
|
||||
whoami::distro()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn openssl_config() -> String {
|
||||
get_openssl_conf()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn update_openssl_config(app_handle: AppHandle) -> tauri::Result<()> {
|
||||
let openssl_conf = get_openssl_conf();
|
||||
let openssl_conf_path = get_openssl_conf_path(&app_handle);
|
||||
|
||||
fs::write(openssl_conf_path, openssl_conf).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -3,13 +3,15 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use crate::utils::get_openssl_conf_path;
|
||||
use env_logger::Env;
|
||||
use gpcommon::{Client, ClientStatus, VpnStatus};
|
||||
use log::warn;
|
||||
use log::{info, warn};
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use tauri::Manager;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use tauri::{Manager, Wry};
|
||||
use tauri_plugin_log::LogTarget;
|
||||
use tauri_plugin_store::{with_store, StoreCollection};
|
||||
|
||||
mod auth;
|
||||
mod commands;
|
||||
@@ -25,8 +27,24 @@ fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client_clone = client.clone();
|
||||
let app_handle = app.handle();
|
||||
|
||||
let stores = app.state::<StoreCollection<Wry>>();
|
||||
let path = PathBuf::from(".settings.dat");
|
||||
let _ = with_store(app_handle.clone(), stores, path, |store| {
|
||||
let settings_data = store.get("SETTINGS_DATA");
|
||||
let custom_openssl = settings_data.map_or(false, |data| {
|
||||
data["customOpenSSL"].as_bool().unwrap_or(false)
|
||||
});
|
||||
|
||||
if custom_openssl {
|
||||
info!("Using custom OpenSSL config");
|
||||
let openssl_conf = get_openssl_conf_path(&app_handle).into_os_string();
|
||||
std::env::set_var("OPENSSL_CONF", openssl_conf);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let _ = client_clone.subscribe_status(move |client_status| match client_status {
|
||||
client_clone.subscribe_status(move |client_status| match client_status {
|
||||
ClientStatus::Vpn(vpn_status) => {
|
||||
let payload = VpnStatusPayload { status: vpn_status };
|
||||
if let Err(err) = app_handle.emit_all("vpn-status-received", payload) {
|
||||
@@ -45,15 +63,12 @@ fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
app.manage(client);
|
||||
|
||||
match std::env::var("XDG_CURRENT_DESKTOP") {
|
||||
Ok(desktop) => {
|
||||
if desktop == "KDE" {
|
||||
if let Some(main_window) = app.get_window("main") {
|
||||
let _ = main_window.set_decorations(false);
|
||||
}
|
||||
if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") {
|
||||
if desktop == "KDE" {
|
||||
if let Some(main_window) = app.get_window("main") {
|
||||
let _ = main_window.set_decorations(false);
|
||||
}
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -61,7 +76,6 @@ fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
fn main() {
|
||||
// env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
@@ -73,13 +87,17 @@ fn main() {
|
||||
.with_colors(Default::default())
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_store::Builder::default().build())
|
||||
.setup(setup)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::service_online,
|
||||
commands::vpn_status,
|
||||
commands::vpn_connect,
|
||||
commands::vpn_disconnect,
|
||||
commands::saml_login
|
||||
commands::saml_login,
|
||||
commands::os_version,
|
||||
commands::openssl_config,
|
||||
commands::update_openssl_config,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use log::{info, warn};
|
||||
use std::time::Instant;
|
||||
use tauri::Window;
|
||||
use std::{path::PathBuf, time::Instant};
|
||||
use tauri::{AppHandle, Window};
|
||||
use tokio::sync::oneshot;
|
||||
use url::{form_urlencoded, Url};
|
||||
use webkit2gtk::{
|
||||
@@ -9,7 +9,7 @@ use webkit2gtk::{
|
||||
};
|
||||
|
||||
pub(crate) fn redact_url(url: &str) -> String {
|
||||
if let Ok(mut url) = Url::parse(&url) {
|
||||
if let Ok(mut url) = Url::parse(url) {
|
||||
if let Err(err) = url.set_host(Some("redacted")) {
|
||||
warn!("Error redacting URL: {}", err);
|
||||
}
|
||||
@@ -20,7 +20,7 @@ pub(crate) fn redact_url(url: &str) -> String {
|
||||
let redacted_query = redact_query(url.query().unwrap_or(""));
|
||||
url.set_query(Some(&redacted_query));
|
||||
}
|
||||
return url.to_string();
|
||||
url.to_string()
|
||||
} else {
|
||||
warn!("Error parsing URL: {}", url);
|
||||
url.to_string()
|
||||
@@ -86,3 +86,40 @@ fn send_result(tx: oneshot::Sender<()>) {
|
||||
warn!("Error sending clear cookies result");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_openssl_conf() -> String {
|
||||
// OpenSSL version number format: 0xMNN00PP0L
|
||||
// https://www.openssl.org/docs/man3.0/man3/OPENSSL_VERSION_NUMBER.html
|
||||
let version_3_0_4: i64 = 0x30000040;
|
||||
let openssl_version = openssl::version::number();
|
||||
|
||||
// See: https://stackoverflow.com/questions/75763525/curl-35-error0a000152ssl-routinesunsafe-legacy-renegotiation-disabled
|
||||
let option = if openssl_version >= version_3_0_4 {
|
||||
"UnsafeLegacyServerConnect"
|
||||
} else {
|
||||
"UnsafeLegacyRenegotiation"
|
||||
};
|
||||
|
||||
format!(
|
||||
"openssl_conf = openssl_init
|
||||
|
||||
[openssl_init]
|
||||
ssl_conf = ssl_sect
|
||||
|
||||
[ssl_sect]
|
||||
system_default = system_default_sect
|
||||
|
||||
[system_default_sect]
|
||||
Options = {}",
|
||||
option
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn get_openssl_conf_path(app_handle: &AppHandle) -> PathBuf {
|
||||
let app_dir = app_handle
|
||||
.path_resolver()
|
||||
.app_data_dir()
|
||||
.expect("failed to resolve app dir");
|
||||
|
||||
app_dir.join("openssl.cnf")
|
||||
}
|
||||
|
Reference in New Issue
Block a user