upgrade gpauth

This commit is contained in:
Kevin Yue
2024-12-13 10:58:39 +00:00
parent f474ab36c0
commit 07e325e467
24 changed files with 4980 additions and 707 deletions

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

@@ -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 {