1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-06-26 10:27:42 +02:00

Add block_on_origin_mismatch option to middleware (#287)

Co-authored-by: CapableWeb <capableweb@domain.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
This commit is contained in:
CapableWeb
2022-09-22 01:22:20 +02:00
committed by GitHub
parent 82a100d96c
commit 3b5682c860
6 changed files with 107 additions and 16 deletions

View File

@ -102,6 +102,7 @@ impl Cors {
send_wildcard: false,
supports_credentials: true,
vary_header: true,
block_on_origin_mismatch: true,
};
Cors {
@ -448,6 +449,24 @@ impl Cors {
self
}
/// Configures whether requests should be pre-emptively blocked on mismatched origin.
///
/// If `true`, a 400 Bad Request is returned immediately when a request fails origin validation.
///
/// If `false`, the request will be processed as normal but relevant CORS headers will not be
/// appended to the response. In this case, the browser is trusted to validate CORS headers and
/// and block requests based on pre-flight requests. Use this setting to allow cURL and other
/// non-browser HTTP clients to function as normal, no matter what `Origin` the request has.
///
/// Defaults to `true`.
pub fn block_on_origin_mismatch(mut self, block: bool) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.block_on_origin_mismatch = block
}
self
}
}
impl Default for Cors {
@ -474,6 +493,7 @@ impl Default for Cors {
send_wildcard: false,
supports_credentials: false,
vary_header: true,
block_on_origin_mismatch: true,
};
Cors {

View File

@ -65,16 +65,19 @@ pub(crate) struct Inner {
pub(crate) send_wildcard: bool,
pub(crate) supports_credentials: bool,
pub(crate) vary_header: bool,
pub(crate) block_on_origin_mismatch: bool,
}
static EMPTY_ORIGIN_SET: Lazy<HashSet<HeaderValue>> = Lazy::new(HashSet::new);
impl Inner {
pub(crate) fn validate_origin(&self, req: &RequestHead) -> Result<(), CorsError> {
/// The bool returned in Ok(_) position indicates whether the `Access-Control-Allow-Origin`
/// header should be added to the response or not.
pub(crate) fn validate_origin(&self, req: &RequestHead) -> Result<bool, CorsError> {
// return early if all origins are allowed or get ref to allowed origins set
#[allow(clippy::mutable_key_type)]
let allowed_origins = match &self.allowed_origins {
AllOrSome::All if self.allowed_origins_fns.is_empty() => return Ok(()),
AllOrSome::All if self.allowed_origins_fns.is_empty() => return Ok(true),
AllOrSome::Some(allowed_origins) => allowed_origins,
// only function origin validators are defined
_ => &EMPTY_ORIGIN_SET,
@ -85,9 +88,11 @@ impl Inner {
// origin header exists and is a string
Some(origin) => {
if allowed_origins.contains(origin) || self.validate_origin_fns(origin, req) {
Ok(())
} else {
Ok(true)
} else if self.block_on_origin_mismatch {
Err(CorsError::OriginNotAllowed)
} else {
Ok(false)
}
}

View File

@ -16,7 +16,7 @@ use log::debug;
use crate::{
builder::intersperse_header_values,
inner::{add_vary_header, header_value_try_into_method},
AllOrSome, Inner,
AllOrSome, CorsError, Inner,
};
/// Service wrapper for Cross-Origin Resource Sharing support.
@ -60,9 +60,14 @@ impl<S> CorsMiddleware<S> {
fn handle_preflight(&self, req: ServiceRequest) -> ServiceResponse {
let inner = Rc::clone(&self.inner);
match inner.validate_origin(req.head()) {
Ok(true) => {}
Ok(false) => return req.error_response(CorsError::OriginNotAllowed),
Err(err) => return req.error_response(err),
};
if let Err(err) = inner
.validate_origin(req.head())
.and_then(|_| inner.validate_allowed_method(req.head()))
.validate_allowed_method(req.head())
.and_then(|_| inner.validate_allowed_headers(req.head()))
{
return req.error_response(err);
@ -108,11 +113,17 @@ impl<S> CorsMiddleware<S> {
req.into_response(res)
}
fn augment_response<B>(inner: &Inner, mut res: ServiceResponse<B>) -> ServiceResponse<B> {
if let Some(origin) = inner.access_control_allow_origin(res.request().head()) {
res.headers_mut()
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
};
fn augment_response<B>(
inner: &Inner,
origin_allowed: bool,
mut res: ServiceResponse<B>,
) -> ServiceResponse<B> {
if origin_allowed {
if let Some(origin) = inner.access_control_allow_origin(res.request().head()) {
res.headers_mut()
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
};
}
if let Some(ref expose) = inner.expose_headers_baked {
log::trace!("exposing selected headers: {:?}", expose);
@ -182,8 +193,10 @@ where
}
// only check actual requests with a origin header
if origin.is_some() {
if let Err(err) = self.inner.validate_origin(req.head()) {
let origin_allowed = match (origin, self.inner.validate_origin(req.head())) {
(None, _) => false,
(_, Ok(origin_allowed)) => origin_allowed,
(_, Err(err)) => {
debug!("origin validation failed; inner service is not called");
let mut res = req.error_response(err);
@ -193,14 +206,14 @@ where
return ok(res.map_into_right_body()).boxed_local();
}
}
};
let inner = Rc::clone(&self.inner);
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await;
Ok(Self::augment_response(&inner, res?).map_into_left_body())
Ok(Self::augment_response(&inner, origin_allowed, res?).map_into_left_body())
})
}
}