|
|
|
@ -1,3 +1,44 @@
|
|
|
|
|
#![deny(missing_docs)]
|
|
|
|
|
//! # Rocket Csrf
|
|
|
|
|
//!
|
|
|
|
|
//! A crate to protect you application against csrf.
|
|
|
|
|
//!
|
|
|
|
|
//! ## Feature
|
|
|
|
|
//!
|
|
|
|
|
//! - Automatically protect all POST, PUT, DELETE and PATCH endpoints
|
|
|
|
|
//! - Ability to define exceptions
|
|
|
|
|
//!
|
|
|
|
|
//! ## Usage
|
|
|
|
|
//!
|
|
|
|
|
//! First add it to your `Cargo.toml` (at the moment using git version, because it was made mainly
|
|
|
|
|
//! for [https://github.com/Plume-org/Plume](Plume) and I didn't have the time to backport it to
|
|
|
|
|
//! older Rocket version)
|
|
|
|
|
//!
|
|
|
|
|
//! ```tome
|
|
|
|
|
//! [dependencies.rocket_csrf]
|
|
|
|
|
//! git = "https://github.com/fdb-hiroshima/rocket_csrf"
|
|
|
|
|
//! rev = "50947b8715ae1fa7b73e60b65fdbd1aaf7754f10"
|
|
|
|
|
//!
|
|
|
|
|
//! Then, in your `main.rs`:
|
|
|
|
|
//!
|
|
|
|
|
//! ```rust
|
|
|
|
|
//! extern crate rocket_csrf;
|
|
|
|
|
//!
|
|
|
|
|
//! //...
|
|
|
|
|
//!
|
|
|
|
|
//! fn main() {
|
|
|
|
|
//! rocket::ignite()
|
|
|
|
|
//! .attach(rocket_csrf::CsrfFairingBuilder::new()
|
|
|
|
|
//! //configure it here
|
|
|
|
|
//! .finish().unwrap())
|
|
|
|
|
//! //add your routes, other fairings...
|
|
|
|
|
//! .lanch();
|
|
|
|
|
//! }
|
|
|
|
|
//! ```
|
|
|
|
|
//!
|
|
|
|
|
//! You should define a route for csrf violation error, and registe it in the builder, otherwise
|
|
|
|
|
//! errors will simply be redirected to the route matching `/`
|
|
|
|
|
//!
|
|
|
|
|
extern crate csrf;
|
|
|
|
|
extern crate data_encoding;
|
|
|
|
|
extern crate rand;
|
|
|
|
@ -22,6 +63,39 @@ use std::env;
|
|
|
|
|
use std::io::Read;
|
|
|
|
|
use std::str::from_utf8;
|
|
|
|
|
|
|
|
|
|
/// Builder for [`CsrfFairing`]
|
|
|
|
|
///
|
|
|
|
|
/// The `CsrfFairingBuilder` type allows for creation and configuration of a [`CsrfFairing`], the
|
|
|
|
|
/// main struct of this crate.
|
|
|
|
|
///
|
|
|
|
|
/// # Usage
|
|
|
|
|
/// A Builder is created via the [`new`] method. Then you can configure it with others provided
|
|
|
|
|
/// methods, and get a [`CsrfFairing`] by a call to [`finalize`]
|
|
|
|
|
///
|
|
|
|
|
/// [`CsrfFairing`]: /rocket_csrf/struct.CsrfFairing.html
|
|
|
|
|
/// [`new`]: /rocket_csrf/struct.CsrfFairing.html#method.new
|
|
|
|
|
/// [`finalize`]: /rocket_csrf/struct.CsrfFairing.html#method.finalize
|
|
|
|
|
///
|
|
|
|
|
/// ## Examples
|
|
|
|
|
///
|
|
|
|
|
/// The following shippet show 'CsrfFairingBuilder' being used to create a fairing protecting all
|
|
|
|
|
/// endpoints and redirecting error to `/csrf-violation` and treat them as if they where `GET`
|
|
|
|
|
/// request then.
|
|
|
|
|
///
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// #extern crate rocket_csrf
|
|
|
|
|
///
|
|
|
|
|
/// use rocket_csrf::CsrfFairingBuilder;
|
|
|
|
|
/// fn main() {
|
|
|
|
|
/// rocket::ignite()
|
|
|
|
|
/// .attach(rocket_csrf::CsrfFairingBuilder::new()
|
|
|
|
|
/// .set_default_target("/csrf-violation", rocket::http::Method::Get)
|
|
|
|
|
/// .finish().unwrap())
|
|
|
|
|
/// //add your routes, other fairings...
|
|
|
|
|
/// .launch();
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
|
|
|
|
|
pub struct CsrfFairingBuilder {
|
|
|
|
|
duration: i64,
|
|
|
|
|
default_target: (String, Method),
|
|
|
|
@ -33,6 +107,9 @@ pub struct CsrfFairingBuilder {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CsrfFairingBuilder {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Create a new builder with default values.
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
CsrfFairingBuilder {
|
|
|
|
|
duration: 60 * 60,
|
|
|
|
@ -45,30 +122,126 @@ impl CsrfFairingBuilder {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set the timeout (in seconds) of CSRF tokens generated by the final Fairing. Default timeout
|
|
|
|
|
/// is one hour.
|
|
|
|
|
pub fn set_timeout(mut self, timeout: i64) -> Self {
|
|
|
|
|
self.duration = timeout;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set the default route when an invalide request is catched, you may add a <uri> as a segment
|
|
|
|
|
/// or a param to get the percent-encoded original target. You can also set the method of the
|
|
|
|
|
/// route to which you choosed to redirect.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// use rocket_csrf::CsrfFairingBuilder;
|
|
|
|
|
/// fn main() {
|
|
|
|
|
/// rocket::ignite()
|
|
|
|
|
/// .attach(rocket_csrf::CsrfFairingBuilder::new()
|
|
|
|
|
/// .set_default_target("/csrf-violation", rocket::http::Method::Get)
|
|
|
|
|
/// .finish().unwrap())
|
|
|
|
|
/// //add your routes, other fairings...
|
|
|
|
|
/// .launch();
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
|
|
pub fn set_default_target(mut self, default_target: String, method: Method) -> Self {
|
|
|
|
|
self.default_target = (default_target, method);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set the list of exceptions which will not be redirected to the default route, removing any
|
|
|
|
|
/// previously added exceptions, to juste add exceptions use [`add_exceptions`] instead. A route may
|
|
|
|
|
/// contain dynamic parts noted as <name>, which will be replaced in the target route.
|
|
|
|
|
/// Note that this is not aware of Rocket's routes, so matching `/something/<dynamic>` while
|
|
|
|
|
/// match against `/something/static`, even if those are different routes for Rocket. To
|
|
|
|
|
/// circunvence this issue, you can add a (not so) exception matching the static route before
|
|
|
|
|
/// the dynamic one, and redirect it to the default target manually.
|
|
|
|
|
///
|
|
|
|
|
/// [`add_exceptions`]: /rocket_csrf/struct.CsrfFairing.html#method.add_exceptions
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// use rocket_csrf::CsrfFairingBuilder;
|
|
|
|
|
/// fn main() {
|
|
|
|
|
/// rocket::ignite()
|
|
|
|
|
/// .attach(rocket_csrf::CsrfFairingBuilder::new()
|
|
|
|
|
/// .set_exceptions(vec![
|
|
|
|
|
/// ("/some/path".to_owned(), "/some/path".to_owned(), rocket::http::Method::Post))//don't verify csrf token
|
|
|
|
|
/// ("/some/<other>/path".to_owned(), "/csrf-error?where=<other>".to_owned(), rocket::http::Method::Get))
|
|
|
|
|
/// ])
|
|
|
|
|
/// .finish().unwrap())
|
|
|
|
|
/// //add your routes, other fairings...
|
|
|
|
|
/// .launch();
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
pub fn set_exceptions(mut self, exceptions: Vec<(String, String, Method)>) -> Self {
|
|
|
|
|
self.exceptions = exceptions;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
/// Add the to list of exceptions which will not be redirected to the default route. See
|
|
|
|
|
/// [`set_exceptions`] for more informations on how exceptions work.
|
|
|
|
|
///
|
|
|
|
|
/// [`set_exceptions`]: /rocket_csrf/struct.CsrfFairing.html#method.set_exceptions
|
|
|
|
|
pub fn add_exceptions(mut self, exceptions: Vec<(String, String, Method)>) -> Self {
|
|
|
|
|
self.exceptions.extend(exceptions);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set the secret key used to generate secure cryptographic tokens. If not set, rocket_csrf
|
|
|
|
|
/// will attempt to get the secret used by Rocket for it's own private cookies via the
|
|
|
|
|
/// ROCKET_SECRET_KEY environment variable, or will generate a new one at each restart.
|
|
|
|
|
/// Having the secret key set (via this or Rocket environment variable) allow tokens to keep
|
|
|
|
|
/// their validity in case of an application restart.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// use rocket_csrf::CsrfFairingBuilder;
|
|
|
|
|
/// fn main() {
|
|
|
|
|
/// rocket::ignite()
|
|
|
|
|
/// .attach(rocket_csrf::CsrfFairingBuilder::new()
|
|
|
|
|
/// .set_secret([0;32])//don't do this, use trully secret array instead
|
|
|
|
|
/// .finish().unwrap())
|
|
|
|
|
/// //add your routes, other fairings...
|
|
|
|
|
/// .launch();
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
pub fn set_secret(mut self, secret: [u8; 32]) -> Self {
|
|
|
|
|
self.secret = Some(secret);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set if this should modify response to insert tokens automatically in all forms. If true,
|
|
|
|
|
/// this will insert tokens in all forms it encounter, if false, you will have to add them via
|
|
|
|
|
/// [`CsrfToken`], which you may obtain via request guards.
|
|
|
|
|
///
|
|
|
|
|
/// [`CsrfToken`]: /rocket_csrf/struct.CsrfToken.html
|
|
|
|
|
pub fn set_auto_insert(mut self, auto_insert: bool) -> Self {
|
|
|
|
|
self.auto_insert = auto_insert;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set prefixs for which this will not try to add tokens in forms. This has no effect if
|
|
|
|
|
/// auto_insert is set to false. Not having to parse response on paths witch don't need it may
|
|
|
|
|
/// improve performances, but not that only html documents are parsed, so it's not usefull to
|
|
|
|
|
/// use it on routes containing only images or stillsheets.
|
|
|
|
|
pub fn set_auto_insert_disable_prefix(mut self, auto_insert_prefix: Vec<String>) -> Self {
|
|
|
|
|
self.auto_insert_disable_prefix = auto_insert_prefix;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set the maximum size of a request before it get send chunked. A request will need at most
|
|
|
|
|
/// this additional memory for the buffer used to parse and tokens into forms. This have no
|
|
|
|
|
/// effect if auto_insert is set to false. Default value is 16Kio
|
|
|
|
|
pub fn set_auto_insert_max_chunk_size(mut self, chunk_size: u64) -> Self {
|
|
|
|
|
self.auto_insert_max_size = chunk_size;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the fairing from the builder.
|
|
|
|
|
pub fn finalize(self) -> Result<CsrfFairing, ()> {
|
|
|
|
|
let secret = self.secret.unwrap_or_else(|| {
|
|
|
|
|
env::vars()
|
|
|
|
@ -116,6 +289,13 @@ impl CsrfFairingBuilder {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Fairing to protect against Csrf attacks.
|
|
|
|
|
///
|
|
|
|
|
/// The `CsrfFairing` type protect a Rocket instance against Csrf attack by requesting mendatory
|
|
|
|
|
/// token on any POST, PUT, DELETE or PATCH request.
|
|
|
|
|
/// This is created via a [`CsrfFairingBuilder`], and implement nothing else than the `Fairing` trait.
|
|
|
|
|
///
|
|
|
|
|
/// [`CsrfFairingBuilder`]: /rocket_csrf/struct.CsrfFairing.html
|
|
|
|
|
pub struct CsrfFairing {
|
|
|
|
|
duration: i64,
|
|
|
|
|
default_target: (Path, Method),
|
|
|
|
@ -364,7 +544,7 @@ impl<'a> Read for CsrfProxy<'a> {
|
|
|
|
|
self.buf.push((buf[pos..].to_vec(), 0));
|
|
|
|
|
self.state = Reset;
|
|
|
|
|
return Ok(pos);
|
|
|
|
|
} //TODO
|
|
|
|
|
}
|
|
|
|
|
_ => SearchInput,
|
|
|
|
|
},
|
|
|
|
|
SearchMethod(pos) => match buf[i] as char {
|
|
|
|
@ -374,7 +554,7 @@ impl<'a> Read for CsrfProxy<'a> {
|
|
|
|
|
self.buf.push((buf[pos..].to_vec(), 0));
|
|
|
|
|
self.state = Reset;
|
|
|
|
|
return Ok(pos);
|
|
|
|
|
} //TODO
|
|
|
|
|
}
|
|
|
|
|
_ => SearchMethod(pos),
|
|
|
|
|
},
|
|
|
|
|
PartialNameMatch(count, pos) => match (buf[i] as char, count) {
|
|
|
|
@ -399,7 +579,6 @@ impl<'a> Read for CsrfProxy<'a> {
|
|
|
|
|
self.buf.push((buf[i + 1..].to_vec(), 0));
|
|
|
|
|
self.state = Reset;
|
|
|
|
|
return Ok(i + 1);
|
|
|
|
|
//TODO
|
|
|
|
|
} else {
|
|
|
|
|
CloseInputTag
|
|
|
|
|
},
|
|
|
|
@ -409,6 +588,13 @@ impl<'a> Read for CsrfProxy<'a> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Csrf token to insert into pages.
|
|
|
|
|
///
|
|
|
|
|
/// The `CsrfToken` type allow you to add tokens into your pages anywhere you want, and is mainly
|
|
|
|
|
/// usefull if you disabled auto-insert when building the fairing registered in Rocket.
|
|
|
|
|
/// This impltement Serde's Serialize so you may insert it directly into your templats as if it was
|
|
|
|
|
/// a String. It also implement FromRequest so you can get it as a request guard. This is also the
|
|
|
|
|
/// only way to get this struct.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct CsrfToken {
|
|
|
|
|
value: String,
|
|
|
|
|