feat: gpauth support macos

This commit is contained in:
Kevin Yue
2025-01-05 23:42:03 +08:00
parent 0c9b8e6c63
commit af9b026b14
40 changed files with 961 additions and 824 deletions

View File

@@ -33,8 +33,9 @@ sha256.workspace = true
tauri = { workspace = true, optional = true }
clap = { workspace = true, optional = true }
clap-verbosity-flag = { workspace = true, optional = true }
[features]
tauri = ["dep:tauri"]
clap = ["dep:clap"]
clap = ["dep:clap", "dep:clap-verbosity-flag"]
webview-auth = []

View File

@@ -72,15 +72,12 @@ impl SamlAuthData {
let prelogin_cookie = parse_xml_tag(html, "prelogin-cookie");
let portal_userauthcookie = parse_xml_tag(html, "portal-userauthcookie");
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)
SamlAuthData::new(username, prelogin_cookie, portal_userauthcookie).map_err(AuthDataParseError::Invalid)
}
Some(status) => Err(AuthDataParseError::Invalid(anyhow::anyhow!(
"SAML auth status: {}",
status
))),
None => Err(AuthDataParseError::NotFound),
}
}
@@ -100,7 +97,7 @@ impl SamlAuthData {
let auth_data: SamlAuthData = serde_urlencoded::from_str(auth_data.borrow()).map_err(|e| {
warn!("Failed to parse token auth data: {}", e);
warn!("Auth data: {}", auth_data);
AuthDataParseError::Invalid
AuthDataParseError::Invalid(anyhow::anyhow!(e))
})?;
return Ok(auth_data);
@@ -108,7 +105,7 @@ impl SamlAuthData {
let auth_data = decode_to_string(auth_data).map_err(|e| {
warn!("Failed to decode SAML auth data: {}", e);
AuthDataParseError::Invalid
AuthDataParseError::Invalid(anyhow::anyhow!(e))
})?;
let auth_data = Self::from_html(&auth_data)?;
@@ -128,7 +125,7 @@ impl SamlAuthData {
}
}
pub fn parse_xml_tag(html: &str, tag: &str) -> Option<String> {
fn parse_xml_tag(html: &str, tag: &str) -> Option<String> {
let re = Regex::new(&format!("<{}>(.*)</{}>", tag, tag)).unwrap();
re.captures(html)
.and_then(|captures| captures.get(1))

View File

@@ -1,3 +1,5 @@
use clap_verbosity_flag::{LogLevel, Verbosity, VerbosityFilter};
use crate::error::PortalError;
pub mod args;
@@ -26,3 +28,41 @@ pub fn handle_error(err: anyhow::Error, args: &impl Args) {
eprintln!("{} --ignore-tls-errors {}\n", args[0], args[1..].join(" "));
}
}
#[derive(Debug)]
pub struct InfoLevel;
pub type InfoLevelVerbosity = Verbosity<InfoLevel>;
impl LogLevel for InfoLevel {
fn default_filter() -> VerbosityFilter {
VerbosityFilter::Info
}
fn verbose_help() -> Option<&'static str> {
Some("Enable verbose output, -v for debug, -vv for trace")
}
fn quiet_help() -> Option<&'static str> {
Some("Decrease logging verbosity, -q for warnings, -qq for errors")
}
}
pub trait VerbosityToCliArg {
fn to_cli_arg(&self) -> Option<&'static str>;
}
/// Convert the verbosity to the CLI argument value
/// The default verbosity is `Info`, which means no argument is needed
impl VerbosityToCliArg for InfoLevelVerbosity {
fn to_cli_arg(&self) -> Option<&'static str> {
match self.filter() {
VerbosityFilter::Off => Some("-qqq"),
VerbosityFilter::Error => Some("-qq"),
VerbosityFilter::Warn => Some("-q"),
VerbosityFilter::Info => None,
VerbosityFilter::Debug => Some("-v"),
VerbosityFilter::Trace => Some("-vv"),
}
}
}

View File

@@ -26,12 +26,12 @@ impl PortalError {
pub enum AuthDataParseError {
#[error("No auth data found")]
NotFound,
#[error("Invalid auth data")]
Invalid,
#[error(transparent)]
Invalid(#[from] anyhow::Error),
}
impl AuthDataParseError {
pub fn is_invalid(&self) -> bool {
matches!(self, AuthDataParseError::Invalid)
matches!(self, AuthDataParseError::Invalid(_))
}
}

View File

@@ -1,6 +1,6 @@
use anyhow::bail;
use dns_lookup::lookup_addr;
use log::{debug, info, warn};
use log::{info, warn};
use reqwest::{Client, StatusCode};
use roxmltree::{Document, Node};
use serde::Serialize;
@@ -135,8 +135,6 @@ pub async fn retrieve_config(portal: &str, cred: &Credential, gp_params: &GpPara
bail!(PortalError::ConfigError("Empty portal config response".to_string()))
}
debug!("Portal config response: {}", res_xml);
let doc = Document::parse(&res_xml).map_err(|e| PortalError::ConfigError(e.to_string()))?;
let root = doc.root();

View File

@@ -3,7 +3,12 @@ use std::process::Stdio;
use anyhow::bail;
use tokio::process::Command;
use crate::{auth::SamlAuthResult, credential::Credential, GP_AUTH_BINARY};
use crate::{
auth::SamlAuthResult,
clap::{InfoLevelVerbosity, VerbosityToCliArg},
credential::Credential,
GP_AUTH_BINARY,
};
use super::command_traits::CommandExt;
@@ -23,6 +28,7 @@ pub struct SamlAuthLauncher<'a> {
#[cfg(feature = "webview-auth")]
default_browser: bool,
browser: Option<&'a str>,
verbose: Option<&'a InfoLevelVerbosity>,
}
impl<'a> SamlAuthLauncher<'a> {
@@ -43,6 +49,7 @@ impl<'a> SamlAuthLauncher<'a> {
#[cfg(feature = "webview-auth")]
default_browser: false,
browser: None,
verbose: None,
}
}
@@ -104,6 +111,11 @@ impl<'a> SamlAuthLauncher<'a> {
self
}
pub fn verbose(mut self, verbose: &'a InfoLevelVerbosity) -> Self {
self.verbose = Some(verbose);
self
}
/// Launch the authenticator binary as the current user or SUDO_USER if available.
pub async fn launch(self) -> anyhow::Result<Credential> {
let mut auth_cmd = Command::new(GP_AUTH_BINARY);
@@ -156,6 +168,11 @@ impl<'a> SamlAuthLauncher<'a> {
auth_cmd.arg("--browser").arg(browser);
}
if let Some(verbose) = self.verbose {
let arg = verbose.to_cli_arg();
arg.map(|arg| auth_cmd.arg(arg));
}
let mut non_root_cmd = auth_cmd.into_non_root()?;
let output = non_root_cmd
.kill_on_drop(true)