use std::path::PathBuf;
use anyhow::{Context, Result};
use crate::drive::lease::acquire::{MAX_EXPIRY_MINUTES, MIN_EXPIRY_MINUTES};
use crate::drive::lease::authenticate::AuthPolicy;
use crate::utils::env::{non_empty_var, truthy_var, EnvSource};
use crate::utils::settings::LeaseSettings;
pub(crate) const DEFAULT_EXPIRY_MINUTES: i64 = 30;
pub(crate) const LEASE_EXPIRY_MINUTES_ENV: &str = "OMNI_DEV_DRIVE_LEASE_EXPIRY_MINUTES";
pub(crate) const LEASE_BACKUP_DIR_ENV: &str = "OMNI_DEV_DRIVE_LEASE_BACKUP_DIR";
pub(crate) const LEASE_BIOMETRICS_ONLY_ENV: &str = "OMNI_DEV_DRIVE_LEASE_BIOMETRICS_ONLY";
pub(crate) const LEASE_ALLOW_HEADLESS_ENV: &str = "OMNI_DEV_DRIVE_LEASE_ALLOW_HEADLESS";
fn default_backup_dir() -> Result<PathBuf> {
let base = dirs::state_dir()
.or_else(dirs::data_dir)
.context("could not resolve the state/data directory for the default backup directory")?;
Ok(base.join("omni-dev").join("drive-backups"))
}
pub(crate) fn resolve_expiry_minutes(
explicit: Option<i64>,
env: &impl EnvSource,
settings: &LeaseSettings,
) -> Result<i64> {
let fallback = || {
settings
.default_expiry_minutes
.unwrap_or(DEFAULT_EXPIRY_MINUTES)
};
let value = if let Some(value) = explicit {
value
} else if let Some(raw) = non_empty_var(env, LEASE_EXPIRY_MINUTES_ENV) {
if let Ok(value) = raw.parse() {
value
} else {
tracing::warn!(
"{LEASE_EXPIRY_MINUTES_ENV}={raw:?} is not a valid integer; falling back to \
settings.json/the hard-coded default"
);
fallback()
}
} else {
fallback()
};
if !(MIN_EXPIRY_MINUTES..=MAX_EXPIRY_MINUTES).contains(&value) {
anyhow::bail!(
"lease expiry must be between {MIN_EXPIRY_MINUTES} and {MAX_EXPIRY_MINUTES} \
minutes (24 hours), got {value} (from {LEASE_EXPIRY_MINUTES_ENV} or \
settings.json's lease.default_expiry_minutes)"
);
}
Ok(value)
}
fn absolutize(dir: PathBuf) -> Result<PathBuf> {
std::path::absolute(&dir).with_context(|| {
if dir.as_os_str().is_empty() {
"Failed to resolve backup directory to an absolute path: path is empty".to_string()
} else {
format!(
"Failed to resolve backup directory {} to an absolute path",
dir.display()
)
}
})
}
pub(crate) fn resolve_backup_dir(
explicit: Option<PathBuf>,
env: &impl EnvSource,
settings: &LeaseSettings,
) -> Result<PathBuf> {
if let Some(dir) = explicit {
return absolutize(dir);
}
if let Some(dir) = non_empty_var(env, LEASE_BACKUP_DIR_ENV) {
return absolutize(PathBuf::from(dir));
}
if let Some(dir) = settings
.backup_dir
.clone()
.filter(|d| !d.as_os_str().is_empty())
{
return absolutize(dir);
}
default_backup_dir()
}
pub(crate) fn resolve_auth_policy(
cli_biometrics_only: bool,
env: &impl EnvSource,
settings: &LeaseSettings,
) -> AuthPolicy {
if cli_biometrics_only || truthy_var(env, LEASE_BIOMETRICS_ONLY_ENV) || settings.biometrics_only
{
AuthPolicy::BiometricsOnly
} else {
AuthPolicy::DeviceOwner
}
}
pub(crate) fn resolve_allow_headless(
cli_flag: bool,
env: &impl EnvSource,
settings: &LeaseSettings,
) -> bool {
if cli_flag {
return true;
}
if truthy_var(env, LEASE_ALLOW_HEADLESS_ENV) {
tracing::warn!(
"drive lease: the human-presence authentication guarantee is waived for this \
invocation because {LEASE_ALLOW_HEADLESS_ENV} is set (ADR-0080 §8)"
);
return true;
}
if settings.allow_headless {
tracing::warn!(
"drive lease: the human-presence authentication guarantee is waived because \
settings.json's lease.allow_headless is true (ADR-0080 §8/§13) — this applies to \
every invocation on this machine until that setting is turned off"
);
return true;
}
false
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::test_support::env::MapEnv;
fn settings() -> LeaseSettings {
LeaseSettings::default()
}
#[test]
fn expiry_minutes_explicit_wins() {
let env = MapEnv::new().with(LEASE_EXPIRY_MINUTES_ENV, "10");
let mut s = settings();
s.default_expiry_minutes = Some(20);
assert_eq!(resolve_expiry_minutes(Some(5), &env, &s).unwrap(), 5);
}
#[test]
fn expiry_minutes_env_beats_settings() {
let env = MapEnv::new().with(LEASE_EXPIRY_MINUTES_ENV, "10");
let mut s = settings();
s.default_expiry_minutes = Some(20);
assert_eq!(resolve_expiry_minutes(None, &env, &s).unwrap(), 10);
}
#[test]
fn expiry_minutes_settings_beats_hardcoded_default() {
let mut s = settings();
s.default_expiry_minutes = Some(20);
assert_eq!(
resolve_expiry_minutes(None, &MapEnv::new(), &s).unwrap(),
20
);
}
#[test]
fn expiry_minutes_falls_back_to_hardcoded_default() {
assert_eq!(
resolve_expiry_minutes(None, &MapEnv::new(), &settings()).unwrap(),
DEFAULT_EXPIRY_MINUTES
);
}
#[test]
fn expiry_minutes_unparseable_env_is_treated_as_unset() {
let env = MapEnv::new().with(LEASE_EXPIRY_MINUTES_ENV, "not-a-number");
let mut s = settings();
s.default_expiry_minutes = Some(20);
assert_eq!(resolve_expiry_minutes(None, &env, &s).unwrap(), 20);
}
#[test]
fn expiry_minutes_empty_env_is_treated_as_unset() {
let env = MapEnv::new().with(LEASE_EXPIRY_MINUTES_ENV, "");
let mut s = settings();
s.default_expiry_minutes = Some(20);
assert_eq!(resolve_expiry_minutes(None, &env, &s).unwrap(), 20);
}
#[test]
fn expiry_minutes_out_of_range_env_value_is_a_clean_error_not_a_panic() {
let env = MapEnv::new().with(LEASE_EXPIRY_MINUTES_ENV, "9223372036854775807");
let err = resolve_expiry_minutes(None, &env, &settings()).unwrap_err();
assert!(err.to_string().contains("must be between"), "{err}");
}
#[test]
fn expiry_minutes_out_of_range_settings_value_is_a_clean_error_not_a_panic() {
let mut s = settings();
s.default_expiry_minutes = Some(0);
let err = resolve_expiry_minutes(None, &MapEnv::new(), &s).unwrap_err();
assert!(err.to_string().contains("must be between"), "{err}");
}
#[test]
fn backup_dir_explicit_wins() {
let env = MapEnv::new().with(LEASE_BACKUP_DIR_ENV, "/from/env");
let mut s = settings();
s.backup_dir = Some(PathBuf::from("/from/settings"));
assert_eq!(
resolve_backup_dir(Some(PathBuf::from("/from/cli")), &env, &s).unwrap(),
PathBuf::from("/from/cli")
);
}
#[test]
fn backup_dir_env_beats_settings() {
let env = MapEnv::new().with(LEASE_BACKUP_DIR_ENV, "/from/env");
let mut s = settings();
s.backup_dir = Some(PathBuf::from("/from/settings"));
assert_eq!(
resolve_backup_dir(None, &env, &s).unwrap(),
PathBuf::from("/from/env")
);
}
#[test]
fn backup_dir_settings_beats_hardcoded_default() {
let mut s = settings();
s.backup_dir = Some(PathBuf::from("/from/settings"));
assert_eq!(
resolve_backup_dir(None, &MapEnv::new(), &s).unwrap(),
PathBuf::from("/from/settings")
);
}
#[test]
fn backup_dir_falls_back_to_hardcoded_default() {
let resolved = resolve_backup_dir(None, &MapEnv::new(), &settings()).unwrap();
assert!(resolved.ends_with("omni-dev/drive-backups"));
}
#[test]
fn backup_dir_empty_settings_is_treated_as_unset() {
let mut s = settings();
s.backup_dir = Some(PathBuf::from(""));
let resolved = resolve_backup_dir(None, &MapEnv::new(), &s).unwrap();
assert!(resolved.ends_with("omni-dev/drive-backups"));
}
#[test]
fn backup_dir_explicit_relative_is_absolutized() {
let resolved = resolve_backup_dir(
Some(PathBuf::from("relative/cli")),
&MapEnv::new(),
&settings(),
)
.unwrap();
assert!(resolved.is_absolute(), "{}", resolved.display());
assert!(resolved.ends_with("relative/cli"), "{}", resolved.display());
}
#[test]
fn backup_dir_env_relative_is_absolutized() {
let env = MapEnv::new().with(LEASE_BACKUP_DIR_ENV, "relative/env");
let resolved = resolve_backup_dir(None, &env, &settings()).unwrap();
assert!(resolved.is_absolute(), "{}", resolved.display());
assert!(resolved.ends_with("relative/env"), "{}", resolved.display());
}
#[test]
fn backup_dir_settings_relative_is_absolutized() {
let mut s = settings();
s.backup_dir = Some(PathBuf::from("relative/settings"));
let resolved = resolve_backup_dir(None, &MapEnv::new(), &s).unwrap();
assert!(resolved.is_absolute(), "{}", resolved.display());
assert!(
resolved.ends_with("relative/settings"),
"{}",
resolved.display()
);
}
#[test]
fn auth_policy_defaults_to_device_owner() {
assert_eq!(
resolve_auth_policy(false, &MapEnv::new(), &settings()),
AuthPolicy::DeviceOwner
);
}
#[test]
fn auth_policy_cli_flag_selects_biometrics_only() {
assert_eq!(
resolve_auth_policy(true, &MapEnv::new(), &settings()),
AuthPolicy::BiometricsOnly
);
}
#[test]
fn auth_policy_env_selects_biometrics_only() {
let env = MapEnv::new().with(LEASE_BIOMETRICS_ONLY_ENV, "true");
assert_eq!(
resolve_auth_policy(false, &env, &settings()),
AuthPolicy::BiometricsOnly
);
}
#[test]
fn auth_policy_settings_selects_biometrics_only() {
let mut s = settings();
s.biometrics_only = true;
assert_eq!(
resolve_auth_policy(false, &MapEnv::new(), &s),
AuthPolicy::BiometricsOnly
);
}
#[test]
fn allow_headless_defaults_to_false() {
assert!(!resolve_allow_headless(false, &MapEnv::new(), &settings()));
}
#[test]
fn allow_headless_cli_flag_wins() {
assert!(resolve_allow_headless(true, &MapEnv::new(), &settings()));
}
#[test]
fn allow_headless_env_wins() {
let env = MapEnv::new().with(LEASE_ALLOW_HEADLESS_ENV, "yes");
assert!(resolve_allow_headless(false, &env, &settings()));
}
#[test]
fn allow_headless_settings_wins() {
let mut s = settings();
s.allow_headless = true;
assert!(resolve_allow_headless(false, &MapEnv::new(), &s));
}
}