use crate::logic::privilege::AuthMode;
#[must_use]
pub fn resolve_auth_mode(settings: &crate::theme::Settings) -> AuthMode {
static WARNED_LEGACY_PASSWORDLESS_MAPPING: std::sync::Once = std::sync::Once::new();
static WARNED_LEGACY_PASSWORDLESS_CONFLICT: std::sync::Once = std::sync::Once::new();
if crate::logic::privilege::is_integration_test() {
if let Ok(val) = std::env::var("PACSEA_TEST_AUTH_MODE") {
tracing::debug!(val = %val, "Using test override for resolve_auth_mode");
if let Some(mode) = AuthMode::from_config_key(&val) {
return coerce_prompt_mode_for_tool_capabilities(mode);
}
}
if std::env::var("PACSEA_TEST_SUDO_PASSWORDLESS")
.ok()
.as_deref()
== Some("1")
{
tracing::debug!("Legacy test env PACSEA_TEST_SUDO_PASSWORDLESS=1 → PasswordlessOnly");
return coerce_prompt_mode_for_tool_capabilities(AuthMode::PasswordlessOnly);
}
}
let explicit_auth_mode = settings.auth_mode;
let legacy_passwordless = settings.use_passwordless_sudo;
let resolved = match (explicit_auth_mode, legacy_passwordless) {
(AuthMode::Prompt, true) => {
WARNED_LEGACY_PASSWORDLESS_MAPPING.call_once(|| {
tracing::warn!(
"Deprecated: 'use_passwordless_sudo = true' is active. \
Mapping to auth_mode = passwordless_only. \
Please migrate to 'auth_mode = passwordless_only' in settings.conf."
);
});
AuthMode::PasswordlessOnly
}
(mode, true) if mode != AuthMode::Prompt => {
WARNED_LEGACY_PASSWORDLESS_CONFLICT.call_once(|| {
tracing::warn!(
auth_mode = %mode,
"Deprecated: 'use_passwordless_sudo' is set alongside 'auth_mode'. \
'auth_mode = {mode}' takes precedence. \
Please remove 'use_passwordless_sudo' from settings.conf."
);
});
mode
}
(mode, _) => mode,
};
coerce_prompt_mode_for_tool_capabilities(resolved)
}
fn coerce_prompt_mode_for_tool_capabilities(mode: AuthMode) -> AuthMode {
if mode != AuthMode::Prompt {
return mode;
}
let tool = match crate::logic::privilege::active_tool() {
Ok(tool) => tool,
Err(err) => {
tracing::debug!(
error = %err,
"Could not resolve active privilege tool while resolving auth mode; leaving mode unchanged"
);
return mode;
}
};
if tool.capabilities().supports_stdin_password {
return mode;
}
tracing::warn!(
tool = %tool,
configured_mode = %mode,
forced_mode = %AuthMode::Interactive,
"Auth mode 'prompt' is incompatible with the active privilege tool; forcing interactive auth"
);
AuthMode::Interactive
}
#[must_use]
pub fn should_use_interactive_auth_handoff(settings: &crate::theme::Settings) -> bool {
resolve_auth_mode(settings) == AuthMode::Interactive
}
#[must_use]
pub fn should_force_interactive_auth_handoff(settings: &crate::theme::Settings) -> bool {
resolve_auth_mode(settings) == AuthMode::Interactive
&& settings.auth_mode != AuthMode::Interactive
}
#[must_use]
pub fn should_skip_password_modal(settings: &crate::theme::Settings) -> bool {
let mode = resolve_auth_mode(settings);
match mode {
AuthMode::Interactive => {
tracing::info!("Auth mode is 'interactive'; skipping Pacsea password modal");
true
}
AuthMode::PasswordlessOnly => should_use_passwordless_sudo(settings),
AuthMode::Prompt => false,
}
}
pub fn check_passwordless_sudo_available() -> Result<bool, String> {
let tool = crate::logic::privilege::active_tool()?;
tool.check_passwordless()
}
#[must_use]
pub fn should_use_passwordless_sudo(settings: &crate::theme::Settings) -> bool {
if crate::logic::privilege::is_integration_test()
&& let Ok(val) = std::env::var("PACSEA_TEST_SUDO_PASSWORDLESS")
{
tracing::debug!(
val = %val,
"Using test override for should_use_passwordless_sudo"
);
return val == "1";
}
let auth_mode = resolve_auth_mode(settings);
let require_legacy_toggle = auth_mode != AuthMode::PasswordlessOnly;
if require_legacy_toggle && !settings.use_passwordless_sudo {
tracing::debug!("Passwordless privilege disabled in settings, requiring password prompt");
return false;
}
match check_passwordless_sudo_available() {
Ok(true) => {
tracing::info!("Passwordless privilege enabled in settings and available on system");
true
}
Ok(false) => {
tracing::debug!(
"Passwordless privilege enabled in settings but not available on system, requiring password prompt"
);
false
}
Err(e) => {
tracing::debug!(
"Passwordless privilege check failed ({}), requiring password prompt",
e
);
false
}
}
}
pub fn validate_sudo_password(password: &str) -> Result<bool, String> {
let tool = crate::logic::privilege::active_tool()?;
crate::logic::privilege::validate_password(tool, password)
}
#[cfg(test)]
mod tests {
use super::*;
fn is_passwordless_sudo() -> bool {
check_passwordless_sudo_available().unwrap_or(false)
}
#[test]
fn test_check_passwordless_sudo_available() {
let _guard = crate::global_test_mutex_lock();
unsafe {
std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "sudo");
std::env::set_var("PACSEA_TEST_SUDO_PASSWORDLESS", "1");
}
let result = check_passwordless_sudo_available();
unsafe {
std::env::remove_var("PACSEA_TEST_SUDO_PASSWORDLESS");
std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
std::env::remove_var("PACSEA_INTEGRATION_TEST");
}
assert_eq!(result, Ok(true));
}
#[test]
fn test_should_use_passwordless_sudo_false_for_doas_when_passwordless_unavailable() {
let _guard = crate::global_test_mutex_lock();
unsafe {
std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "doas");
std::env::set_var("PACSEA_TEST_AUTH_MODE", "passwordless_only");
std::env::set_var("PACSEA_TEST_SUDO_PASSWORDLESS", "0");
}
let settings = crate::theme::Settings::default();
let should_skip_prompt = should_use_passwordless_sudo(&settings);
unsafe {
std::env::remove_var("PACSEA_TEST_SUDO_PASSWORDLESS");
std::env::remove_var("PACSEA_TEST_AUTH_MODE");
std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
std::env::remove_var("PACSEA_INTEGRATION_TEST");
}
assert!(
!should_skip_prompt,
"doas should not be treated as passwordless when -n check fails"
);
}
#[test]
fn test_should_use_interactive_auth_handoff_true_for_prompt_doas() {
let _guard = crate::global_test_mutex_lock();
unsafe {
std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "doas");
std::env::remove_var("PACSEA_TEST_AUTH_MODE");
}
let settings = crate::theme::Settings::default();
let result = should_use_interactive_auth_handoff(&settings);
unsafe {
std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
std::env::remove_var("PACSEA_INTEGRATION_TEST");
}
assert!(
result,
"doas + prompt should force interactive auth handoff"
);
}
#[test]
fn test_resolve_auth_mode_default_is_prompt() {
let _guard = crate::global_test_mutex_lock();
unsafe {
std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "sudo");
std::env::remove_var("PACSEA_TEST_AUTH_MODE");
}
let settings = crate::theme::Settings::default();
let mode = resolve_auth_mode(&settings);
unsafe {
std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
std::env::remove_var("PACSEA_INTEGRATION_TEST");
}
assert_eq!(mode, AuthMode::Prompt);
}
#[test]
fn test_resolve_auth_mode_prompt_for_doas_forces_interactive() {
let _guard = crate::global_test_mutex_lock();
unsafe {
std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "doas");
std::env::remove_var("PACSEA_TEST_AUTH_MODE");
}
let settings = crate::theme::Settings::default();
let mode = resolve_auth_mode(&settings);
unsafe {
std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
std::env::remove_var("PACSEA_INTEGRATION_TEST");
}
assert_eq!(mode, AuthMode::Interactive);
}
#[test]
fn test_resolve_auth_mode_explicit_interactive() {
let _guard = crate::global_test_mutex_lock();
unsafe {
std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "sudo");
std::env::remove_var("PACSEA_TEST_AUTH_MODE");
}
let settings = crate::theme::Settings {
auth_mode: AuthMode::Interactive,
..crate::theme::Settings::default()
};
let mode = resolve_auth_mode(&settings);
unsafe {
std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
std::env::remove_var("PACSEA_INTEGRATION_TEST");
}
assert_eq!(mode, AuthMode::Interactive);
}
#[test]
fn test_resolve_auth_mode_legacy_passwordless_maps() {
let _guard = crate::global_test_mutex_lock();
unsafe {
std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "sudo");
std::env::remove_var("PACSEA_TEST_AUTH_MODE");
}
let settings = crate::theme::Settings {
use_passwordless_sudo: true,
..crate::theme::Settings::default()
};
let mode = resolve_auth_mode(&settings);
unsafe {
std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
std::env::remove_var("PACSEA_INTEGRATION_TEST");
}
assert_eq!(mode, AuthMode::PasswordlessOnly);
}
#[test]
fn test_resolve_auth_mode_explicit_wins_over_legacy() {
let _guard = crate::global_test_mutex_lock();
unsafe {
std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "sudo");
std::env::remove_var("PACSEA_TEST_AUTH_MODE");
}
let settings = crate::theme::Settings {
auth_mode: AuthMode::Interactive,
use_passwordless_sudo: true,
..crate::theme::Settings::default()
};
let mode = resolve_auth_mode(&settings);
unsafe {
std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
std::env::remove_var("PACSEA_INTEGRATION_TEST");
}
assert_eq!(mode, AuthMode::Interactive);
}
#[test]
fn test_resolve_auth_mode_env_override() {
let _guard = crate::global_test_mutex_lock();
unsafe {
std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "sudo");
std::env::set_var("PACSEA_TEST_AUTH_MODE", "interactive");
}
let settings = crate::theme::Settings::default();
let mode = resolve_auth_mode(&settings);
unsafe {
std::env::remove_var("PACSEA_TEST_AUTH_MODE");
std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
std::env::remove_var("PACSEA_INTEGRATION_TEST");
}
assert_eq!(mode, AuthMode::Interactive);
}
#[test]
fn test_should_skip_password_modal_interactive() {
let _guard = crate::global_test_mutex_lock();
unsafe {
std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "sudo");
std::env::remove_var("PACSEA_TEST_AUTH_MODE");
}
let settings = crate::theme::Settings {
auth_mode: AuthMode::Interactive,
..crate::theme::Settings::default()
};
let skip = should_skip_password_modal(&settings);
unsafe {
std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
std::env::remove_var("PACSEA_INTEGRATION_TEST");
}
assert!(skip, "Interactive mode should always skip password modal");
}
#[test]
fn test_should_skip_password_modal_interactive_doas() {
let _guard = crate::global_test_mutex_lock();
unsafe {
std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "doas");
std::env::remove_var("PACSEA_TEST_AUTH_MODE");
}
let settings = crate::theme::Settings {
auth_mode: AuthMode::Interactive,
..crate::theme::Settings::default()
};
let skip = should_skip_password_modal(&settings);
unsafe {
std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
std::env::remove_var("PACSEA_INTEGRATION_TEST");
}
assert!(
skip,
"Interactive mode should skip password modal for doas too"
);
}
#[test]
fn test_should_skip_password_modal_prompt() {
let _guard = crate::global_test_mutex_lock();
unsafe {
std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "sudo");
std::env::remove_var("PACSEA_TEST_AUTH_MODE");
}
let settings = crate::theme::Settings::default();
let skip = should_skip_password_modal(&settings);
unsafe {
std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
std::env::remove_var("PACSEA_INTEGRATION_TEST");
}
assert!(!skip, "Prompt mode should always show password modal");
}
#[test]
#[ignore = "Uses sudo with wrong password - may lock user out. Run with --ignored"]
fn test_validate_sudo_password_invalid() {
if is_passwordless_sudo() {
return;
}
let result = validate_sudo_password("definitely_wrong_password_12345");
if let Ok(valid) = result {
assert!(!valid);
} else {
}
}
#[test]
#[ignore = "Uses sudo with wrong password - may lock user out. Run with --ignored"]
fn test_validate_sudo_password_empty() {
if is_passwordless_sudo() {
return;
}
let result = validate_sudo_password("");
if let Ok(valid) = result {
assert!(!valid);
} else {
}
}
#[test]
#[ignore = "Uses sudo with wrong password - may lock user out. Run with --ignored"]
fn test_validate_sudo_password_special_chars() {
let passwords = vec![
"pass'word",
"pass\"word",
"pass$word",
"pass`word",
"pass\\word",
];
for pass in passwords {
let result = validate_sudo_password(pass);
let _ = result;
}
}
#[test]
#[ignore = "Uses sudo with wrong password - may lock user out. Run with --ignored"]
fn test_validate_sudo_password_signature() {
let result: Result<bool, String> = validate_sudo_password("test");
let _ = result;
}
}