use parlov_core::{DifferentialSet, ResponseSurface};
use super::auth_classifier::classify_auth_block;
use super::auth_types::{AuthBlockFamily, AuthBlockSignature, AuthChallenge, AuthGateDecision};
use super::precondition::{AuthBlockLayer, PreconditionBlock};
#[must_use]
pub fn equivalent_auth_block(
b: &AuthBlockSignature,
p: &AuthBlockSignature,
b_res: &ResponseSurface,
p_res: &ResponseSurface,
) -> bool {
if b.family != p.family || b.credential_state != p.credential_state || b.status != p.status {
return false;
}
if !equivalent_challenge(b.challenge.as_ref(), p.challenge.as_ref()) {
return false;
}
if !equivalent_body(b, p, b_res, p_res) {
return false;
}
if b.family == AuthBlockFamily::LoginRedirect && !equivalent_login_redirect(b, p) {
return false;
}
true
}
fn equivalent_challenge(a: Option<&AuthChallenge>, b: Option<&AuthChallenge>) -> bool {
match (a, b) {
(None, None) => true,
(Some(x), Some(y)) => {
x.scheme == y.scheme && x.error == y.error && x.realm == y.realm && x.scope == y.scope
}
_ => false,
}
}
fn equivalent_body(
b: &AuthBlockSignature,
p: &AuthBlockSignature,
b_res: &ResponseSurface,
p_res: &ResponseSurface,
) -> bool {
match (&b.body_signal, &p.body_signal) {
(Some(x), Some(y)) => x.code == y.code,
(None, None) => b_res.body == p_res.body,
_ => false,
}
}
fn equivalent_login_redirect(b: &AuthBlockSignature, p: &AuthBlockSignature) -> bool {
match (&b.login_redirect, &p.login_redirect) {
(Some(x), Some(y)) => x.location == y.location,
_ => false,
}
}
#[must_use]
pub fn auth_gate_decision(differential: &DifferentialSet) -> AuthGateDecision {
let Some(b_ex) = differential.baseline.first() else {
return AuthGateDecision::NoAuthInvolvement;
};
let Some(p_ex) = differential.probe.first() else {
return AuthGateDecision::NoAuthInvolvement;
};
let b_sig = classify_auth_block(&b_ex.request, &b_ex.response);
let p_sig = classify_auth_block(&p_ex.request, &p_ex.response);
match (b_sig, p_sig) {
(Some(b), Some(p)) if equivalent_auth_block(&b, &p, &b_ex.response, &p_ex.response) => {
let layer = layer_from_family(b.family);
AuthGateDecision::Gate(PreconditionBlock::AuthGateBeforeTechnique {
credential_state: b.credential_state,
layer,
})
}
(Some(_) | None, Some(_)) | (Some(_), None) => AuthGateDecision::DoNotGate,
(None, None) => AuthGateDecision::NoAuthInvolvement,
}
}
#[must_use]
pub fn layer_from_family(family: AuthBlockFamily) -> AuthBlockLayer {
match family {
AuthBlockFamily::OriginAuthentication
| AuthBlockFamily::OriginAuthorization
| AuthBlockFamily::AuthErrorEnvelope => AuthBlockLayer::Origin,
AuthBlockFamily::ProxyAuthentication => AuthBlockLayer::Proxy,
AuthBlockFamily::NetworkAuthentication => AuthBlockLayer::Network,
AuthBlockFamily::LoginRedirect => AuthBlockLayer::LoginRedirect,
}
}