upgrade gpauth

This commit is contained in:
Kevin Yue
2024-12-13 10:58:39 +00:00
parent 32cb582e78
commit 2b3bce442a
42 changed files with 1138 additions and 4504 deletions

View File

@@ -1,5 +1,6 @@
[package]
name = "gpapi"
rust-version.workspace = true
version.workspace = true
edition.workspace = true
license = "MIT"

View File

@@ -1,11 +1,14 @@
use std::borrow::{Borrow, Cow};
use anyhow::bail;
use log::{info, warn};
use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::{error::AuthDataParseError, utils::base64::decode_to_string};
pub type AuthDataParseResult = anyhow::Result<SamlAuthData, AuthDataParseError>;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SamlAuthData {
@@ -33,33 +36,51 @@ impl SamlAuthResult {
}
impl SamlAuthData {
pub fn new(username: String, prelogin_cookie: Option<String>, portal_userauthcookie: Option<String>) -> Self {
Self {
username,
prelogin_cookie,
portal_userauthcookie,
token: None,
pub fn new(
username: Option<String>,
prelogin_cookie: Option<String>,
portal_userauthcookie: Option<String>,
) -> anyhow::Result<Self> {
let username = username.unwrap_or_default();
if username.is_empty() {
bail!("Invalid username: <empty>");
}
let prelogin_cookie = prelogin_cookie.unwrap_or_default();
let portal_userauthcookie = portal_userauthcookie.unwrap_or_default();
if prelogin_cookie.len() <= 5 && portal_userauthcookie.len() <= 5 {
bail!(
"Invalid prelogin-cookie: {}, portal-userauthcookie: {}",
prelogin_cookie,
portal_userauthcookie
);
}
Ok(Self {
username,
prelogin_cookie: Some(prelogin_cookie),
portal_userauthcookie: Some(portal_userauthcookie),
token: None,
})
}
pub fn from_html(html: &str) -> anyhow::Result<SamlAuthData, AuthDataParseError> {
pub fn from_html(html: &str) -> AuthDataParseResult {
match parse_xml_tag(html, "saml-auth-status") {
Some(saml_status) if saml_status == "1" => {
Some(status) if status == "1" => {
let username = parse_xml_tag(html, "saml-username");
let prelogin_cookie = parse_xml_tag(html, "prelogin-cookie");
let portal_userauthcookie = parse_xml_tag(html, "portal-userauthcookie");
if SamlAuthData::check(&username, &prelogin_cookie, &portal_userauthcookie) {
Ok(SamlAuthData::new(
username.unwrap(),
prelogin_cookie,
portal_userauthcookie,
))
} else {
Err(AuthDataParseError::Invalid)
}
SamlAuthData::new(username, prelogin_cookie, portal_userauthcookie).map_err(|e| {
warn!("Failed to parse auth data: {}", e);
AuthDataParseError::Invalid
})
}
Some(status) => {
warn!("Found invalid auth status: {}", status);
Err(AuthDataParseError::Invalid)
}
Some(_) => Err(AuthDataParseError::Invalid),
None => Err(AuthDataParseError::NotFound),
}
}
@@ -105,27 +126,6 @@ impl SamlAuthData {
pub fn token(&self) -> Option<&str> {
self.token.as_deref()
}
pub fn check(
username: &Option<String>,
prelogin_cookie: &Option<String>,
portal_userauthcookie: &Option<String>,
) -> bool {
let username_valid = username.as_ref().is_some_and(|username| !username.is_empty());
let prelogin_cookie_valid = prelogin_cookie.as_ref().is_some_and(|val| val.len() > 5);
let portal_userauthcookie_valid = portal_userauthcookie.as_ref().is_some_and(|val| val.len() > 5);
let is_valid = username_valid && (prelogin_cookie_valid || portal_userauthcookie_valid);
if !is_valid {
warn!(
"Invalid SAML auth data: username: {:?}, prelogin-cookie: {:?}, portal-userauthcookie: {:?}",
username, prelogin_cookie, portal_userauthcookie
);
}
is_valid
}
}
pub fn parse_xml_tag(html: &str, tag: &str) -> Option<String> {

View File

@@ -1 +1,28 @@
use crate::error::PortalError;
pub mod args;
pub trait Args {
fn fix_openssl(&self) -> bool;
fn ignore_tls_errors(&self) -> bool;
}
pub fn handle_error(err: anyhow::Error, args: &impl Args) {
eprintln!("\nError: {}", err);
let Some(err) = err.downcast_ref::<PortalError>() else {
return;
};
if err.is_legacy_openssl_error() && !args.fix_openssl() {
eprintln!("\nRe-run it with the `--fix-openssl` option to work around this issue, e.g.:\n");
let args = std::env::args().collect::<Vec<_>>();
eprintln!("{} --fix-openssl {}\n", args[0], args[1..].join(" "));
}
if err.is_tls_error() && !args.ignore_tls_errors() {
eprintln!("\nRe-run it with the `--ignore-tls-errors` option to ignore the certificate error, e.g.:\n");
let args = std::env::args().collect::<Vec<_>>();
eprintln!("{} --ignore-tls-errors {}\n", args[0], args[1..].join(" "));
}
}

View File

@@ -7,7 +7,19 @@ pub enum PortalError {
#[error("Portal config error: {0}")]
ConfigError(String),
#[error("Network error: {0}")]
NetworkError(String),
NetworkError(#[from] reqwest::Error),
#[error("TLS error")]
TlsError,
}
impl PortalError {
pub fn is_legacy_openssl_error(&self) -> bool {
format!("{:?}", self).contains("unsafe legacy renegotiation")
}
pub fn is_tls_error(&self) -> bool {
matches!(self, PortalError::TlsError) || format!("{:?}", self).contains("certificate verify failed")
}
}
#[derive(Error, Debug)]
@@ -17,3 +29,9 @@ pub enum AuthDataParseError {
#[error("Invalid auth data")]
Invalid,
}
impl AuthDataParseError {
pub fn is_invalid(&self) -> bool {
matches!(self, AuthDataParseError::Invalid)
}
}

View File

@@ -36,7 +36,7 @@ pub async fn gateway_login(gateway: &str, cred: &Credential, gp_params: &GpParam
.form(&params)
.send()
.await
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e.to_string())))?;
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e)))?;
let res = parse_gp_response(res).await.map_err(|err| {
warn!("{err}");

View File

@@ -16,6 +16,7 @@ pub const GP_API_KEY: &[u8; 32] = &[0; 32];
pub const GP_USER_AGENT: &str = "PAN GlobalProtect";
pub const GP_SERVICE_LOCK_FILE: &str = "/var/run/gpservice.lock";
pub const GP_CALLBACK_PORT_FILENAME: &str = "gpcallback.port";
#[cfg(not(debug_assertions))]
pub const GP_CLIENT_BINARY: &str = "/usr/bin/gpclient";

View File

@@ -116,7 +116,7 @@ pub async fn retrieve_config(portal: &str, cred: &Credential, gp_params: &GpPara
.form(&params)
.send()
.await
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e.to_string())))?;
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e)))?;
let res_xml = parse_gp_response(res).await.or_else(|err| {
if err.status == StatusCode::NOT_FOUND {

View File

@@ -116,14 +116,12 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prel
let client = Client::try_from(gp_params)?;
info!("Perform prelogin, user_agent: {}", gp_params.user_agent());
let res = client
.post(&prelogin_url)
.form(&params)
.send()
.await
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e.to_string())))?;
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e)))?;
let res_xml = parse_gp_response(res).await.or_else(|err| {
if err.status == StatusCode::NOT_FOUND {

View File

@@ -7,17 +7,12 @@ use tokio::process::Command;
pub trait WindowExt {
fn raise(&self) -> anyhow::Result<()>;
fn hide_menu(&self);
}
impl WindowExt for WebviewWindow {
fn raise(&self) -> anyhow::Result<()> {
raise_window(self)
}
fn hide_menu(&self) {
hide_menu(self);
}
}
pub fn raise_window(win: &WebviewWindow) -> anyhow::Result<()> {
@@ -40,7 +35,7 @@ pub fn raise_window(win: &WebviewWindow) -> anyhow::Result<()> {
// Calling window.show() on Windows will cause the menu to be shown.
// We need to hide it again.
hide_menu(win);
win.hide_menu()?;
Ok(())
}
@@ -76,22 +71,3 @@ async fn wmctrl_try_raise_window(title: &str) -> anyhow::Result<ExitStatus> {
Ok(exit_status)
}
fn hide_menu(win: &WebviewWindow) {
// let menu_handle = win.menu_handle();
// tokio::spawn(async move {
// loop {
// let menu_visible = menu_handle.is_visible().unwrap_or(false);
// if !menu_visible {
// break;
// }
// if menu_visible {
// let _ = menu_handle.hide();
// tokio::time::sleep(Duration::from_millis(10)).await;
// }
// }
// });
}