1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9#[non_exhaustive]
10pub enum ProductionMiddlewareStage {
11 TrustedProxy,
13 BodyLimit,
15 RequestId,
17 Tracing,
19 SecureHeaders,
21 Cors,
23 WafRasp,
25 Csrf,
27 Session,
29 Authentication,
31 Tenant,
33 Authorization,
35 RateLimit,
37 Handler,
39}
40
41#[derive(Debug, Clone, Copy, Default)]
47pub struct ProductionPreset;
48
49impl ProductionPreset {
50 pub const MIDDLEWARE_ORDER: &'static [ProductionMiddlewareStage] = &[
52 ProductionMiddlewareStage::TrustedProxy,
53 ProductionMiddlewareStage::BodyLimit,
54 ProductionMiddlewareStage::RequestId,
55 ProductionMiddlewareStage::Tracing,
56 ProductionMiddlewareStage::SecureHeaders,
57 ProductionMiddlewareStage::Cors,
58 ProductionMiddlewareStage::WafRasp,
59 ProductionMiddlewareStage::Csrf,
60 ProductionMiddlewareStage::Session,
61 ProductionMiddlewareStage::Authentication,
62 ProductionMiddlewareStage::Tenant,
63 ProductionMiddlewareStage::Authorization,
64 ProductionMiddlewareStage::RateLimit,
65 ProductionMiddlewareStage::Handler,
66 ];
67
68 pub const fn middleware_order() -> &'static [ProductionMiddlewareStage] {
70 Self::MIDDLEWARE_ORDER
71 }
72
73 pub const fn academy() -> AcademyProductionPreset {
81 AcademyProductionPreset
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
87#[non_exhaustive]
88pub enum AcademyProductionRequirement {
89 AuthenticatedIdentity,
91 SchoolMembership,
93 ActiveEntitlement,
95 ObjectAuthorization,
97 TenantIsolation,
99 ServerValidatedAssessment,
101 IdempotentScoreEvents,
103 DurableAutomation,
105 DurableAdminAudit,
107 SafeContentPipeline,
109 PrivacyLifecycle,
111 DistributedAbuseControl,
113}
114
115impl AcademyProductionRequirement {
116 pub const ALL: &'static [Self] = &[
118 Self::AuthenticatedIdentity,
119 Self::SchoolMembership,
120 Self::ActiveEntitlement,
121 Self::ObjectAuthorization,
122 Self::TenantIsolation,
123 Self::ServerValidatedAssessment,
124 Self::IdempotentScoreEvents,
125 Self::DurableAutomation,
126 Self::DurableAdminAudit,
127 Self::SafeContentPipeline,
128 Self::PrivacyLifecycle,
129 Self::DistributedAbuseControl,
130 ];
131
132 pub const fn as_str(self) -> &'static str {
134 match self {
135 Self::AuthenticatedIdentity => "authenticated_identity",
136 Self::SchoolMembership => "school_membership",
137 Self::ActiveEntitlement => "active_entitlement",
138 Self::ObjectAuthorization => "object_authorization",
139 Self::TenantIsolation => "tenant_isolation",
140 Self::ServerValidatedAssessment => "server_validated_assessment",
141 Self::IdempotentScoreEvents => "idempotent_score_events",
142 Self::DurableAutomation => "durable_automation",
143 Self::DurableAdminAudit => "durable_admin_audit",
144 Self::SafeContentPipeline => "safe_content_pipeline",
145 Self::PrivacyLifecycle => "privacy_lifecycle",
146 Self::DistributedAbuseControl => "distributed_abuse_control",
147 }
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153#[non_exhaustive]
154pub enum AcademyCheckStatus {
155 Pass,
157 Fail,
159 Skipped,
161 NotEvaluated,
163}
164
165impl AcademyCheckStatus {
166 pub const fn as_str(self) -> &'static str {
168 match self {
169 Self::Pass => "PASS",
170 Self::Fail => "FAIL",
171 Self::Skipped => "SKIPPED",
172 Self::NotEvaluated => "NOT_EVALUATED",
173 }
174 }
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179#[non_exhaustive]
180pub struct AcademyBoundaryAssessment {
181 requirement: AcademyProductionRequirement,
182 status: AcademyCheckStatus,
183}
184
185impl AcademyBoundaryAssessment {
186 pub const fn requirement(&self) -> AcademyProductionRequirement {
188 self.requirement
189 }
190
191 pub const fn status(&self) -> AcademyCheckStatus {
193 self.status
194 }
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
199#[non_exhaustive]
200pub enum AcademyPresetError {
201 #[error("Academy requirement {0:?} was supplied more than once")]
203 DuplicateRequirement(AcademyProductionRequirement),
204 #[error("Academy requirement {requirement:?} is {status:?}, not PASS")]
206 RequirementNotPassed {
207 requirement: AcademyProductionRequirement,
209 status: AcademyCheckStatus,
211 },
212}
213
214#[derive(Debug, Clone, Copy, Default)]
216pub struct AcademyProductionPreset;
217
218impl AcademyProductionPreset {
219 pub const fn middleware_order(&self) -> &'static [ProductionMiddlewareStage] {
221 ProductionPreset::middleware_order()
222 }
223
224 pub fn assess(
228 &self,
229 observations: impl IntoIterator<Item = (AcademyProductionRequirement, AcademyCheckStatus)>,
230 ) -> Result<Vec<AcademyBoundaryAssessment>, AcademyPresetError> {
231 let observations = observations.into_iter().collect::<Vec<_>>();
232 for (index, (requirement, _)) in observations.iter().enumerate() {
233 if observations[index + 1..]
234 .iter()
235 .any(|(candidate, _)| candidate == requirement)
236 {
237 return Err(AcademyPresetError::DuplicateRequirement(*requirement));
238 }
239 }
240
241 Ok(AcademyProductionRequirement::ALL
242 .iter()
243 .map(|requirement| AcademyBoundaryAssessment {
244 requirement: *requirement,
245 status: observations
246 .iter()
247 .find_map(|(candidate, status)| (candidate == requirement).then_some(*status))
248 .unwrap_or(AcademyCheckStatus::NotEvaluated),
249 })
250 .collect())
251 }
252
253 pub fn validate(
255 &self,
256 observations: impl IntoIterator<Item = (AcademyProductionRequirement, AcademyCheckStatus)>,
257 ) -> Result<(), AcademyPresetError> {
258 for assessment in self.assess(observations)? {
259 if assessment.status != AcademyCheckStatus::Pass {
260 return Err(AcademyPresetError::RequirementNotPassed {
261 requirement: assessment.requirement,
262 status: assessment.status,
263 });
264 }
265 }
266 Ok(())
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::{
273 AcademyCheckStatus, AcademyPresetError, AcademyProductionRequirement,
274 ProductionMiddlewareStage, ProductionPreset,
275 };
276 use std::collections::HashSet;
277
278 #[test]
279 fn production_order_is_complete_unique_and_ends_at_the_handler() {
280 let order = ProductionPreset::middleware_order();
281 let unique = order.iter().copied().collect::<HashSet<_>>();
282
283 assert_eq!(order.len(), 14);
284 assert_eq!(unique.len(), order.len());
285 assert_eq!(
286 order.first(),
287 Some(&ProductionMiddlewareStage::TrustedProxy)
288 );
289 assert_eq!(order.last(), Some(&ProductionMiddlewareStage::Handler));
290 }
291
292 #[test]
293 fn identity_is_validated_before_tenant_authorization_and_rate_limit() {
294 let order = ProductionPreset::middleware_order();
295 let expected_identity_order = [
296 ProductionMiddlewareStage::Session,
297 ProductionMiddlewareStage::Authentication,
298 ProductionMiddlewareStage::Tenant,
299 ProductionMiddlewareStage::Authorization,
300 ProductionMiddlewareStage::RateLimit,
301 ];
302 let observed_identity_order = order
303 .iter()
304 .copied()
305 .filter(|stage| expected_identity_order.contains(stage))
306 .collect::<Vec<_>>();
307
308 assert_eq!(observed_identity_order, expected_identity_order);
309 }
310
311 #[test]
312 fn academy_preset_defaults_to_not_evaluated_and_fails_closed() -> Result<(), AcademyPresetError>
313 {
314 let preset = ProductionPreset::academy();
315 let assessments = preset.assess([])?;
316
317 assert_eq!(
318 preset.middleware_order(),
319 ProductionPreset::middleware_order()
320 );
321 assert_eq!(assessments.len(), AcademyProductionRequirement::ALL.len());
322 assert!(
323 assessments
324 .iter()
325 .all(|assessment| assessment.status() == AcademyCheckStatus::NotEvaluated)
326 );
327 assert!(matches!(
328 preset.validate([]),
329 Err(AcademyPresetError::RequirementNotPassed {
330 status: AcademyCheckStatus::NotEvaluated,
331 ..
332 })
333 ));
334 Ok(())
335 }
336
337 #[test]
338 fn academy_preset_requires_one_explicit_pass_per_requirement() {
339 let preset = ProductionPreset::academy();
340 let passes = AcademyProductionRequirement::ALL
341 .iter()
342 .copied()
343 .map(|requirement| (requirement, AcademyCheckStatus::Pass));
344 assert_eq!(preset.validate(passes), Ok(()));
345
346 assert!(matches!(
347 preset.validate([
348 (
349 AcademyProductionRequirement::AuthenticatedIdentity,
350 AcademyCheckStatus::Pass,
351 ),
352 (
353 AcademyProductionRequirement::AuthenticatedIdentity,
354 AcademyCheckStatus::Fail,
355 ),
356 ]),
357 Err(AcademyPresetError::DuplicateRequirement(
358 AcademyProductionRequirement::AuthenticatedIdentity
359 ))
360 ));
361 assert_eq!(AcademyCheckStatus::Skipped.as_str(), "SKIPPED");
362 }
363}