http_security_headers/
config.rs1use crate::error::{Error, Result};
6use crate::policy::*;
7
8#[derive(Debug, Clone)]
27pub struct SecurityHeaders {
28 pub(crate) content_security_policy: Option<ContentSecurityPolicy>,
29 pub(crate) strict_transport_security: Option<StrictTransportSecurity>,
30 pub(crate) x_frame_options: Option<XFrameOptions>,
31 pub(crate) x_content_type_options: bool,
32 pub(crate) referrer_policy: Option<ReferrerPolicy>,
33 pub(crate) cross_origin_opener_policy: Option<CrossOriginOpenerPolicy>,
34 pub(crate) cross_origin_embedder_policy: Option<CrossOriginEmbedderPolicy>,
35 pub(crate) cross_origin_resource_policy: Option<CrossOriginResourcePolicy>,
36}
37
38impl SecurityHeaders {
39 pub fn builder() -> SecurityHeadersBuilder {
41 SecurityHeadersBuilder::default()
42 }
43
44 pub fn content_security_policy(&self) -> Option<&ContentSecurityPolicy> {
46 self.content_security_policy.as_ref()
47 }
48
49 pub fn strict_transport_security(&self) -> Option<&StrictTransportSecurity> {
51 self.strict_transport_security.as_ref()
52 }
53
54 pub fn x_frame_options(&self) -> Option<XFrameOptions> {
56 self.x_frame_options
57 }
58
59 pub fn x_content_type_options_enabled(&self) -> bool {
61 self.x_content_type_options
62 }
63
64 pub fn referrer_policy(&self) -> Option<ReferrerPolicy> {
66 self.referrer_policy
67 }
68
69 pub fn cross_origin_opener_policy(&self) -> Option<CrossOriginOpenerPolicy> {
71 self.cross_origin_opener_policy
72 }
73
74 pub fn cross_origin_embedder_policy(&self) -> Option<CrossOriginEmbedderPolicy> {
76 self.cross_origin_embedder_policy
77 }
78
79 pub fn cross_origin_resource_policy(&self) -> Option<CrossOriginResourcePolicy> {
81 self.cross_origin_resource_policy
82 }
83}
84
85#[derive(Debug, Default)]
89pub struct SecurityHeadersBuilder {
90 content_security_policy: Option<ContentSecurityPolicy>,
91 strict_transport_security: Option<StrictTransportSecurity>,
92 x_frame_options: Option<XFrameOptions>,
93 x_content_type_options: bool,
94 referrer_policy: Option<ReferrerPolicy>,
95 cross_origin_opener_policy: Option<CrossOriginOpenerPolicy>,
96 cross_origin_embedder_policy: Option<CrossOriginEmbedderPolicy>,
97 cross_origin_resource_policy: Option<CrossOriginResourcePolicy>,
98}
99
100impl SecurityHeadersBuilder {
101 pub fn content_security_policy(mut self, policy: ContentSecurityPolicy) -> Self {
118 self.content_security_policy = Some(policy);
119 self
120 }
121
122 pub fn strict_transport_security(
142 mut self,
143 max_age: std::time::Duration,
144 include_subdomains: bool,
145 preload: bool,
146 ) -> Self {
147 let mut hsts = StrictTransportSecurity::new(max_age);
148 if include_subdomains {
149 hsts = hsts.include_subdomains(true);
150 }
151 if preload {
152 hsts = hsts.preload(true);
153 }
154 self.strict_transport_security = Some(hsts);
155 self
156 }
157
158 pub fn strict_transport_security_policy(mut self, policy: StrictTransportSecurity) -> Self {
160 self.strict_transport_security = Some(policy);
161 self
162 }
163
164 pub fn x_frame_options_deny(mut self) -> Self {
177 self.x_frame_options = Some(XFrameOptions::Deny);
178 self
179 }
180
181 pub fn x_frame_options_sameorigin(mut self) -> Self {
194 self.x_frame_options = Some(XFrameOptions::SameOrigin);
195 self
196 }
197
198 pub fn x_frame_options(mut self, policy: XFrameOptions) -> Self {
200 self.x_frame_options = Some(policy);
201 self
202 }
203
204 pub fn x_content_type_options_nosniff(mut self) -> Self {
219 self.x_content_type_options = true;
220 self
221 }
222
223 pub fn referrer_policy(mut self, policy: ReferrerPolicy) -> Self {
225 self.referrer_policy = Some(policy);
226 self
227 }
228
229 pub fn referrer_policy_no_referrer(mut self) -> Self {
231 self.referrer_policy = Some(ReferrerPolicy::NoReferrer);
232 self
233 }
234
235 pub fn referrer_policy_strict_origin_when_cross_origin(mut self) -> Self {
237 self.referrer_policy = Some(ReferrerPolicy::StrictOriginWhenCrossOrigin);
238 self
239 }
240
241 pub fn cross_origin_opener_policy(mut self, policy: CrossOriginOpenerPolicy) -> Self {
243 self.cross_origin_opener_policy = Some(policy);
244 self
245 }
246
247 pub fn cross_origin_embedder_policy(mut self, policy: CrossOriginEmbedderPolicy) -> Self {
249 self.cross_origin_embedder_policy = Some(policy);
250 self
251 }
252
253 pub fn cross_origin_resource_policy(mut self, policy: CrossOriginResourcePolicy) -> Self {
255 self.cross_origin_resource_policy = Some(policy);
256 self
257 }
258
259 pub fn build(self) -> Result<SecurityHeaders> {
265 if let Some(csp) = &self.content_security_policy {
266 csp.to_header_value()?;
267 }
268
269 if self.content_security_policy.is_none()
271 && self.strict_transport_security.is_none()
272 && self.x_frame_options.is_none()
273 && !self.x_content_type_options
274 && self.referrer_policy.is_none()
275 && self.cross_origin_opener_policy.is_none()
276 && self.cross_origin_embedder_policy.is_none()
277 && self.cross_origin_resource_policy.is_none()
278 {
279 return Err(Error::ValidationFailed(
280 "At least one security header must be configured".to_string(),
281 ));
282 }
283
284 Ok(SecurityHeaders {
285 content_security_policy: self.content_security_policy,
286 strict_transport_security: self.strict_transport_security,
287 x_frame_options: self.x_frame_options,
288 x_content_type_options: self.x_content_type_options,
289 referrer_policy: self.referrer_policy,
290 cross_origin_opener_policy: self.cross_origin_opener_policy,
291 cross_origin_embedder_policy: self.cross_origin_embedder_policy,
292 cross_origin_resource_policy: self.cross_origin_resource_policy,
293 })
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use std::time::Duration;
301
302 #[test]
303 fn test_builder_empty_fails() {
304 let result = SecurityHeaders::builder().build();
305 assert!(result.is_err());
306 }
307
308 #[test]
309 fn test_builder_with_hsts() {
310 let headers = SecurityHeaders::builder()
311 .strict_transport_security(Duration::from_secs(31536000), true, false)
312 .build()
313 .unwrap();
314
315 assert!(headers.strict_transport_security().is_some());
316 let hsts = headers.strict_transport_security().unwrap();
317 assert_eq!(hsts.max_age(), Duration::from_secs(31536000));
318 assert!(hsts.includes_subdomains());
319 assert!(!hsts.is_preload());
320 }
321
322 #[test]
323 fn test_builder_with_frame_options() {
324 let headers = SecurityHeaders::builder()
325 .x_frame_options_deny()
326 .build()
327 .unwrap();
328
329 assert_eq!(headers.x_frame_options(), Some(XFrameOptions::Deny));
330 }
331
332 #[test]
333 fn test_builder_with_referrer_policy() {
334 let headers = SecurityHeaders::builder()
335 .referrer_policy_no_referrer()
336 .build()
337 .unwrap();
338
339 assert_eq!(headers.referrer_policy(), Some(ReferrerPolicy::NoReferrer));
340 }
341
342 #[test]
343 fn test_builder_with_multiple_headers() {
344 let csp = ContentSecurityPolicy::new().default_src(vec!["'self'"]);
345
346 let headers = SecurityHeaders::builder()
347 .content_security_policy(csp)
348 .strict_transport_security(Duration::from_secs(31536000), true, false)
349 .x_frame_options_deny()
350 .x_content_type_options_nosniff()
351 .referrer_policy_no_referrer()
352 .cross_origin_opener_policy(CrossOriginOpenerPolicy::SameOrigin)
353 .cross_origin_embedder_policy(CrossOriginEmbedderPolicy::RequireCorp)
354 .cross_origin_resource_policy(CrossOriginResourcePolicy::SameOrigin)
355 .build()
356 .unwrap();
357
358 assert!(headers.content_security_policy().is_some());
359 assert!(headers.strict_transport_security().is_some());
360 assert!(headers.x_frame_options().is_some());
361 assert!(headers.x_content_type_options_enabled());
362 assert!(headers.referrer_policy().is_some());
363 assert!(headers.cross_origin_opener_policy().is_some());
364 assert!(headers.cross_origin_embedder_policy().is_some());
365 assert!(headers.cross_origin_resource_policy().is_some());
366 }
367
368 #[test]
369 fn test_builder_with_empty_csp_fails() {
370 let result = SecurityHeaders::builder()
371 .content_security_policy(ContentSecurityPolicy::new())
372 .build();
373
374 assert!(result.is_err());
375 }
376}