mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-04-02 18:31:50 -04:00
feat: support macos
This commit is contained in:
parent
d199d9e331
commit
81c5a6160c
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -23,8 +23,12 @@
|
|||||||
"gpgui",
|
"gpgui",
|
||||||
"gpservice",
|
"gpservice",
|
||||||
"hidpi",
|
"hidpi",
|
||||||
|
"Ivars",
|
||||||
"jnlp",
|
"jnlp",
|
||||||
"LOGNAME",
|
"LOGNAME",
|
||||||
|
"NSHTTPURL",
|
||||||
|
"NSURL",
|
||||||
|
"objc",
|
||||||
"oneshot",
|
"oneshot",
|
||||||
"openconnect",
|
"openconnect",
|
||||||
"pkcs",
|
"pkcs",
|
||||||
@ -55,6 +59,7 @@
|
|||||||
"Vite",
|
"Vite",
|
||||||
"vpnc",
|
"vpnc",
|
||||||
"vpninfo",
|
"vpninfo",
|
||||||
|
"webbrowser",
|
||||||
"wmctrl",
|
"wmctrl",
|
||||||
"XAUTHORITY",
|
"XAUTHORITY",
|
||||||
"yuezk"
|
"yuezk"
|
||||||
|
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -178,9 +178,13 @@ name = "auth"
|
|||||||
version = "2.4.0"
|
version = "2.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"block2",
|
||||||
"gpapi",
|
"gpapi",
|
||||||
"html-escape",
|
"html-escape",
|
||||||
"log",
|
"log",
|
||||||
|
"objc2",
|
||||||
|
"objc2-foundation",
|
||||||
|
"objc2-web-kit",
|
||||||
"open",
|
"open",
|
||||||
"regex",
|
"regex",
|
||||||
"tao 0.31.0",
|
"tao 0.31.0",
|
||||||
|
@ -32,6 +32,12 @@ html-escape = { version = "0.2.13", optional = true }
|
|||||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||||
webkit2gtk = { version = "2", optional = true }
|
webkit2gtk = { version = "2", optional = true }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
block2 = { version = "0.5", optional = true }
|
||||||
|
objc2 = { version = "0.5", optional = true }
|
||||||
|
objc2-foundation = { version = "0.2", optional = true }
|
||||||
|
objc2-web-kit = { version = "0.2", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
browser-auth = [
|
browser-auth = [
|
||||||
"dep:webbrowser",
|
"dep:webbrowser",
|
||||||
@ -47,4 +53,8 @@ webview-auth = [
|
|||||||
"dep:tokio-util",
|
"dep:tokio-util",
|
||||||
"dep:html-escape",
|
"dep:html-escape",
|
||||||
"dep:webkit2gtk",
|
"dep:webkit2gtk",
|
||||||
|
"dep:block2",
|
||||||
|
"dep:objc2",
|
||||||
|
"dep:objc2-foundation",
|
||||||
|
"dep:objc2-web-kit",
|
||||||
]
|
]
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
mod auth_messenger;
|
mod auth_messenger;
|
||||||
mod auth_response;
|
mod auth_response;
|
||||||
|
mod webview_auth;
|
||||||
|
|
||||||
#[cfg_attr(not(target_os = "macos"), path = "webview/unix.rs")]
|
#[cfg_attr(not(target_os = "macos"), path = "webview/unix.rs")]
|
||||||
|
#[cfg_attr(target_os = "macos", path = "webview/macos.rs")]
|
||||||
mod platform_impl;
|
mod platform_impl;
|
||||||
mod webview_auth;
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
mod navigation_delegate;
|
||||||
|
|
||||||
pub use webview_auth::WebviewAuthenticator;
|
pub use webview_auth::WebviewAuthenticator;
|
||||||
pub use webview_auth::WebviewAuthenticatorBuilder;
|
pub use webview_auth::WebviewAuthenticatorBuilder;
|
||||||
|
@ -71,8 +71,9 @@ where
|
|||||||
{
|
{
|
||||||
auth_response.get_body(|body| match body {
|
auth_response.get_body(|body| match body {
|
||||||
Ok(body) => {
|
Ok(body) => {
|
||||||
let html = String::from_utf8_lossy(&body);
|
if let Some(html) = body {
|
||||||
cb(read_from_html(&html))
|
cb(read_from_html(&html))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
info!("Failed to read body: {}", err);
|
info!("Failed to read body: {}", err);
|
||||||
|
90
crates/auth/src/webview/macos.rs
Normal file
90
crates/auth/src/webview/macos.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
||||||
|
use block2::RcBlock;
|
||||||
|
use objc2::{
|
||||||
|
rc::Retained,
|
||||||
|
runtime::{AnyObject, ProtocolObject},
|
||||||
|
};
|
||||||
|
use objc2_foundation::{NSError, NSHTTPURLResponse, NSString};
|
||||||
|
use wry::WebViewExtMacOS;
|
||||||
|
|
||||||
|
use super::{auth_messenger::AuthError, navigation_delegate::NavigationDelegate};
|
||||||
|
|
||||||
|
pub struct AuthResponse {
|
||||||
|
response: Option<Retained<NSHTTPURLResponse>>,
|
||||||
|
body: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthResponse {
|
||||||
|
pub fn url(&self) -> Option<String> {
|
||||||
|
let response = self.response.as_ref()?;
|
||||||
|
let url = unsafe { response.URL().and_then(|url| url.absoluteString()) };
|
||||||
|
|
||||||
|
url.map(|u| u.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_header(&self, key: &str) -> Option<String> {
|
||||||
|
let response = self.response.as_ref()?;
|
||||||
|
let value = unsafe { response.valueForHTTPHeaderField(&NSString::from_str(key)) };
|
||||||
|
|
||||||
|
value.map(|v| v.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_body<F>(&self, cb: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(anyhow::Result<Option<Cow<'_, str>>>) + 'static,
|
||||||
|
{
|
||||||
|
if let Some(body) = self.body.as_deref() {
|
||||||
|
cb(Ok(Some(Cow::Borrowed(body))));
|
||||||
|
} else {
|
||||||
|
cb(Ok(None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect_webview_response<F>(wv: &wry::WebView, cb: F)
|
||||||
|
where
|
||||||
|
F: Fn(anyhow::Result<AuthResponse, AuthError>) + 'static,
|
||||||
|
{
|
||||||
|
let wv = wv.webview();
|
||||||
|
let wv_clone = Retained::clone(&wv);
|
||||||
|
|
||||||
|
let callback = Arc::new(cb);
|
||||||
|
|
||||||
|
let delegate = NavigationDelegate::new(move |response| {
|
||||||
|
let callback_clone = Arc::clone(&callback);
|
||||||
|
|
||||||
|
if let Some(response) = response {
|
||||||
|
callback_clone(Ok(AuthResponse {
|
||||||
|
response: Some(response),
|
||||||
|
body: None,
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let callback_clone = Arc::clone(&callback);
|
||||||
|
let js_callback = RcBlock::new(move |body: *mut AnyObject, _err: *mut NSError| {
|
||||||
|
let body = body as *mut NSString;
|
||||||
|
let body = body.as_ref().unwrap();
|
||||||
|
|
||||||
|
callback_clone(Ok(AuthResponse {
|
||||||
|
response: None,
|
||||||
|
body: Some(body.to_string()),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
wv_clone.evaluateJavaScript_completionHandler(
|
||||||
|
&NSString::from_str("document.documentElement.outerHTML"),
|
||||||
|
Some(&js_callback),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let proto_delegate = ProtocolObject::from_ref(delegate.as_ref());
|
||||||
|
unsafe {
|
||||||
|
wv.setNavigationDelegate(Some(proto_delegate));
|
||||||
|
// The UI will freeze if we don't call this method
|
||||||
|
let _ = wv.navigationDelegate();
|
||||||
|
};
|
||||||
|
}
|
75
crates/auth/src/webview/navigation_delegate.rs
Normal file
75
crates/auth/src/webview/navigation_delegate.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use objc2::{
|
||||||
|
declare_class, msg_send_id,
|
||||||
|
mutability::MainThreadOnly,
|
||||||
|
rc::Retained,
|
||||||
|
runtime::{NSObject, NSObjectProtocol},
|
||||||
|
ClassType, DeclaredClass,
|
||||||
|
};
|
||||||
|
use objc2_foundation::{MainThreadMarker, NSHTTPURLResponse, NSURLResponse};
|
||||||
|
use objc2_web_kit::{WKNavigation, WKNavigationDelegate, WKNavigationResponse, WKNavigationResponsePolicy, WKWebView};
|
||||||
|
|
||||||
|
pub struct NavigationDelegateIvars {
|
||||||
|
pub on_response: Box<dyn Fn(Option<Retained<NSHTTPURLResponse>>) + 'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_class!(
|
||||||
|
pub struct NavigationDelegate;
|
||||||
|
|
||||||
|
unsafe impl ClassType for NavigationDelegate {
|
||||||
|
type Super = NSObject;
|
||||||
|
type Mutability = MainThreadOnly;
|
||||||
|
const NAME: &'static str = "NavigationDelegate";
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeclaredClass for NavigationDelegate {
|
||||||
|
type Ivars = NavigationDelegateIvars;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl NSObjectProtocol for NavigationDelegate {}
|
||||||
|
|
||||||
|
unsafe impl WKNavigationDelegate for NavigationDelegate {
|
||||||
|
#[method(webView:decidePolicyForNavigationResponse:decisionHandler:)]
|
||||||
|
fn navigation_policy_response(
|
||||||
|
&self,
|
||||||
|
_wv: &WKWebView,
|
||||||
|
response: &WKNavigationResponse,
|
||||||
|
handler: &block2::Block<dyn Fn(WKNavigationResponsePolicy)>,
|
||||||
|
) {
|
||||||
|
println!("navigation_policy_response start");
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if response.isForMainFrame() {
|
||||||
|
let url_response: Retained<NSURLResponse> = response.response();
|
||||||
|
let http_response = Retained::cast::<NSHTTPURLResponse>(url_response);
|
||||||
|
(self.ivars().on_response)(Some(http_response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("navigation_policy_response end");
|
||||||
|
(*handler).call((WKNavigationResponsePolicy::Allow,));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[method(webView:didFinishNavigation:)]
|
||||||
|
fn webview_did_finish_navigation(
|
||||||
|
&self,
|
||||||
|
_wv: &WKWebView,
|
||||||
|
_navigation: Option<&WKNavigation>,
|
||||||
|
) {
|
||||||
|
(self.ivars().on_response)(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
impl NavigationDelegate {
|
||||||
|
pub fn new<F>(on_response: F) -> Retained<Self>
|
||||||
|
where
|
||||||
|
F: Fn(Option<Retained<NSHTTPURLResponse>>) + 'static,
|
||||||
|
{
|
||||||
|
let mtm = MainThreadMarker::new().expect("Not on main thread");
|
||||||
|
let delegate = mtm.alloc::<NavigationDelegate>().set_ivars(NavigationDelegateIvars {
|
||||||
|
on_response: Box::new(on_response),
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe { msg_send_id![super(delegate), init] }
|
||||||
|
}
|
||||||
|
}
|
@ -76,12 +76,15 @@ impl<'a> WebviewAuthenticatorBuilder<'a> {
|
|||||||
event_loop: &'a EventLoop<anyhow::Result<SamlAuthData>>,
|
event_loop: &'a EventLoop<anyhow::Result<SamlAuthData>>,
|
||||||
) -> anyhow::Result<WebviewAuthenticator<'a>> {
|
) -> anyhow::Result<WebviewAuthenticator<'a>> {
|
||||||
let window = WindowBuilder::new()
|
let window = WindowBuilder::new()
|
||||||
.with_title("GlobalProtect Authentication")
|
.with_title("GlobalProtect Login")
|
||||||
.with_focused(true)
|
.with_focused(true)
|
||||||
.build(event_loop)?;
|
.build(event_loop)?;
|
||||||
|
|
||||||
let builder = WebViewBuilder::new();
|
let builder = WebViewBuilder::new();
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let webview = builder.build(&window)?;
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
let webview = {
|
let webview = {
|
||||||
use tao::platform::unix::WindowExtUnix;
|
use tao::platform::unix::WindowExtUnix;
|
||||||
|
Loading…
Reference in New Issue
Block a user