1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-25 00:12:59 +01:00
actix-extras/src/middleware/session.rs

449 lines
13 KiB
Rust
Raw Normal View History

2017-11-25 18:52:32 +01:00
use std::rc::Rc;
use std::sync::Arc;
2017-12-09 13:33:40 +01:00
use std::marker::PhantomData;
2017-11-27 02:30:35 +01:00
use std::collections::HashMap;
2017-11-25 18:52:32 +01:00
use serde_json;
2017-11-27 02:30:35 +01:00
use serde_json::error::Error as JsonError;
2017-11-25 18:52:32 +01:00
use serde::{Serialize, Deserialize};
use http::header::{self, HeaderValue};
use time::Duration;
2017-11-25 18:52:32 +01:00
use cookie::{CookieJar, Cookie, Key};
use futures::Future;
use futures::future::{FutureResult, ok as FutOk, err as FutErr};
use error::{Result, Error, ResponseError};
2017-11-25 18:52:32 +01:00
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
2017-12-27 04:59:41 +01:00
use middleware::{Middleware, Started, Response};
2017-11-25 18:52:32 +01:00
/// The helper trait to obtain your session data from a request.
2017-12-20 01:09:19 +01:00
///
/// ```rust
/// use actix_web::*;
2017-12-27 04:59:41 +01:00
/// use actix_web::middleware::RequestSession;
2017-12-20 01:09:19 +01:00
///
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = req.session().get::<i32>("counter")? {
/// req.session().set("counter", count+1)?;
/// } else {
/// req.session().set("counter", 1)?;
/// }
///
/// Ok("Welcome!")
/// }
/// # fn main() {}
/// ```
2017-11-25 18:52:32 +01:00
pub trait RequestSession {
fn session(&mut self) -> Session;
}
impl<S> RequestSession for HttpRequest<S> {
2017-11-25 18:52:32 +01:00
fn session(&mut self) -> Session {
if let Some(s_impl) = self.extensions().get_mut::<Arc<SessionImplBox>>() {
if let Some(s) = Arc::get_mut(s_impl) {
return Session(s.0.as_mut())
}
}
Session(unsafe{&mut DUMMY})
2017-11-25 18:52:32 +01:00
}
}
/// The high-level interface you use to modify session data.
///
/// Session object could be obtained with
/// [`RequestSession::session`](trait.RequestSession.html#tymethod.session)
/// method. `RequestSession` trait is implemented for `HttpRequest`.
2017-12-20 00:44:25 +01:00
///
/// ```rust
/// use actix_web::*;
2017-12-27 04:59:41 +01:00
/// use actix_web::middleware::RequestSession;
2017-12-20 00:44:25 +01:00
///
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = req.session().get::<i32>("counter")? {
/// req.session().set("counter", count+1)?;
/// } else {
/// req.session().set("counter", 1)?;
/// }
///
/// Ok("Welcome!")
/// }
/// # fn main() {}
/// ```
2017-11-25 18:52:32 +01:00
pub struct Session<'a>(&'a mut SessionImpl);
impl<'a> Session<'a> {
/// Get a `value` from the session.
pub fn get<T: Deserialize<'a>>(&'a self, key: &str) -> Result<Option<T>> {
if let Some(s) = self.0.get(key) {
Ok(Some(serde_json::from_str(s)?))
} else {
Ok(None)
}
}
/// Set a `value` from the session.
2018-02-28 20:35:26 +01:00
pub fn set<T: Serialize>(&mut self, key: &str, value: T) -> Result<()> {
2017-11-25 18:52:32 +01:00
self.0.set(key, serde_json::to_string(&value)?);
Ok(())
}
/// Remove value from the session.
pub fn remove(&'a mut self, key: &str) {
self.0.remove(key)
}
/// Clear the session.
pub fn clear(&'a mut self) {
self.0.clear()
}
}
struct SessionImplBox(Box<SessionImpl>);
#[doc(hidden)]
unsafe impl Send for SessionImplBox {}
#[doc(hidden)]
unsafe impl Sync for SessionImplBox {}
/// Session storage middleware
2017-12-20 00:44:25 +01:00
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
2018-03-31 09:16:55 +02:00
/// use actix_web::App;
/// use actix_web::middleware::{SessionStorage, CookieSessionBackend};
2017-12-20 00:44:25 +01:00
///
/// fn main() {
2018-03-31 09:16:55 +02:00
/// let app = App::new().middleware(
2017-12-27 04:59:41 +01:00
/// SessionStorage::new( // <- create session middleware
2018-04-09 17:22:25 +02:00
/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend
2018-04-09 18:31:11 +02:00
/// .secure(false))
2017-12-20 00:44:25 +01:00
/// );
/// }
/// ```
2017-12-09 13:33:40 +01:00
pub struct SessionStorage<T, S>(T, PhantomData<S>);
2017-11-25 18:52:32 +01:00
2017-12-09 13:33:40 +01:00
impl<S, T: SessionBackend<S>> SessionStorage<T, S> {
2017-11-25 18:52:32 +01:00
/// Create session storage
2017-12-09 13:33:40 +01:00
pub fn new(backend: T) -> SessionStorage<T, S> {
SessionStorage(backend, PhantomData)
2017-11-25 18:52:32 +01:00
}
}
2017-12-09 13:33:40 +01:00
impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
2017-11-25 18:52:32 +01:00
2018-01-10 07:48:35 +01:00
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
2017-11-27 06:47:33 +01:00
let mut req = req.clone();
2017-11-25 18:52:32 +01:00
let fut = self.0.from_request(&mut req)
2017-11-27 06:47:33 +01:00
.then(move |res| {
2017-11-25 18:52:32 +01:00
match res {
Ok(sess) => {
req.extensions().insert(Arc::new(SessionImplBox(Box::new(sess))));
2017-11-27 06:47:33 +01:00
FutOk(None)
2017-11-25 18:52:32 +01:00
},
Err(err) => FutErr(err)
}
});
2018-01-10 07:48:35 +01:00
Ok(Started::Future(Box::new(fut)))
2017-11-25 18:52:32 +01:00
}
2018-01-10 07:48:35 +01:00
fn response(&self, req: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
2017-11-25 18:52:32 +01:00
if let Some(s_box) = req.extensions().remove::<Arc<SessionImplBox>>() {
s_box.0.write(resp)
} else {
2018-01-10 07:48:35 +01:00
Ok(Response::Done(resp))
2017-11-25 18:52:32 +01:00
}
}
}
/// A simple key-value storage interface that is internally used by `Session`.
#[doc(hidden)]
pub trait SessionImpl: 'static {
fn get(&self, key: &str) -> Option<&str>;
fn set(&mut self, key: &str, value: String);
fn remove(&mut self, key: &str);
fn clear(&mut self);
/// Write session to storage backend.
2018-01-10 07:48:35 +01:00
fn write(&self, resp: HttpResponse) -> Result<Response>;
2017-11-25 18:52:32 +01:00
}
/// Session's storage backend trait definition.
#[doc(hidden)]
2017-12-09 13:33:40 +01:00
pub trait SessionBackend<S>: Sized + 'static {
2017-11-25 18:52:32 +01:00
type Session: SessionImpl;
type ReadFuture: Future<Item=Self::Session, Error=Error>;
/// Parse the session from request and load data from a storage backend.
2017-12-09 13:33:40 +01:00
fn from_request(&self, request: &mut HttpRequest<S>) -> Self::ReadFuture;
2017-11-25 18:52:32 +01:00
}
/// Dummy session impl, does not do anything
struct DummySessionImpl;
static mut DUMMY: DummySessionImpl = DummySessionImpl;
2017-11-25 18:52:32 +01:00
impl SessionImpl for DummySessionImpl {
fn get(&self, _: &str) -> Option<&str> { None }
fn set(&mut self, _: &str, _: String) {}
fn remove(&mut self, _: &str) {}
2017-11-25 18:52:32 +01:00
fn clear(&mut self) {}
2018-01-10 07:48:35 +01:00
fn write(&self, resp: HttpResponse) -> Result<Response> {
Ok(Response::Done(resp))
2017-11-25 18:52:32 +01:00
}
}
/// Session that uses signed cookies as session storage
pub struct CookieSession {
2017-11-27 02:30:35 +01:00
changed: bool,
state: HashMap<String, String>,
inner: Rc<CookieSessionInner>,
}
2018-01-15 22:47:25 +01:00
/// Errors that can occur during handling cookie session
2017-11-27 02:30:35 +01:00
#[derive(Fail, Debug)]
pub enum CookieSessionError {
/// Size of the serialized session is greater than 4000 bytes.
#[fail(display="Size of the serialized session is greater than 4000 bytes.")]
Overflow,
/// Fail to serialize session.
#[fail(display="Fail to serialize session")]
Serialize(JsonError),
2017-11-25 18:52:32 +01:00
}
impl ResponseError for CookieSessionError {}
2017-11-27 02:30:35 +01:00
2017-11-25 18:52:32 +01:00
impl SessionImpl for CookieSession {
fn get(&self, key: &str) -> Option<&str> {
2017-11-27 02:30:35 +01:00
if let Some(s) = self.state.get(key) {
Some(s)
} else {
None
}
2017-11-25 18:52:32 +01:00
}
fn set(&mut self, key: &str, value: String) {
2017-11-27 02:30:35 +01:00
self.changed = true;
self.state.insert(key.to_owned(), value);
2017-11-25 18:52:32 +01:00
}
fn remove(&mut self, key: &str) {
2017-11-27 02:30:35 +01:00
self.changed = true;
self.state.remove(key);
2017-11-25 18:52:32 +01:00
}
fn clear(&mut self) {
2017-11-27 02:30:35 +01:00
self.changed = true;
self.state.clear()
2017-11-25 18:52:32 +01:00
}
2018-01-10 07:48:35 +01:00
fn write(&self, mut resp: HttpResponse) -> Result<Response> {
2017-11-27 02:30:35 +01:00
if self.changed {
let _ = self.inner.set_cookie(&mut resp, &self.state);
2017-11-25 18:52:32 +01:00
}
2018-01-10 07:48:35 +01:00
Ok(Response::Done(resp))
2017-11-25 18:52:32 +01:00
}
}
2018-04-09 17:22:25 +02:00
enum CookieSecurity {
Signed,
Private
}
2017-11-27 02:30:35 +01:00
struct CookieSessionInner {
key: Key,
2018-04-09 17:22:25 +02:00
security: CookieSecurity,
2017-11-27 02:30:35 +01:00
name: String,
path: String,
domain: Option<String>,
secure: bool,
max_age: Option<Duration>,
2017-11-27 02:30:35 +01:00
}
impl CookieSessionInner {
2018-04-09 17:22:25 +02:00
fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner {
2017-11-27 02:30:35 +01:00
CookieSessionInner {
key: Key::from_master(key),
2018-04-09 17:22:25 +02:00
security: security,
2017-12-29 10:01:31 +01:00
name: "actix-session".to_owned(),
2017-11-27 02:30:35 +01:00
path: "/".to_owned(),
domain: None,
secure: true,
2018-04-09 17:22:25 +02:00
max_age: None,
}
2017-11-27 02:30:35 +01:00
}
fn set_cookie(&self, resp: &mut HttpResponse, state: &HashMap<String, String>) -> Result<()> {
let value = serde_json::to_string(&state)
.map_err(CookieSessionError::Serialize)?;
if value.len() > 4064 {
return Err(CookieSessionError::Overflow.into())
}
let mut cookie = Cookie::new(self.name.clone(), value);
cookie.set_path(self.path.clone());
cookie.set_secure(self.secure);
2017-11-29 18:17:00 +01:00
cookie.set_http_only(true);
2017-11-27 02:30:35 +01:00
if let Some(ref domain) = self.domain {
cookie.set_domain(domain.clone());
}
if let Some(max_age) = self.max_age {
cookie.set_max_age(max_age);
}
2017-11-27 02:30:35 +01:00
let mut jar = CookieJar::new();
2018-04-09 17:22:25 +02:00
match self.security {
CookieSecurity::Signed => jar.signed(&self.key).add(cookie),
CookieSecurity::Private => jar.private(&self.key).add(cookie),
}
2017-11-27 02:30:35 +01:00
for cookie in jar.delta() {
let val = HeaderValue::from_str(&cookie.to_string())?;
resp.headers_mut().append(header::SET_COOKIE, val);
}
Ok(())
}
2017-12-09 13:33:40 +01:00
fn load<S>(&self, req: &mut HttpRequest<S>) -> HashMap<String, String> {
2017-12-08 03:00:20 +01:00
if let Ok(cookies) = req.cookies() {
2017-11-27 02:30:35 +01:00
for cookie in cookies {
if cookie.name() == self.name {
let mut jar = CookieJar::new();
jar.add_original(cookie.clone());
2018-04-09 17:22:25 +02:00
let cookie_opt = match self.security {
CookieSecurity::Signed => jar.signed(&self.key).get(&self.name),
CookieSecurity::Private => jar.private(&self.key).get(&self.name),
};
if let Some(cookie) = cookie_opt {
2017-11-27 02:30:35 +01:00
if let Ok(val) = serde_json::from_str(cookie.value()) {
return val;
}
}
}
}
}
HashMap::new()
}
}
2018-04-09 17:22:25 +02:00
/// Use cookies for session storage.
2017-11-25 18:52:32 +01:00
///
2017-11-27 02:30:35 +01:00
/// `CookieSessionBackend` creates sessions which are limited to storing
/// fewer than 4000 bytes of data (as the payload must fit into a single cookie).
2018-04-09 17:22:25 +02:00
/// An Internal Server Error is generated if the session contains more than 4000 bytes.
///
/// A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSessionBackend` constructor.
///
/// A *signed* cookie is stored on the client as plaintext alongside
/// a signature such that the cookie may be viewed but not modified by the client.
2017-11-27 02:30:35 +01:00
///
2018-04-09 17:22:25 +02:00
/// A *private* cookie is stored on the client as encrypted text
/// such that it may neither be viewed nor modified by the client.
2017-11-25 18:52:32 +01:00
///
2018-04-09 17:22:25 +02:00
/// The constructors take a key as an argument.
/// This is the private key for cookie session - when this value is changed, all session data is lost.
/// The constructors will panic if the key is less than 32 bytes in length.
2017-11-25 18:52:32 +01:00
///
2017-11-27 02:30:35 +01:00
///
/// # Example
///
/// ```rust
/// # extern crate actix_web;
2017-12-27 04:59:41 +01:00
/// use actix_web::middleware::CookieSessionBackend;
2017-11-27 02:30:35 +01:00
///
/// # fn main() {
2018-04-09 17:22:25 +02:00
/// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32])
2017-11-27 02:30:35 +01:00
/// .domain("www.rust-lang.org")
2017-12-28 13:02:46 +01:00
/// .name("actix_session")
2017-11-27 02:30:35 +01:00
/// .path("/")
/// .secure(true);
2017-11-27 02:30:35 +01:00
/// # }
/// ```
pub struct CookieSessionBackend(Rc<CookieSessionInner>);
2017-11-27 02:30:35 +01:00
impl CookieSessionBackend {
2018-04-09 17:22:25 +02:00
/// Construct new *signed* `CookieSessionBackend` instance.
///
/// Panics if key length is less than 32 bytes.
pub fn signed(key: &[u8]) -> CookieSessionBackend {
CookieSessionBackend(
Rc::new(CookieSessionInner::new(key, CookieSecurity::Signed)))
}
/// Construct new *private* `CookieSessionBackend` instance.
///
/// Panics if key length is less than 32 bytes.
pub fn private(key: &[u8]) -> CookieSessionBackend {
CookieSessionBackend(
Rc::new(CookieSessionInner::new(key, CookieSecurity::Private)))
2017-11-27 02:30:35 +01:00
}
/// Sets the `path` field in the session cookie being built.
pub fn path<S: Into<String>>(mut self, value: S) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().path = value.into();
2017-11-27 02:30:35 +01:00
self
}
2017-12-28 13:02:46 +01:00
/// Sets the `name` field in the session cookie being built.
pub fn name<S: Into<String>>(mut self, value: S) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().name = value.into();
2017-12-28 13:02:46 +01:00
self
}
2017-11-27 02:30:35 +01:00
/// Sets the `domain` field in the session cookie being built.
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into());
2017-11-27 02:30:35 +01:00
self
}
/// Sets the `secure` field in the session cookie being built.
2018-04-09 17:22:25 +02:00
///
/// If the `secure` field is set, a cookie will only be transmitted when the
/// connection is secure - i.e. `https`
pub fn secure(mut self, value: bool) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().secure = value;
2017-11-27 02:30:35 +01:00
self
}
/// Sets the `max-age` field in the session cookie being built.
pub fn max_age(mut self, value: Duration) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
self
}
}
impl<S> SessionBackend<S> for CookieSessionBackend {
type Session = CookieSession;
type ReadFuture = FutureResult<CookieSession, Error>;
fn from_request(&self, req: &mut HttpRequest<S>) -> Self::ReadFuture {
let state = self.0.load(req);
FutOk(
CookieSession {
changed: false,
inner: Rc::clone(&self.0),
state,
})
2017-11-25 18:52:32 +01:00
}
}