use axum::http::HeaderMap;
use super::conformance::Report;
use crate::api::{AuthError, Authenticator, Caller};
#[derive(Debug, Default)]
pub struct Requests {
pub accepted: Option<(HeaderMap, String)>,
pub rejected: Vec<(&'static str, HeaderMap)>,
}
pub async fn check(auth: &dyn Authenticator, requests: &Requests, report: &mut Report) {
no_credentials_is_missing_not_anonymous(auth, report).await;
a_presented_credential_is_rejected_not_missing(auth, requests, report).await;
an_accepted_request_names_its_actor(auth, requests, report).await;
}
async fn no_credentials_is_missing_not_anonymous(auth: &dyn Authenticator, r: &mut Report) {
const RULE: &str = "no credentials is a refusal";
r.checked += 1;
match auth.authenticate(&HeaderMap::new()).await {
Err(AuthError::Missing) => {}
Err(AuthError::Rejected) => r.record(
RULE,
"a request carrying nothing was `Rejected` rather than `Missing` — the two \
are different answers, and only one of them means somebody tried",
),
Ok(caller) => r.record(
RULE,
format!(
"a request carrying no credentials was authenticated as `{}` — an \
unnamed actor on an approval is the one absence this design refuses",
caller.actor
),
),
}
}
async fn a_presented_credential_is_rejected_not_missing(
auth: &dyn Authenticator,
requests: &Requests,
r: &mut Report,
) {
const RULE: &str = "a presented credential is rejected, not missing";
for (what, headers) in &requests.rejected {
r.checked += 1;
match auth.authenticate(headers).await {
Err(AuthError::Rejected) => {}
Err(AuthError::Missing) => r.record(
RULE,
format!(
"`{what}` carries a credential this server speaks and was refused as \
`Missing` — that bit separates *the right shape* from *nothing at \
all*, which is what a prober is looking for"
),
),
Ok(caller) => r.record(
RULE,
format!(
"`{what}` was accepted as `{}`, so this deployment's own example of \
a bad credential is one it admits",
caller.actor
),
),
}
}
}
async fn an_accepted_request_names_its_actor(
auth: &dyn Authenticator,
requests: &Requests,
r: &mut Report,
) {
const RULE: &str = "an accepted request names its actor";
let Some((headers, principal)) = requests.accepted.as_ref() else {
return;
};
r.checked += 1;
match auth.authenticate(headers).await {
Ok(Caller { actor, .. }) if &actor == principal => {}
Ok(caller) => r.record(
RULE,
format!(
"the accepted request named `{}` rather than `{principal}` — the \
actor is what every later authorization and every recorded act is \
attributed to",
caller.actor
),
),
Err(e) => r.record(
RULE,
format!(
"the request this deployment supplied as accepted was refused ({e}), so \
the refusals above prove only that this authenticator refuses"
),
),
}
}