mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-04-02 18:31:50 -04:00
refactor: get portal config
This commit is contained in:
parent
c74ce52c2d
commit
d975f981cc
@ -131,7 +131,7 @@ fn setup_webview(
|
||||
) -> tauri::Result<()> {
|
||||
window.with_webview(move |wv| {
|
||||
let wv = wv.inner();
|
||||
let event_tx = event_tx.clone();
|
||||
let event_tx_clone = event_tx.clone();
|
||||
|
||||
if clear_cookies {
|
||||
clear_webview_cookies(&wv);
|
||||
@ -146,25 +146,22 @@ fn setup_webview(
|
||||
// Empty URI indicates that an error occurred
|
||||
if uri.is_empty() {
|
||||
warn!("Empty URI loaded");
|
||||
if let Err(err) = event_tx.blocking_send(AuthEvent::Error(AuthError::TokenInvalid))
|
||||
{
|
||||
warn!("Error sending event: {}", err);
|
||||
}
|
||||
send_auth_error(&event_tx_clone, AuthError::TokenInvalid);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO, redact URI
|
||||
debug!("Loaded URI: {}", uri);
|
||||
|
||||
if let Some(main_res) = wv.main_resource() {
|
||||
parse_auth_data(&main_res, event_tx.clone());
|
||||
parse_auth_data(&main_res, event_tx_clone.clone());
|
||||
} else {
|
||||
warn!("No main_resource");
|
||||
}
|
||||
});
|
||||
|
||||
wv.connect_load_failed(|_wv, event, err_msg, err| {
|
||||
warn!("Load failed: {:?}, {}, {:?}", event, err_msg, err);
|
||||
wv.connect_load_failed(move |_wv, event, _uri, err| {
|
||||
warn!("Load failed: {:?}, {:?}", event, err);
|
||||
send_auth_error(&event_tx, AuthError::TokenInvalid);
|
||||
false
|
||||
});
|
||||
})
|
||||
@ -175,9 +172,7 @@ fn setup_window(window: &Window, event_tx: mpsc::Sender<AuthEvent>) -> EventHand
|
||||
window.on_window_event(move |event| {
|
||||
if let CloseRequested { api, .. } = event {
|
||||
api.prevent_close();
|
||||
if let Err(err) = event_tx_clone.blocking_send(AuthEvent::Cancel) {
|
||||
warn!("Error sending event: {}", err)
|
||||
}
|
||||
send_auth_event(&event_tx_clone, AuthEvent::Cancel);
|
||||
}
|
||||
});
|
||||
|
||||
@ -335,9 +330,7 @@ fn parse_auth_data(main_res: &WebResource, event_tx: mpsc::Sender<AuthEvent>) {
|
||||
}
|
||||
Err(err) => {
|
||||
debug!("Error reading auth data from HTML: {:?}", err);
|
||||
if let Err(err) = event_tx.blocking_send(AuthEvent::Error(err)) {
|
||||
warn!("Error sending event: {}", err)
|
||||
}
|
||||
send_auth_error(&event_tx, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -395,7 +388,15 @@ fn parse_xml_tag(html: &str, tag: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
fn send_auth_data(event_tx: &mpsc::Sender<AuthEvent>, auth_data: AuthData) {
|
||||
if let Err(err) = event_tx.blocking_send(AuthEvent::Success(auth_data)) {
|
||||
send_auth_event(event_tx, AuthEvent::Success(auth_data));
|
||||
}
|
||||
|
||||
fn send_auth_error(event_tx: &mpsc::Sender<AuthEvent>, err: AuthError) {
|
||||
send_auth_event(event_tx, AuthEvent::Error(err));
|
||||
}
|
||||
|
||||
fn send_auth_event(event_tx: &mpsc::Sender<AuthEvent>, auth_event: AuthEvent) {
|
||||
if let Err(err) = event_tx.blocking_send(auth_event) {
|
||||
warn!("Error sending event: {}", err)
|
||||
}
|
||||
}
|
||||
|
@ -37,16 +37,16 @@ export default function App() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
authService.onAuthSuccess((data) => {});
|
||||
authService.onAuthError(async () => {
|
||||
const preloginResponse = await portalService.prelogin(portalAddress);
|
||||
// Retry SAML login when auth error occurs
|
||||
authService.emitAuthRequest({
|
||||
samlBinding: preloginResponse.samlAuthMethod!,
|
||||
samlRequest: preloginResponse.samlAuthRequest!,
|
||||
});
|
||||
if (portalService.isSamlAuth(preloginResponse)) {
|
||||
// Retry SAML login when auth error occurs
|
||||
await authService.emitAuthRequest({
|
||||
samlBinding: preloginResponse.samlAuthMethod,
|
||||
samlRequest: preloginResponse.samlAuthRequest,
|
||||
});
|
||||
}
|
||||
});
|
||||
authService.onAuthCancel(() => {});
|
||||
}, [portalAddress]);
|
||||
|
||||
function closeNotification() {
|
||||
@ -70,18 +70,33 @@ export default function App() {
|
||||
async function handleConnect(e: FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
|
||||
setProcessing(true);
|
||||
// setProcessing(true);
|
||||
setStatus("processing");
|
||||
|
||||
try {
|
||||
const response = await portalService.prelogin(portalAddress);
|
||||
|
||||
if (portalService.isSamlAuth(response)) {
|
||||
const { samlAuthMethod, samlAuthRequest } = response;
|
||||
setStatus("authenticating");
|
||||
const authData = await authService.samlLogin(
|
||||
samlAuthMethod,
|
||||
samlAuthRequest
|
||||
);
|
||||
console.log("authData", authData);
|
||||
if (!authData) {
|
||||
throw new Error("User cancelled");
|
||||
}
|
||||
|
||||
const portalConfigResponse = await portalService.fetchConfig(
|
||||
portalAddress,
|
||||
{
|
||||
user: authData.username,
|
||||
"prelogin-cookie": authData.prelogin_cookie,
|
||||
"portal-userauthcookie": authData.portal_userauthcookie,
|
||||
}
|
||||
);
|
||||
|
||||
console.log("portalConfigResponse", portalConfigResponse);
|
||||
} else if (portalService.isPasswordAuth(response)) {
|
||||
setPasswordAuthOpen(true);
|
||||
setPasswordAuth({
|
||||
@ -94,17 +109,17 @@ export default function App() {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setProcessing(false);
|
||||
setStatus("disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
// TODO cancel the request first
|
||||
setProcessing(false);
|
||||
setStatus("disconnected");
|
||||
}
|
||||
|
||||
async function handleDisconnect() {
|
||||
setProcessing(true);
|
||||
setStatus("processing");
|
||||
|
||||
try {
|
||||
await vpnService.disconnect();
|
||||
@ -116,18 +131,20 @@ export default function App() {
|
||||
message: err.message,
|
||||
});
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
setStatus("disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePasswordAuth({ username, password }: Credentials) {
|
||||
async function handlePasswordAuth({
|
||||
username: user,
|
||||
password: passwd,
|
||||
}: Credentials) {
|
||||
try {
|
||||
setPasswordAuthenticating(true);
|
||||
const portalConfigResponse = await portalService.fetchConfig({
|
||||
portal: portalAddress,
|
||||
username,
|
||||
password,
|
||||
});
|
||||
const portalConfigResponse = await portalService.fetchConfig(
|
||||
portalAddress,
|
||||
{ user, passwd }
|
||||
);
|
||||
|
||||
const { gateways, preferredGateway, userAuthCookie } =
|
||||
portalConfigResponse;
|
||||
@ -139,13 +156,13 @@ export default function App() {
|
||||
|
||||
const token = await gatewayService.login({
|
||||
gateway: preferredGateway,
|
||||
username,
|
||||
password,
|
||||
user,
|
||||
passwd,
|
||||
userAuthCookie,
|
||||
});
|
||||
|
||||
await vpnService.connect(preferredGateway.address!, token);
|
||||
setProcessing(false);
|
||||
// setProcessing(false);
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
setNotification({
|
||||
@ -162,7 +179,8 @@ export default function App() {
|
||||
function cancelPasswordAuth() {
|
||||
setPasswordAuthenticating(false);
|
||||
setPasswordAuthOpen(false);
|
||||
setProcessing(false);
|
||||
// setProcessing(false);
|
||||
setStatus("disconnected");
|
||||
}
|
||||
return (
|
||||
<Box padding={2} paddingTop={3}>
|
||||
@ -193,10 +211,11 @@ export default function App() {
|
||||
Connect
|
||||
</Button>
|
||||
)}
|
||||
{status === "connecting" && (
|
||||
{["processing", "authenticating", "connecting"].includes(status) && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
disabled={status === "authenticating"}
|
||||
onClick={handleCancel}
|
||||
sx={{ textTransform: "none" }}
|
||||
>
|
||||
|
@ -11,6 +11,7 @@ import { BeatLoader } from "react-spinners";
|
||||
|
||||
export type Status =
|
||||
| "processing"
|
||||
| "authenticating"
|
||||
| "disconnected"
|
||||
| "connecting"
|
||||
| "connected"
|
||||
@ -18,6 +19,7 @@ export type Status =
|
||||
|
||||
export const statusTextMap: Record<Status, string> = {
|
||||
processing: "Processing...",
|
||||
authenticating: "Authenticating...",
|
||||
connected: "Connected",
|
||||
disconnected: "Not Connected",
|
||||
connecting: "Connecting...",
|
||||
@ -32,13 +34,14 @@ export default function ConnectionStatus(
|
||||
const { palette } = theme;
|
||||
const colorsMap: Record<Status, string> = {
|
||||
processing: palette.info.main,
|
||||
authenticating: palette.info.main,
|
||||
connected: palette.success.main,
|
||||
disconnected: palette.action.disabled,
|
||||
connecting: palette.info.main,
|
||||
disconnecting: palette.info.main,
|
||||
};
|
||||
|
||||
const pending = ["processing", "connecting", "disconnecting"].includes(status);
|
||||
const pending = ["processing", "authenticating", "connecting", "disconnecting"].includes(status);
|
||||
const connected = status === "connected";
|
||||
const disconnected = status === "disconnected";
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Event, emit, listen } from "@tauri-apps/api/event";
|
||||
import { emit, listen } from "@tauri-apps/api/event";
|
||||
import invokeCommand from "../utils/invokeCommand";
|
||||
|
||||
type AuthData = {
|
||||
@ -8,51 +8,35 @@ type AuthData = {
|
||||
};
|
||||
|
||||
class AuthService {
|
||||
private authSuccessCallback: ((data: AuthData) => void) | undefined;
|
||||
private authErrorCallback: (() => void) | undefined;
|
||||
private authCancelCallback: (() => void) | undefined;
|
||||
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
private async init() {
|
||||
await listen("auth-success", (event: Event<AuthData>) => {
|
||||
this.authSuccessCallback?.(event.payload);
|
||||
});
|
||||
await listen("auth-error", (event) => {
|
||||
await listen("auth-error", () => {
|
||||
this.authErrorCallback?.();
|
||||
});
|
||||
await listen("auth-cancel", (event) => {
|
||||
this.authCancelCallback?.();
|
||||
});
|
||||
}
|
||||
|
||||
onAuthSuccess(callback: (data: AuthData) => void) {
|
||||
this.authSuccessCallback = callback;
|
||||
}
|
||||
|
||||
onAuthError(callback: () => void) {
|
||||
this.authErrorCallback = callback;
|
||||
}
|
||||
|
||||
onAuthCancel(callback: () => void) {
|
||||
this.authCancelCallback = callback;
|
||||
}
|
||||
|
||||
// binding: "POST" | "REDIRECT"
|
||||
async samlLogin(binding: string, request: string) {
|
||||
return invokeCommand<AuthData>("saml_login", { binding, request });
|
||||
}
|
||||
|
||||
emitAuthRequest({
|
||||
async emitAuthRequest({
|
||||
samlBinding,
|
||||
samlRequest,
|
||||
}: {
|
||||
samlBinding: string;
|
||||
samlRequest: string;
|
||||
}) {
|
||||
emit("auth-request", { samlBinding, samlRequest });
|
||||
await emit("auth-request", { samlBinding, samlRequest });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,14 +5,14 @@ import { Gateway } from "./types";
|
||||
|
||||
type LoginParams = {
|
||||
gateway: Gateway;
|
||||
username: string;
|
||||
password: string;
|
||||
user: string;
|
||||
passwd: string;
|
||||
userAuthCookie: Maybe<string>;
|
||||
};
|
||||
|
||||
class GatewayService {
|
||||
async login(params: LoginParams) {
|
||||
const { gateway, username, password, userAuthCookie } = params;
|
||||
const { gateway, user, passwd, userAuthCookie } = params;
|
||||
if (!gateway.address) {
|
||||
throw new Error("Gateway address is required");
|
||||
}
|
||||
@ -37,8 +37,8 @@ class GatewayService {
|
||||
clientos: "Linux",
|
||||
"os-version": "Linux",
|
||||
server: gateway.address,
|
||||
user: username,
|
||||
passwd: password,
|
||||
user,
|
||||
passwd,
|
||||
"portal-userauthcookie": userAuthCookie ?? "",
|
||||
"portal-prelogonuserauthcookie": "",
|
||||
"prelogin-cookie": "",
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Body, ResponseType, fetch } from "@tauri-apps/api/http";
|
||||
import { Maybe, MaybeProperties } from "../types";
|
||||
import { parseXml } from "../utils/parseXml";
|
||||
import authService from "./authService";
|
||||
import { Gateway } from "./types";
|
||||
|
||||
type SamlPreloginResponse = {
|
||||
@ -30,6 +29,19 @@ type ConfigResponse = {
|
||||
gateways: Gateway[];
|
||||
};
|
||||
|
||||
// user: username,
|
||||
// passwd: password,
|
||||
// "prelogin-cookie": "",
|
||||
// "portal-userauthcookie": "",
|
||||
// "portal-prelogonuserauthcookie": "",
|
||||
type PortalConfigParams = {
|
||||
user: string;
|
||||
passwd?: string | null;
|
||||
"prelogin-cookie"?: string | null;
|
||||
"portal-userauthcookie"?: string | null;
|
||||
"portal-prelogonuserauthcookie"?: string | null;
|
||||
};
|
||||
|
||||
class PortalService {
|
||||
async prelogin(portal: string) {
|
||||
const preloginUrl = `https://${portal}/global-protect/prelogin.esp`;
|
||||
@ -84,40 +96,42 @@ class PortalService {
|
||||
return false;
|
||||
}
|
||||
|
||||
async fetchConfig({
|
||||
portal,
|
||||
username,
|
||||
password,
|
||||
}: {
|
||||
portal: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}) {
|
||||
async fetchConfig(portal: string, params: PortalConfigParams) {
|
||||
const {
|
||||
user,
|
||||
passwd,
|
||||
"prelogin-cookie": preloginCookie,
|
||||
"portal-userauthcookie": portalUserAuthCookie,
|
||||
"portal-prelogonuserauthcookie": portalPrelogonUserAuthCookie,
|
||||
} = params;
|
||||
|
||||
const configUrl = `https://${portal}/global-protect/getconfig.esp`;
|
||||
const body = Body.form({
|
||||
prot: "https:",
|
||||
inputStr: "",
|
||||
jnlpReady: "jnlpReady",
|
||||
computer: "Linux", // TODO
|
||||
clientos: "Linux",
|
||||
ok: "Login",
|
||||
direct: "yes",
|
||||
clientVer: "4100",
|
||||
"os-version": "Linux",
|
||||
"ipv6-support": "yes",
|
||||
server: portal,
|
||||
user,
|
||||
passwd: passwd || "",
|
||||
"prelogin-cookie": preloginCookie || "",
|
||||
"portal-userauthcookie": portalUserAuthCookie || "",
|
||||
"portal-prelogonuserauthcookie": portalPrelogonUserAuthCookie || "",
|
||||
});
|
||||
|
||||
const response = await fetch<string>(configUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"User-Agent": "PAN GlobalProtect",
|
||||
},
|
||||
responseType: ResponseType.Text,
|
||||
body: Body.form({
|
||||
prot: "https:",
|
||||
inputStr: "",
|
||||
jnlpReady: "jnlpReady",
|
||||
computer: "Linux", // TODO
|
||||
clientos: "Linux",
|
||||
ok: "Login",
|
||||
direct: "yes",
|
||||
clientVer: "4100",
|
||||
"os-version": "Linux",
|
||||
"ipv6-support": "yes",
|
||||
server: portal,
|
||||
user: username,
|
||||
passwd: password,
|
||||
"portal-userauthcookie": "",
|
||||
"portal-prelogonuserauthcookie": "",
|
||||
"prelogin-cookie": "",
|
||||
}),
|
||||
body,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
Loading…
Reference in New Issue
Block a user