cratestack_axum/envelope_layer/seal_policy.rs
1//! [`ResponseSealPolicy`]: under `Optional`, whether an unsigned request's
2//! response is sealed (maintainer decision D10).
3
4use http::{HeaderMap, Method};
5
6use super::resolver::ResolvedRoute;
7
8/// An unsigned request under `Optional` that carries a valid
9/// `Cratestack-Nonce`, as a [`ResponseSealPolicy`] sees it.
10#[derive(Debug)]
11pub struct UnsignedRequest<'a> {
12 method: &'a Method,
13 route: &'a ResolvedRoute,
14 headers: &'a HeaderMap,
15 accept_names_envelope: bool,
16}
17
18impl<'a> UnsignedRequest<'a> {
19 /// A request as the layer describes it; `accept_names_envelope` is the
20 /// layer's own reading of `headers`. Public so a policy can be
21 /// unit-tested (unlike [`super::VerifiedRequest`], nothing here claims a
22 /// verification happened).
23 pub fn new(
24 method: &'a Method,
25 route: &'a ResolvedRoute,
26 headers: &'a HeaderMap,
27 accept_names_envelope: bool,
28 ) -> Self {
29 Self {
30 method,
31 route,
32 headers,
33 accept_names_envelope,
34 }
35 }
36
37 /// The request method.
38 pub fn method(&self) -> &'a Method {
39 self.method
40 }
41
42 /// The op it addresses.
43 pub fn route(&self) -> &'a ResolvedRoute {
44 self.route
45 }
46
47 /// Its headers.
48 pub fn headers(&self) -> &'a HeaderMap {
49 self.headers
50 }
51
52 /// Whether an `Accept` entry with a non-zero weight names
53 /// `application/cose` or the envelope's own media type.
54 pub fn accept_names_envelope(&self) -> bool {
55 self.accept_names_envelope
56 }
57}
58
59/// Decides whether the response to an unsigned request is sealed, under
60/// `Optional`. The default is [`AcceptNamesEnvelope`] (D10). A closure is a
61/// policy (second-review nit):
62///
63/// ```
64/// use cratestack_axum::envelope_layer::{EnvelopeLayerBuilder, UnsignedRequest};
65///
66/// fn reads_only(builder: EnvelopeLayerBuilder) -> EnvelopeLayerBuilder {
67/// builder.response_seal_policy(|r: &UnsignedRequest<'_>| {
68/// r.accept_names_envelope() && r.method().as_str() == "GET"
69/// })
70/// }
71/// # let _ = reads_only;
72/// ```
73///
74/// Such a seal binds the client's `Cratestack-Nonce` and the request
75/// payload, and nothing about the caller: the request was not signed, so
76/// the response proves only that this server answered this nonce and body,
77/// not who asked. A client must not read it as an authenticated exchange.
78///
79/// **What the layer enforces whatever this returns:** it is asked only
80/// under `Optional`, only about an unsigned request, and only when that
81/// request carries a well-formed `Cratestack-Nonce` (without one the
82/// response could not be bound to this request, and a cached one could be
83/// replayed for the next). A signed request's response is always sealed,
84/// and under `Required` every response is: neither is this policy's call.
85pub trait ResponseSealPolicy: Send + Sync + 'static {
86 /// `true` to seal this request's response.
87 fn seal_unsigned(&self, request: &UnsignedRequest<'_>) -> bool;
88}
89
90/// Seal when the request's `Accept` names `application/cose` (D10).
91#[derive(Debug, Clone, Copy, Default)]
92pub struct AcceptNamesEnvelope;
93
94impl ResponseSealPolicy for AcceptNamesEnvelope {
95 fn seal_unsigned(&self, request: &UnsignedRequest<'_>) -> bool {
96 request.accept_names_envelope
97 }
98}
99
100impl<F> ResponseSealPolicy for F
101where
102 F: Fn(&UnsignedRequest<'_>) -> bool + Send + Sync + 'static,
103{
104 fn seal_unsigned(&self, request: &UnsignedRequest<'_>) -> bool {
105 self(request)
106 }
107}