Skip to main content

ocre/security/
policy.rs

1//! `Content-Security-Policy` and `Permissions-Policy` headers, as tower layers.
2
3use std::{
4    convert::Infallible,
5    fmt,
6    future::Future,
7    pin::Pin,
8    sync::Arc,
9    task::{Context, Poll},
10};
11
12use axum::{
13    extract::{FromRequestParts, Request},
14    http::{HeaderName, HeaderValue, header, request::Parts},
15    response::Response,
16};
17use base64::{Engine as _, engine::general_purpose::STANDARD};
18use tower_layer::Layer;
19use tower_service::Service;
20
21use crate::{
22    Error,
23    session::{Rejection, reject},
24};
25
26/// `'self'`: the app's own origin, in a [`ContentSecurityPolicy`] or [`PermissionsPolicy`] source list.
27///
28/// # Examples
29///
30/// ```
31/// use ocre::security::{ContentSecurityPolicy, SELF};
32///
33/// assert_eq!(ContentSecurityPolicy::new().default_src(&[SELF]).header_value(None), "default-src 'self'");
34/// ```
35pub const SELF: &str = "'self'";
36/// `'none'`: nothing is allowed, in a [`ContentSecurityPolicy`] source list.
37///
38/// # Examples
39///
40/// ```
41/// use ocre::security::{ContentSecurityPolicy, NONE};
42///
43/// assert_eq!(ContentSecurityPolicy::new().object_src(&[NONE]).header_value(None), "object-src 'none'");
44/// ```
45pub const NONE: &str = "'none'";
46/// Placeholder for the request's nonce: sent as `'nonce-<value>'` (Rails' `content_security_policy_nonce`).
47///
48/// Put it in `script_src` or `style_src`; the layer generates a new random
49/// nonce per request and handlers get it with the [`CspNonce`] extractor, to
50/// write `<script nonce="{{ nonce }}">`.
51///
52/// # Examples
53///
54/// ```
55/// use ocre::security::{ContentSecurityPolicy, NONCE, SELF};
56///
57/// let csp = ContentSecurityPolicy::new().script_src(&[SELF, NONCE]);
58/// assert_eq!(csp.header_value(Some("abc")), "script-src 'self' 'nonce-abc'");
59/// ```
60pub const NONCE: &str = "'nonce'";
61/// `'unsafe-inline'`: allows inline `<style>`/`<script>` and `style=` attributes. Avoid it for scripts.
62///
63/// # Examples
64///
65/// ```
66/// assert_eq!(ocre::security::UNSAFE_INLINE, "'unsafe-inline'");
67/// ```
68pub const UNSAFE_INLINE: &str = "'unsafe-inline'";
69/// `'unsafe-eval'`: allows `eval` and `new Function` (htmx's `hx-on` and `js:` need it). Avoid it.
70///
71/// # Examples
72///
73/// ```
74/// assert_eq!(ocre::security::UNSAFE_EVAL, "'unsafe-eval'");
75/// ```
76pub const UNSAFE_EVAL: &str = "'unsafe-eval'";
77/// `'strict-dynamic'`: scripts loaded by a nonced script are trusted too.
78///
79/// # Examples
80///
81/// ```
82/// assert_eq!(ocre::security::STRICT_DYNAMIC, "'strict-dynamic'");
83/// ```
84pub const STRICT_DYNAMIC: &str = "'strict-dynamic'";
85/// `data:` URLs (inline images, fonts).
86///
87/// # Examples
88///
89/// ```
90/// assert_eq!(ocre::security::DATA, "data:");
91/// ```
92pub const DATA: &str = "data:";
93/// `blob:` URLs (files built in the browser).
94///
95/// # Examples
96///
97/// ```
98/// assert_eq!(ocre::security::BLOB, "blob:");
99/// ```
100pub const BLOB: &str = "blob:";
101/// Any `https:` URL.
102///
103/// # Examples
104///
105/// ```
106/// assert_eq!(ocre::security::HTTPS, "https:");
107/// ```
108pub const HTTPS: &str = "https:";
109
110/// A `Content-Security-Policy` header, built directive by directive, and the layer that sends it.
111///
112/// Rails' `content_security_policy` initializer as plain Rust: build the
113/// policy in `src/lib.rs` and add it to the router with `.layer(policy)`
114/// (generated apps do, in `content_security_policy()`). Every response gets
115/// the header unless the handler (or a layer closer to it) set one already,
116/// which is how routes override the global policy: give a nested router its
117/// own `.layer(...)`, or return the header from the handler.
118///
119/// Sources are written as in the header: [`SELF`], [`NONE`], [`DATA`],
120/// `"https://unpkg.com"`, ... [`NONCE`] stands for the request's random nonce
121/// (see [`CspNonce`]). Calling a directive twice replaces it.
122/// [`report_only`](Self::report_only) sends
123/// `Content-Security-Policy-Report-Only` instead, to try a policy without
124/// breaking pages; [`report_uri`](Self::report_uri) and
125/// [`report_to`](Self::report_to) collect violations.
126///
127/// # Free plan
128///
129/// A header per response and, when the policy uses [`NONCE`], 16 random
130/// bytes per request: no binding call, microseconds of CPU.
131///
132/// # Examples
133///
134/// ```
135/// use axum::{Router, routing::get};
136/// use ocre::security::{ContentSecurityPolicy, DATA, NONCE, NONE, SELF};
137///
138/// let policy = ContentSecurityPolicy::new()
139///     .default_src(&[SELF])
140///     .script_src(&[SELF, NONCE, "https://unpkg.com"])
141///     .img_src(&[SELF, DATA])
142///     .object_src(&[NONE])
143///     .report_uri("/csp-reports");
144/// assert_eq!(
145///     policy.header_value(Some("r4nd0m")),
146///     "default-src 'self'; script-src 'self' 'nonce-r4nd0m' https://unpkg.com; img-src 'self' data:; \
147///      object-src 'none'; report-uri /csp-reports"
148/// );
149///
150/// let app: Router = Router::new().route("/", get(|| async { "home" })).layer(policy);
151/// # let _ = app;
152/// ```
153#[derive(Debug, Clone, Default, PartialEq, Eq)]
154pub struct ContentSecurityPolicy {
155    directives: Vec<(String, Vec<String>)>,
156    report_only: bool,
157}
158
159impl ContentSecurityPolicy {
160    /// An empty policy: add directives with the builder methods.
161    ///
162    /// # Examples
163    ///
164    /// ```
165    /// assert_eq!(ocre::security::ContentSecurityPolicy::new().header_value(None), "");
166    /// ```
167    pub fn new() -> Self {
168        Self::default()
169    }
170
171    /// Sets any directive, e.g. `directive("sandbox", &["allow-forms"])`; replaces a previous value.
172    ///
173    /// An empty `sources` list writes the directive alone
174    /// (`upgrade-insecure-requests`).
175    ///
176    /// # Examples
177    ///
178    /// ```
179    /// use ocre::security::{ContentSecurityPolicy, SELF};
180    ///
181    /// let csp = ContentSecurityPolicy::new().directive("worker-src", &[SELF]).directive("worker-src", &["blob:"]);
182    /// assert_eq!(csp.header_value(None), "worker-src blob:");
183    /// ```
184    #[must_use]
185    pub fn directive(mut self, name: &str, sources: &[&str]) -> Self {
186        let sources = sources.iter().map(|source| (*source).to_owned()).collect();
187        match self.directives.iter_mut().find(|(existing, _)| existing == name) {
188            Some((_, existing)) => *existing = sources,
189            None => self.directives.push((name.to_owned(), sources)),
190        }
191        self
192    }
193
194    /// `default-src`: the fallback for every fetch directive not set.
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// use ocre::security::{ContentSecurityPolicy, SELF};
200    ///
201    /// assert_eq!(ContentSecurityPolicy::new().default_src(&[SELF]).header_value(None), "default-src 'self'");
202    /// ```
203    #[must_use]
204    pub fn default_src(self, sources: &[&str]) -> Self {
205        self.directive("default-src", sources)
206    }
207
208    /// `script-src`: where scripts may come from.
209    ///
210    /// # Examples
211    ///
212    /// ```
213    /// use ocre::security::{ContentSecurityPolicy, SELF};
214    ///
215    /// let csp = ContentSecurityPolicy::new().script_src(&[SELF, "https://unpkg.com"]);
216    /// assert_eq!(csp.header_value(None), "script-src 'self' https://unpkg.com");
217    /// ```
218    #[must_use]
219    pub fn script_src(self, sources: &[&str]) -> Self {
220        self.directive("script-src", sources)
221    }
222
223    /// `style-src`: where stylesheets and inline styles may come from.
224    ///
225    /// # Examples
226    ///
227    /// ```
228    /// use ocre::security::{ContentSecurityPolicy, SELF, UNSAFE_INLINE};
229    ///
230    /// let csp = ContentSecurityPolicy::new().style_src(&[SELF, UNSAFE_INLINE]);
231    /// assert_eq!(csp.header_value(None), "style-src 'self' 'unsafe-inline'");
232    /// ```
233    #[must_use]
234    pub fn style_src(self, sources: &[&str]) -> Self {
235        self.directive("style-src", sources)
236    }
237
238    /// `img-src`: images and favicons.
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// use ocre::security::{ContentSecurityPolicy, DATA, SELF};
244    ///
245    /// assert_eq!(ContentSecurityPolicy::new().img_src(&[SELF, DATA]).header_value(None), "img-src 'self' data:");
246    /// ```
247    #[must_use]
248    pub fn img_src(self, sources: &[&str]) -> Self {
249        self.directive("img-src", sources)
250    }
251
252    /// `font-src`: web fonts.
253    ///
254    /// # Examples
255    ///
256    /// ```
257    /// use ocre::security::{ContentSecurityPolicy, SELF};
258    ///
259    /// assert_eq!(ContentSecurityPolicy::new().font_src(&[SELF]).header_value(None), "font-src 'self'");
260    /// ```
261    #[must_use]
262    pub fn font_src(self, sources: &[&str]) -> Self {
263        self.directive("font-src", sources)
264    }
265
266    /// `connect-src`: `fetch`, XHR (htmx requests), WebSockets and `EventSource`.
267    ///
268    /// # Examples
269    ///
270    /// ```
271    /// use ocre::security::{ContentSecurityPolicy, SELF};
272    ///
273    /// assert_eq!(ContentSecurityPolicy::new().connect_src(&[SELF]).header_value(None), "connect-src 'self'");
274    /// ```
275    #[must_use]
276    pub fn connect_src(self, sources: &[&str]) -> Self {
277        self.directive("connect-src", sources)
278    }
279
280    /// `media-src`: `<audio>` and `<video>`.
281    ///
282    /// # Examples
283    ///
284    /// ```
285    /// use ocre::security::{ContentSecurityPolicy, SELF};
286    ///
287    /// assert_eq!(ContentSecurityPolicy::new().media_src(&[SELF]).header_value(None), "media-src 'self'");
288    /// ```
289    #[must_use]
290    pub fn media_src(self, sources: &[&str]) -> Self {
291        self.directive("media-src", sources)
292    }
293
294    /// `object-src`: `<object>` and `<embed>`; set it to [`NONE`].
295    ///
296    /// # Examples
297    ///
298    /// ```
299    /// use ocre::security::{ContentSecurityPolicy, NONE};
300    ///
301    /// assert_eq!(ContentSecurityPolicy::new().object_src(&[NONE]).header_value(None), "object-src 'none'");
302    /// ```
303    #[must_use]
304    pub fn object_src(self, sources: &[&str]) -> Self {
305        self.directive("object-src", sources)
306    }
307
308    /// `frame-src`: pages this app may put in an `<iframe>`.
309    ///
310    /// # Examples
311    ///
312    /// ```
313    /// use ocre::security::ContentSecurityPolicy;
314    ///
315    /// let csp = ContentSecurityPolicy::new().frame_src(&["https://www.youtube-nocookie.com"]);
316    /// assert_eq!(csp.header_value(None), "frame-src https://www.youtube-nocookie.com");
317    /// ```
318    #[must_use]
319    pub fn frame_src(self, sources: &[&str]) -> Self {
320        self.directive("frame-src", sources)
321    }
322
323    /// `frame-ancestors`: sites that may put this app in a frame (the modern `X-Frame-Options`).
324    ///
325    /// # Examples
326    ///
327    /// ```
328    /// use ocre::security::{ContentSecurityPolicy, SELF};
329    ///
330    /// assert_eq!(ContentSecurityPolicy::new().frame_ancestors(&[SELF]).header_value(None), "frame-ancestors 'self'");
331    /// ```
332    #[must_use]
333    pub fn frame_ancestors(self, sources: &[&str]) -> Self {
334        self.directive("frame-ancestors", sources)
335    }
336
337    /// `form-action`: where forms may be submitted.
338    ///
339    /// # Examples
340    ///
341    /// ```
342    /// use ocre::security::{ContentSecurityPolicy, SELF};
343    ///
344    /// assert_eq!(ContentSecurityPolicy::new().form_action(&[SELF]).header_value(None), "form-action 'self'");
345    /// ```
346    #[must_use]
347    pub fn form_action(self, sources: &[&str]) -> Self {
348        self.directive("form-action", sources)
349    }
350
351    /// `base-uri`: allowed `<base href>` values.
352    ///
353    /// # Examples
354    ///
355    /// ```
356    /// use ocre::security::{ContentSecurityPolicy, SELF};
357    ///
358    /// assert_eq!(ContentSecurityPolicy::new().base_uri(&[SELF]).header_value(None), "base-uri 'self'");
359    /// ```
360    #[must_use]
361    pub fn base_uri(self, sources: &[&str]) -> Self {
362        self.directive("base-uri", sources)
363    }
364
365    /// `upgrade-insecure-requests`: browsers load `http:` resources over HTTPS.
366    ///
367    /// # Examples
368    ///
369    /// ```
370    /// use ocre::security::ContentSecurityPolicy;
371    ///
372    /// let csp = ContentSecurityPolicy::new().upgrade_insecure_requests();
373    /// assert_eq!(csp.header_value(None), "upgrade-insecure-requests");
374    /// ```
375    #[must_use]
376    pub fn upgrade_insecure_requests(self) -> Self {
377        self.directive("upgrade-insecure-requests", &[])
378    }
379
380    /// `report-uri`: where browsers POST violation reports (JSON), e.g. a route of the app.
381    ///
382    /// # Examples
383    ///
384    /// ```
385    /// use ocre::security::ContentSecurityPolicy;
386    ///
387    /// let csp = ContentSecurityPolicy::new().report_uri("/csp-reports");
388    /// assert_eq!(csp.header_value(None), "report-uri /csp-reports");
389    /// ```
390    #[must_use]
391    pub fn report_uri(self, uri: &str) -> Self {
392        self.directive("report-uri", &[uri])
393    }
394
395    /// `report-to`: the `Reporting-Endpoints` group violation reports go to.
396    ///
397    /// Send the `Reporting-Endpoints: csp="/csp-reports"` header too
398    /// (browsers without Reporting API support use [`report_uri`](Self::report_uri)).
399    ///
400    /// # Examples
401    ///
402    /// ```
403    /// use ocre::security::ContentSecurityPolicy;
404    ///
405    /// assert_eq!(ContentSecurityPolicy::new().report_to("csp").header_value(None), "report-to csp");
406    /// ```
407    #[must_use]
408    pub fn report_to(self, group: &str) -> Self {
409        self.directive("report-to", &[group])
410    }
411
412    /// Sends `Content-Security-Policy-Report-Only`: browsers report violations but block nothing.
413    ///
414    /// # Examples
415    ///
416    /// ```
417    /// use ocre::security::{ContentSecurityPolicy, SELF};
418    ///
419    /// let csp = ContentSecurityPolicy::new().default_src(&[SELF]).report_only();
420    /// assert_eq!(csp.header_name(), "content-security-policy-report-only");
421    /// ```
422    #[must_use]
423    pub fn report_only(mut self) -> Self {
424        self.report_only = true;
425        self
426    }
427
428    /// `content-security-policy`, or `content-security-policy-report-only` after
429    /// [`report_only`](Self::report_only).
430    ///
431    /// # Examples
432    ///
433    /// ```
434    /// assert_eq!(ocre::security::ContentSecurityPolicy::new().header_name(), "content-security-policy");
435    /// ```
436    pub fn header_name(&self) -> HeaderName {
437        if self.report_only { header::CONTENT_SECURITY_POLICY_REPORT_ONLY } else { header::CONTENT_SECURITY_POLICY }
438    }
439
440    /// The header value, with [`NONCE`] replaced by `'nonce-<nonce>'` (and dropped when `nonce` is `None`).
441    ///
442    /// # Examples
443    ///
444    /// ```
445    /// use ocre::security::{ContentSecurityPolicy, NONCE, SELF};
446    ///
447    /// let csp = ContentSecurityPolicy::new().default_src(&[SELF]).script_src(&[SELF, NONCE]);
448    /// assert_eq!(csp.header_value(Some("n0nce")), "default-src 'self'; script-src 'self' 'nonce-n0nce'");
449    /// assert_eq!(csp.header_value(None), "default-src 'self'; script-src 'self'");
450    /// ```
451    pub fn header_value(&self, nonce: Option<&str>) -> String {
452        let mut value = String::new();
453        for (name, sources) in &self.directives {
454            if !value.is_empty() {
455                value.push_str("; ");
456            }
457            value.push_str(name);
458            for source in sources {
459                if source == NONCE {
460                    let Some(nonce) = nonce else { continue };
461                    value.push_str(" 'nonce-");
462                    value.push_str(nonce);
463                    value.push('\'');
464                } else {
465                    value.push(' ');
466                    value.push_str(source);
467                }
468            }
469        }
470        value
471    }
472
473    fn uses_nonce(&self) -> bool {
474        self.directives.iter().any(|(_, sources)| sources.iter().any(|source| source == NONCE))
475    }
476}
477
478/// The request's Content-Security-Policy nonce, as an extractor (Rails' `content_security_policy_nonce`).
479///
480/// A new random value (16 bytes, base64) per request, created by the
481/// [`ContentSecurityPolicy`] layer and sent as `'nonce-<value>'` wherever the
482/// policy lists [`NONCE`]. Pass it to the template and write
483/// `<script nonce="{{ nonce }}">`; `<meta name="csp-nonce" content="{{ nonce }}">`
484/// exposes it to JavaScript (Rails' `csp_meta_tag`). Rejects with
485/// [`Error::Internal`] when no policy layer wraps the route.
486///
487/// # Examples
488///
489/// ```no_run
490/// use axum::response::Html;
491/// use ocre::security::CspNonce;
492///
493/// async fn page(nonce: CspNonce) -> Html<String> {
494///     Html(format!(r#"<script nonce="{nonce}">console.log("allowed")</script>"#))
495/// }
496/// # let _ = page;
497/// ```
498#[derive(Debug, Clone, PartialEq, Eq)]
499pub struct CspNonce(String);
500
501impl CspNonce {
502    fn generate() -> Self {
503        Self(STANDARD.encode(crate::token::random_bytes::<16>()))
504    }
505
506    /// The nonce value, without the `'nonce-...'` wrapper.
507    ///
508    /// # Examples
509    ///
510    /// ```no_run
511    /// async fn page(nonce: ocre::security::CspNonce) -> String {
512    ///     nonce.as_str().to_owned()
513    /// }
514    /// # let _ = page;
515    /// ```
516    pub fn as_str(&self) -> &str {
517        &self.0
518    }
519}
520
521impl fmt::Display for CspNonce {
522    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523        f.write_str(&self.0)
524    }
525}
526
527impl<S: Sync> FromRequestParts<S> for CspNonce {
528    type Rejection = Rejection;
529
530    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Rejection> {
531        parts.extensions.get::<CspNonce>().cloned().ok_or_else(|| {
532            reject(Error::internal(
533                "no CSP nonce on this request. Fix: add `.layer(content_security_policy())` (an \
534                 `ocre::security::ContentSecurityPolicy`) to the router serving this route",
535            ))
536        })
537    }
538}
539
540/// A `Permissions-Policy` header, built feature by feature, and the layer that sends it.
541///
542/// Rails' `permissions_policy` initializer: it turns browser features
543/// (camera, microphone, geolocation, ...) off for the app and for the frames
544/// it embeds. Add it with `.layer(policy)`; like [`ContentSecurityPolicy`],
545/// a handler's own header wins, so routes can override it.
546///
547/// In allowlists, [`SELF`] (or `"self"`) and `"*"` are keywords; other
548/// entries are origins, quoted in the header.
549///
550/// # Free plan
551///
552/// One header per response; no binding call.
553///
554/// # Examples
555///
556/// ```
557/// use axum::{Router, routing::get};
558/// use ocre::security::{PermissionsPolicy, SELF};
559///
560/// let policy = PermissionsPolicy::new()
561///     .deny(&["camera", "microphone", "geolocation"])
562///     .allow("fullscreen", &[SELF, "https://player.example"]);
563/// assert_eq!(
564///     policy.header_value(),
565///     r#"camera=(), microphone=(), geolocation=(), fullscreen=(self "https://player.example")"#
566/// );
567///
568/// let app: Router = Router::new().route("/", get(|| async { "home" })).layer(policy);
569/// # let _ = app;
570/// ```
571#[derive(Debug, Clone, Default, PartialEq, Eq)]
572pub struct PermissionsPolicy {
573    features: Vec<(String, Vec<String>)>,
574}
575
576impl PermissionsPolicy {
577    /// An empty policy: add features with [`allow`](Self::allow) and [`deny`](Self::deny).
578    ///
579    /// # Examples
580    ///
581    /// ```
582    /// assert_eq!(ocre::security::PermissionsPolicy::new().header_value(), "");
583    /// ```
584    pub fn new() -> Self {
585        Self::default()
586    }
587
588    /// Allows `feature` for the listed origins only; replaces a previous rule for it.
589    ///
590    /// # Examples
591    ///
592    /// ```
593    /// use ocre::security::{PermissionsPolicy, SELF};
594    ///
595    /// assert_eq!(PermissionsPolicy::new().allow("geolocation", &[SELF]).header_value(), "geolocation=(self)");
596    /// assert_eq!(PermissionsPolicy::new().allow("autoplay", &["*"]).header_value(), "autoplay=*");
597    /// ```
598    #[must_use]
599    pub fn allow(mut self, feature: &str, allowlist: &[&str]) -> Self {
600        let allowlist = allowlist
601            .iter()
602            .map(|origin| match *origin {
603                SELF | "self" => "self".to_owned(),
604                "*" => "*".to_owned(),
605                origin => format!("\"{}\"", origin.replace(['"', '\\'], "")),
606            })
607            .collect();
608        match self.features.iter_mut().find(|(existing, _)| existing == feature) {
609            Some((_, existing)) => *existing = allowlist,
610            None => self.features.push((feature.to_owned(), allowlist)),
611        }
612        self
613    }
614
615    /// Turns `features` off everywhere: `camera=()`.
616    ///
617    /// # Examples
618    ///
619    /// ```
620    /// use ocre::security::PermissionsPolicy;
621    ///
622    /// assert_eq!(PermissionsPolicy::new().deny(&["camera", "usb"]).header_value(), "camera=(), usb=()");
623    /// ```
624    #[must_use]
625    pub fn deny(self, features: &[&str]) -> Self {
626        features.iter().fold(self, |policy, feature| policy.allow(feature, &[]))
627    }
628
629    /// The header value.
630    ///
631    /// # Examples
632    ///
633    /// ```
634    /// let policy = ocre::security::PermissionsPolicy::new().deny(&["payment"]);
635    /// assert_eq!(policy.header_value(), "payment=()");
636    /// ```
637    pub fn header_value(&self) -> String {
638        let rules: Vec<String> = self
639            .features
640            .iter()
641            .map(|(feature, allowlist)| match allowlist.as_slice() {
642                [star] if star == "*" => format!("{feature}=*"),
643                _ => format!("{feature}=({})", allowlist.join(" ")),
644            })
645            .collect();
646        rules.join(", ")
647    }
648}
649
650mod sealed {
651    use axum::{
652        extract::Request,
653        http::{HeaderName, HeaderValue},
654    };
655
656    /// What a policy layer adds to a request and its response. Sealed:
657    /// implemented by [`ContentSecurityPolicy`](super::ContentSecurityPolicy)
658    /// and [`PermissionsPolicy`](super::PermissionsPolicy) only.
659    pub trait Policy: Send + Sync + 'static {
660        /// The header name and value for this request; may add extensions (the nonce).
661        fn prepare(&self, req: &mut Request) -> (HeaderName, HeaderValue);
662        /// Headers that, when the response already has one, mean the handler chose its own policy.
663        fn overridden_by(&self) -> [HeaderName; 2];
664    }
665}
666
667use sealed::Policy;
668
669impl Policy for ContentSecurityPolicy {
670    fn prepare(&self, req: &mut Request) -> (HeaderName, HeaderValue) {
671        let nonce = match req.extensions().get::<CspNonce>() {
672            Some(nonce) => nonce.clone(),
673            None => CspNonce::generate(),
674        };
675        let value = self.header_value(self.uses_nonce().then_some(nonce.as_str()));
676        req.extensions_mut().insert(nonce);
677        (self.header_name(), header_value(value))
678    }
679
680    fn overridden_by(&self) -> [HeaderName; 2] {
681        [header::CONTENT_SECURITY_POLICY, header::CONTENT_SECURITY_POLICY_REPORT_ONLY]
682    }
683}
684
685impl Policy for PermissionsPolicy {
686    fn prepare(&self, _req: &mut Request) -> (HeaderName, HeaderValue) {
687        (permissions_policy(), header_value(self.header_value()))
688    }
689
690    fn overridden_by(&self) -> [HeaderName; 2] {
691        [permissions_policy(), permissions_policy()]
692    }
693}
694
695fn permissions_policy() -> HeaderName {
696    HeaderName::from_static("permissions-policy")
697}
698
699/// Sources come from app code; characters a header cannot carry are dropped.
700fn header_value(value: String) -> HeaderValue {
701    HeaderValue::try_from(value).unwrap_or_else(|err| {
702        crate::error::log_internal(&format!("invalid security policy header, not sent: {err}"));
703        HeaderValue::from_static("")
704    })
705}
706
707impl<S> Layer<S> for ContentSecurityPolicy {
708    type Service = PolicyService<S, ContentSecurityPolicy>;
709
710    fn layer(&self, inner: S) -> Self::Service {
711        PolicyService { inner, policy: Arc::new(self.clone()) }
712    }
713}
714
715impl<S> Layer<S> for PermissionsPolicy {
716    type Service = PolicyService<S, PermissionsPolicy>;
717
718    fn layer(&self, inner: S) -> Self::Service {
719        PolicyService { inner, policy: Arc::new(self.clone()) }
720    }
721}
722
723/// The service a [`ContentSecurityPolicy`] or [`PermissionsPolicy`] layer wraps routes in.
724///
725/// Not used directly: `router.layer(policy)` builds it.
726///
727/// # Examples
728///
729/// ```
730/// use axum::{Router, routing::get};
731/// use ocre::security::{PermissionsPolicy, PolicyService};
732/// use tower_layer::Layer as _;
733///
734/// let router: Router = Router::new().route("/", get(|| async { "home" }));
735/// let service: PolicyService<Router, PermissionsPolicy> = PermissionsPolicy::new().layer(router);
736/// # let _ = service;
737/// ```
738pub struct PolicyService<S, P> {
739    inner: S,
740    policy: Arc<P>,
741}
742
743impl<S: Clone, P> Clone for PolicyService<S, P> {
744    fn clone(&self) -> Self {
745        Self { inner: self.inner.clone(), policy: Arc::clone(&self.policy) }
746    }
747}
748
749impl<S, P> Service<Request> for PolicyService<S, P>
750where
751    S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + 'static,
752    S::Future: Send + 'static,
753    P: Policy,
754{
755    type Response = Response;
756    type Error = Infallible;
757    type Future = Pin<Box<dyn Future<Output = Result<Response, Infallible>> + Send>>;
758
759    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Infallible>> {
760        self.inner.poll_ready(cx)
761    }
762
763    fn call(&mut self, mut req: Request) -> Self::Future {
764        let (name, value) = self.policy.prepare(&mut req);
765        let overridden_by = self.policy.overridden_by();
766        // The service polled ready is the one that must be called.
767        let clone = self.inner.clone();
768        let mut inner = std::mem::replace(&mut self.inner, clone);
769        Box::pin(async move {
770            let mut response = inner.call(req).await?;
771            let headers = response.headers_mut();
772            if !overridden_by.iter().any(|name| headers.contains_key(name)) && !value.is_empty() {
773                headers.insert(name, value);
774            }
775            Ok(response)
776        })
777    }
778}
779
780#[cfg(test)]
781#[path = "../../tests/security/policy.rs"]
782mod tests;