use core::time::Duration;
use crate::LifecycleState;
pub const ADMIN_PASSKEY_CHECK_TIMEOUT: Duration = Duration::from_millis(500);
pub const ACCESS_RECORD_RETENTION_FLOOR_DAYS: i64 = 730;
pub const ACCESS_RECORD_RETENTION_DAYS: i64 = 730;
const _: () = {
assert!(
ACCESS_RECORD_RETENTION_DAYS >= ACCESS_RECORD_RETENTION_FLOOR_DAYS,
"admin audit retention is below the 접속기록 statutory floor",
);
assert!(
ACCESS_RECORD_RETENTION_FLOOR_DAYS >= 365,
"the 접속기록 floor is at least one year",
);
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActivePasskeys {
Counted(i64),
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AdminLayer1 {
pub is_admin: bool,
pub lifecycle_state: LifecycleState,
}
impl AdminLayer1 {
#[must_use]
pub const fn absent() -> Self {
Self {
is_admin: false,
lifecycle_state: LifecycleState::Tombstoned,
}
}
#[must_use]
pub const fn is_in_effect(self) -> bool {
self.is_admin && matches!(self.lifecycle_state, LifecycleState::Active)
}
#[must_use]
pub const fn with_passkeys(self, passkeys: ActivePasskeys) -> AdminFacts {
AdminFacts {
layer1: self,
passkeys,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AdminFacts {
pub layer1: AdminLayer1,
pub passkeys: ActivePasskeys,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdminVerdict {
Authorized,
NotAdmin,
NoActivePasskey,
CheckTimeout,
}
impl AdminVerdict {
#[must_use]
pub const fn is_authorized(self) -> bool {
matches!(self, Self::Authorized)
}
}
pub const ADMIN_PREMISE_COLUMNS: &[AdminPremiseColumn] = &[
AdminPremiseColumn {
table: "ppnums",
column: "is_admin",
supplies: "AdminLayer1::is_admin",
},
AdminPremiseColumn {
table: "ppnums",
column: "lifecycle_state",
supplies: "AdminLayer1::lifecycle_state",
},
AdminPremiseColumn {
table: "passkey_credentials",
column: "ppnum_id",
supplies: "ActivePasskeys — the count is scoped to one account",
},
AdminPremiseColumn {
table: "passkey_credentials",
column: "revoked_at",
supplies: "ActivePasskeys — a revoked credential is not possession",
},
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AdminPremiseColumn {
pub table: &'static str,
pub column: &'static str,
pub supplies: &'static str,
}
#[must_use]
pub const fn effective_admin(facts: &AdminFacts) -> AdminVerdict {
if !facts.layer1.is_in_effect() {
return AdminVerdict::NotAdmin;
}
match facts.passkeys {
ActivePasskeys::Unknown => AdminVerdict::CheckTimeout,
ActivePasskeys::Counted(n) if n > 0 => AdminVerdict::Authorized,
ActivePasskeys::Counted(_) => AdminVerdict::NoActivePasskey,
}
}
const _: () = {
const fn verdict(
is_admin: bool,
state: LifecycleState,
passkeys: ActivePasskeys,
) -> AdminVerdict {
effective_admin(
&AdminLayer1 {
is_admin,
lifecycle_state: state,
}
.with_passkeys(passkeys),
)
}
assert!(matches!(
verdict(true, LifecycleState::Active, ActivePasskeys::Counted(1)),
AdminVerdict::Authorized
));
assert!(matches!(
verdict(
true,
LifecycleState::Deactivated,
ActivePasskeys::Counted(1)
),
AdminVerdict::NotAdmin
));
assert!(matches!(
verdict(true, LifecycleState::Active, ActivePasskeys::Counted(0)),
AdminVerdict::NoActivePasskey
));
assert!(matches!(
verdict(true, LifecycleState::Active, ActivePasskeys::Unknown),
AdminVerdict::CheckTimeout
));
assert!(matches!(
verdict(false, LifecycleState::Active, ActivePasskeys::Unknown),
AdminVerdict::NotAdmin
));
};
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn the_whole_input_space_decides_as_specified() {
let mut authorized_rows = 0;
for is_admin in [true, false] {
for state in LifecycleState::ALL {
for passkeys in [
ActivePasskeys::Counted(-1),
ActivePasskeys::Counted(0),
ActivePasskeys::Counted(1),
ActivePasskeys::Counted(i64::MAX),
ActivePasskeys::Unknown,
] {
let facts = AdminLayer1 {
is_admin,
lifecycle_state: state,
}
.with_passkeys(passkeys);
let in_effect = is_admin && state == LifecycleState::Active;
let expected = if !in_effect {
AdminVerdict::NotAdmin
} else {
match passkeys {
ActivePasskeys::Unknown => AdminVerdict::CheckTimeout,
ActivePasskeys::Counted(n) if n > 0 => AdminVerdict::Authorized,
ActivePasskeys::Counted(_) => AdminVerdict::NoActivePasskey,
}
};
assert_eq!(
effective_admin(&facts),
expected,
"premises {facts:?} decided against the specification",
);
if expected == AdminVerdict::Authorized {
authorized_rows += 1;
}
}
}
}
assert_eq!(
authorized_rows, 2,
"the admitting region of the premise space changed",
);
}
#[test]
fn a_granted_admin_outside_active_is_denied() {
for state in LifecycleState::ALL {
if state == LifecycleState::Active {
continue;
}
let facts = AdminLayer1 {
is_admin: true,
lifecycle_state: state,
}
.with_passkeys(ActivePasskeys::Counted(3));
assert_eq!(
effective_admin(&facts),
AdminVerdict::NotAdmin,
"a granted admin in {state:?} must not be effective",
);
}
}
#[test]
fn absent_premises_never_authorize() {
for passkeys in [
ActivePasskeys::Counted(0),
ActivePasskeys::Counted(9),
ActivePasskeys::Unknown,
] {
assert_eq!(
effective_admin(&AdminLayer1::absent().with_passkeys(passkeys)),
AdminVerdict::NotAdmin,
);
}
}
#[test]
fn layer1_alone_agrees_with_the_full_verdict() {
for is_admin in [true, false] {
for state in LifecycleState::ALL {
let layer1 = AdminLayer1 {
is_admin,
lifecycle_state: state,
};
for passkeys in [
ActivePasskeys::Counted(0),
ActivePasskeys::Counted(7),
ActivePasskeys::Unknown,
] {
let verdict = effective_admin(&layer1.with_passkeys(passkeys));
if layer1.is_in_effect() {
assert_ne!(
verdict,
AdminVerdict::NotAdmin,
"Layer 1 holds for {layer1:?} but the verdict denies on Layer 1",
);
} else {
assert_eq!(
verdict,
AdminVerdict::NotAdmin,
"Layer 1 fails for {layer1:?} but the verdict did not deny",
);
}
}
}
}
assert!(
AdminLayer1 {
is_admin: true,
lifecycle_state: LifecycleState::Active,
}
.is_in_effect(),
"the one row that holds must hold",
);
assert!(!AdminLayer1::absent().is_in_effect());
}
#[test]
fn is_authorized_admits_exactly_one_verdict() {
assert!(AdminVerdict::Authorized.is_authorized());
for v in [
AdminVerdict::NotAdmin,
AdminVerdict::NoActivePasskey,
AdminVerdict::CheckTimeout,
] {
assert!(!v.is_authorized(), "{v:?} must not read as authorized");
}
}
#[cfg(feature = "serde")]
#[test]
fn layer1_round_trips_as_json() {
let facts = AdminLayer1 {
is_admin: true,
lifecycle_state: LifecycleState::Active,
};
let json = serde_json::to_string(&facts).expect("serialize");
assert_eq!(json, r#"{"is_admin":true,"lifecycle_state":"active"}"#);
assert_eq!(
serde_json::from_str::<AdminLayer1>(&json).expect("deserialize"),
facts,
);
}
#[cfg(feature = "serde")]
#[test]
fn a_stale_bool_cache_entry_does_not_decode() {
assert!(serde_json::from_str::<AdminLayer1>("true").is_err());
assert!(serde_json::from_str::<AdminLayer1>("false").is_err());
}
}