You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
9.3 KiB

  1. // //! A filter for cross-site request forgery (CSRF).
  2. // //!
  3. // //! This middleware is stateless and [based on request
  4. // //! headers](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers).
  5. // //!
  6. // //! By default requests are allowed only if one of these is true:
  7. // //!
  8. // //! * The request method is safe (`GET`, `HEAD`, `OPTIONS`). It is the
  9. // //! applications responsibility to ensure these methods cannot be used to
  10. // //! execute unwanted actions. Note that upgrade requests for websockets are
  11. // //! also considered safe.
  12. // //! * The `Origin` header (added automatically by the browser) matches one
  13. // //! of the allowed origins.
  14. // //! * There is no `Origin` header but the `Referer` header matches one of
  15. // //! the allowed origins.
  16. // //!
  17. // //! Use [`CsrfFilter::allow_xhr()`](struct.CsrfFilter.html#method.allow_xhr)
  18. // //! if you want to allow requests with unprotected methods via
  19. // //! [CORS](../cors/struct.Cors.html).
  20. // //!
  21. // //! # Example
  22. // //!
  23. // //! ```
  24. // //! # extern crate actix_web;
  25. // //! use actix_web::middleware::csrf;
  26. // //! use actix_web::{http, App, HttpRequest, HttpResponse};
  27. // //!
  28. // //! fn handle_post(_: &HttpRequest) -> &'static str {
  29. // //! "This action should only be triggered with requests from the same site"
  30. // //! }
  31. // //!
  32. // //! fn main() {
  33. // //! let app = App::new()
  34. // //! .middleware(
  35. // //! csrf::CsrfFilter::new().allowed_origin("https://www.example.com"),
  36. // //! )
  37. // //! .resource("/", |r| {
  38. // //! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
  39. // //! r.method(http::Method::POST).f(handle_post);
  40. // //! })
  41. // //! .finish();
  42. // //! }
  43. // //! ```
  44. // //!
  45. // //! In this example the entire application is protected from CSRF.
  46. // use std::borrow::Cow;
  47. // use std::collections::HashSet;
  48. // use bytes::Bytes;
  49. // use error::{ResponseError, Result};
  50. // use http::{header, HeaderMap, HttpTryFrom, Uri};
  51. // use httprequest::HttpRequest;
  52. // use httpresponse::HttpResponse;
  53. // use middleware::{Middleware, Started};
  54. // use server::Request;
  55. // /// Potential cross-site request forgery detected.
  56. // #[derive(Debug, Fail)]
  57. // pub enum CsrfError {
  58. // /// The HTTP request header `Origin` was required but not provided.
  59. // #[fail(display = "Origin header required")]
  60. // MissingOrigin,
  61. // /// The HTTP request header `Origin` could not be parsed correctly.
  62. // #[fail(display = "Could not parse Origin header")]
  63. // BadOrigin,
  64. // /// The cross-site request was denied.
  65. // #[fail(display = "Cross-site request denied")]
  66. // CsrDenied,
  67. // }
  68. // impl ResponseError for CsrfError {
  69. // fn error_response(&self) -> HttpResponse {
  70. // HttpResponse::Forbidden().body(self.to_string())
  71. // }
  72. // }
  73. // fn uri_origin(uri: &Uri) -> Option<String> {
  74. // match (
  75. // uri.scheme_part(),
  76. // uri.host(),
  77. // uri.port_part().map(|port| port.as_u16()),
  78. // ) {
  79. // (Some(scheme), Some(host), Some(port)) => Some(format!("{}://{}:{}", scheme, host, port)),
  80. // (Some(scheme), Some(host), None) => Some(format!("{}://{}", scheme, host)),
  81. // _ => None,
  82. // }
  83. // }
  84. // fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
  85. // headers
  86. // .get(header::ORIGIN)
  87. // .map(|origin| {
  88. // origin
  89. // .to_str()
  90. // .map_err(|_| CsrfError::BadOrigin)
  91. // .map(|o| o.into())
  92. // })
  93. // .or_else(|| {
  94. // headers.get(header::REFERER).map(|referer| {
  95. // Uri::try_from(Bytes::from(referer.as_bytes()))
  96. // .ok()
  97. // .as_ref()
  98. // .and_then(uri_origin)
  99. // .ok_or(CsrfError::BadOrigin)
  100. // .map(|o| o.into())
  101. // })
  102. // })
  103. // }
  104. // /// A middleware that filters cross-site requests.
  105. // ///
  106. // /// To construct a CSRF filter:
  107. // ///
  108. // /// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to
  109. // /// start building.
  110. // /// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed
  111. // /// origins.
  112. // /// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve
  113. // /// the constructed filter.
  114. // ///
  115. // /// # Example
  116. // ///
  117. // /// ```
  118. // /// use actix_web::middleware::csrf;
  119. // /// use actix_web::App;
  120. // ///
  121. // /// # fn main() {
  122. // /// let app = App::new()
  123. // /// .middleware(csrf::CsrfFilter::new().allowed_origin("https://www.example.com"));
  124. // /// # }
  125. // /// ```
  126. // #[derive(Default)]
  127. // pub struct CsrfFilter {
  128. // origins: HashSet<String>,
  129. // allow_xhr: bool,
  130. // allow_missing_origin: bool,
  131. // allow_upgrade: bool,
  132. // }
  133. // impl CsrfFilter {
  134. // /// Start building a `CsrfFilter`.
  135. // pub fn new() -> CsrfFilter {
  136. // CsrfFilter {
  137. // origins: HashSet::new(),
  138. // allow_xhr: false,
  139. // allow_missing_origin: false,
  140. // allow_upgrade: false,
  141. // }
  142. // }
  143. // /// Add an origin that is allowed to make requests. Will be verified
  144. // /// against the `Origin` request header.
  145. // pub fn allowed_origin<T: Into<String>>(mut self, origin: T) -> CsrfFilter {
  146. // self.origins.insert(origin.into());
  147. // self
  148. // }
  149. // /// Allow all requests with an `X-Requested-With` header.
  150. // ///
  151. // /// A cross-site attacker should not be able to send requests with custom
  152. // /// headers unless a CORS policy whitelists them. Therefore it should be
  153. // /// safe to allow requests with an `X-Requested-With` header (added
  154. // /// automatically by many JavaScript libraries).
  155. // ///
  156. // /// This is disabled by default, because in Safari it is possible to
  157. // /// circumvent this using redirects and Flash.
  158. // ///
  159. // /// Use this method to enable more lax filtering.
  160. // pub fn allow_xhr(mut self) -> CsrfFilter {
  161. // self.allow_xhr = true;
  162. // self
  163. // }
  164. // /// Allow requests if the expected `Origin` header is missing (and
  165. // /// there is no `Referer` to fall back on).
  166. // ///
  167. // /// The filter is conservative by default, but it should be safe to allow
  168. // /// missing `Origin` headers because a cross-site attacker cannot prevent
  169. // /// the browser from sending `Origin` on unprotected requests.
  170. // pub fn allow_missing_origin(mut self) -> CsrfFilter {
  171. // self.allow_missing_origin = true;
  172. // self
  173. // }
  174. // /// Allow cross-site upgrade requests (for example to open a WebSocket).
  175. // pub fn allow_upgrade(mut self) -> CsrfFilter {
  176. // self.allow_upgrade = true;
  177. // self
  178. // }
  179. // fn validate(&self, req: &Request) -> Result<(), CsrfError> {
  180. // let is_upgrade = req.headers().contains_key(header::UPGRADE);
  181. // let is_safe = req.method().is_safe() && (self.allow_upgrade || !is_upgrade);
  182. // if is_safe || (self.allow_xhr && req.headers().contains_key("x-requested-with")) {
  183. // Ok(())
  184. // } else if let Some(header) = origin(req.headers()) {
  185. // match header {
  186. // Ok(ref origin) if self.origins.contains(origin.as_ref()) => Ok(()),
  187. // Ok(_) => Err(CsrfError::CsrDenied),
  188. // Err(err) => Err(err),
  189. // }
  190. // } else if self.allow_missing_origin {
  191. // Ok(())
  192. // } else {
  193. // Err(CsrfError::MissingOrigin)
  194. // }
  195. // }
  196. // }
  197. // impl<S> Middleware<S> for CsrfFilter {
  198. // fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
  199. // self.validate(req)?;
  200. // Ok(Started::Done)
  201. // }
  202. // }
  203. // #[cfg(test)]
  204. // mod tests {
  205. // use super::*;
  206. // use http::Method;
  207. // use test::TestRequest;
  208. // #[test]
  209. // fn test_safe() {
  210. // let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
  211. // let req = TestRequest::with_header("Origin", "https://www.w3.org")
  212. // .method(Method::HEAD)
  213. // .finish();
  214. // assert!(csrf.start(&req).is_ok());
  215. // }
  216. // #[test]
  217. // fn test_csrf() {
  218. // let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
  219. // let req = TestRequest::with_header("Origin", "https://www.w3.org")
  220. // .method(Method::POST)
  221. // .finish();
  222. // assert!(csrf.start(&req).is_err());
  223. // }
  224. // #[test]
  225. // fn test_referer() {
  226. // let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
  227. // let req =
  228. // TestRequest::with_header("Referer", "https://www.example.com/some/path?query=param")
  229. // .method(Method::POST)
  230. // .finish();
  231. // assert!(csrf.start(&req).is_ok());
  232. // }
  233. // #[test]
  234. // fn test_upgrade() {
  235. // let strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
  236. // let lax_csrf = CsrfFilter::new()
  237. // .allowed_origin("https://www.example.com")
  238. // .allow_upgrade();
  239. // let req = TestRequest::with_header("Origin", "https://cswsh.com")
  240. // .header("Connection", "Upgrade")
  241. // .header("Upgrade", "websocket")
  242. // .method(Method::GET)
  243. // .finish();
  244. // assert!(strict_csrf.start(&req).is_err());
  245. // assert!(lax_csrf.start(&req).is_ok());
  246. // }
  247. // }