1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-24 07:53:00 +01:00

port identity middleware

This commit is contained in:
Nikolay Kim 2019-03-09 20:40:09 -08:00
parent 134863d5c8
commit 12f0c78091
5 changed files with 211 additions and 275 deletions

View File

@ -33,10 +33,10 @@ members = [
] ]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["ssl", "tls", "rust-tls"] #, "session"] features = ["ssl", "tls", "rust-tls", "session"]
[features] [features]
default = ["brotli", "flate2-c"] default = ["brotli", "flate2-c", "session"]
# brotli encoding, requires c compiler # brotli encoding, requires c compiler
brotli = ["brotli2"] brotli = ["brotli2"]
@ -48,7 +48,7 @@ flate2-c = ["flate2/miniz-sys"]
flate2-rust = ["flate2/rust_backend"] flate2-rust = ["flate2/rust_backend"]
# sessions feature, session require "ring" crate and c compiler # sessions feature, session require "ring" crate and c compiler
# session = ["actix-session"] session = ["cookie/secure"]
# tls # tls
tls = ["native-tls", "actix-server/ssl"] tls = ["native-tls", "actix-server/ssl"]
@ -72,6 +72,7 @@ actix-server = { git = "https://github.com/actix/actix-net.git" }
actix-server-config = { git = "https://github.com/actix/actix-net.git" } actix-server-config = { git = "https://github.com/actix/actix-net.git" }
bytes = "0.4" bytes = "0.4"
cookie = { version="0.11", features=["percent-encode"] }
derive_more = "0.14" derive_more = "0.14"
encoding = "0.2" encoding = "0.2"
futures = "0.1" futures = "0.1"

View File

@ -1,141 +0,0 @@
use std::collections::HashMap;
use error::Result;
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response};
type ErrorHandler<S> = Fn(&HttpRequest<S>, HttpResponse) -> Result<Response>;
/// `Middleware` for allowing custom handlers for responses.
///
/// You can use `ErrorHandlers::handler()` method to register a custom error
/// handler for specific status code. You can modify existing response or
/// create completely new one.
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::{ErrorHandlers, Response};
/// use actix_web::{http, App, HttpRequest, HttpResponse, Result};
///
/// fn render_500<S>(_: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
/// let mut builder = resp.into_builder();
/// builder.header(http::header::CONTENT_TYPE, "application/json");
/// Ok(Response::Done(builder.into()))
/// }
///
/// fn main() {
/// let app = App::new()
/// .middleware(
/// ErrorHandlers::new()
/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500),
/// )
/// .resource("/test", |r| {
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .finish();
/// }
/// ```
pub struct ErrorHandlers<S> {
handlers: HashMap<StatusCode, Box<ErrorHandler<S>>>,
}
impl<S> Default for ErrorHandlers<S> {
fn default() -> Self {
ErrorHandlers {
handlers: HashMap::new(),
}
}
}
impl<S> ErrorHandlers<S> {
/// Construct new `ErrorHandlers` instance
pub fn new() -> Self {
ErrorHandlers::default()
}
/// Register error handler for specified status code
pub fn handler<F>(mut self, status: StatusCode, handler: F) -> Self
where
F: Fn(&HttpRequest<S>, HttpResponse) -> Result<Response> + 'static,
{
self.handlers.insert(status, Box::new(handler));
self
}
}
impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(handler) = self.handlers.get(&resp.status()) {
handler(req, resp)
} else {
Ok(Response::Done(resp))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use error::{Error, ErrorInternalServerError};
use http::header::CONTENT_TYPE;
use http::StatusCode;
use httpmessage::HttpMessage;
use middleware::Started;
use test::{self, TestRequest};
fn render_500<S>(_: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
let mut builder = resp.into_builder();
builder.header(CONTENT_TYPE, "0001");
Ok(Response::Done(builder.into()))
}
#[test]
fn test_handler() {
let mw =
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
let mut req = TestRequest::default().finish();
let resp = HttpResponse::InternalServerError().finish();
let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let resp = HttpResponse::Ok().finish();
let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};
assert!(!resp.headers().contains_key(CONTENT_TYPE));
}
struct MiddlewareOne;
impl<S> Middleware<S> for MiddlewareOne {
fn start(&self, _: &HttpRequest<S>) -> Result<Started, Error> {
Err(ErrorInternalServerError("middleware error"))
}
}
#[test]
fn test_middleware_start_error() {
let mut srv = test::TestServer::new(move |app| {
app.middleware(
ErrorHandlers::new()
.handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
).middleware(MiddlewareOne)
.handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001");
}
}

View File

@ -14,26 +14,26 @@
//! *HttpRequest* implements *RequestIdentity* trait. //! *HttpRequest* implements *RequestIdentity* trait.
//! //!
//! ```rust //! ```rust
//! use actix_web::middleware::identity::RequestIdentity; //! use actix_web::middleware::identity::Identity;
//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; //! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
//! use actix_web::*; //! use actix_web::*;
//! //!
//! fn index(req: HttpRequest) -> Result<String> { //! fn index(id: Identity) -> String {
//! // access request identity //! // access request identity
//! if let Some(id) = req.identity() { //! if let Some(id) = id.identity() {
//! Ok(format!("Welcome! {}", id)) //! format!("Welcome! {}", id)
//! } else { //! } else {
//! Ok("Welcome Anonymous!".to_owned()) //! "Welcome Anonymous!".to_owned()
//! } //! }
//! } //! }
//! //!
//! fn login(mut req: HttpRequest) -> HttpResponse { //! fn login(id: Idenity) -> HttpResponse {
//! req.remember("User1".to_owned()); // <- remember identity //! id.remember("User1".to_owned()); // <- remember identity
//! HttpResponse::Ok().finish() //! HttpResponse::Ok().finish()
//! } //! }
//! //!
//! fn logout(mut req: HttpRequest) -> HttpResponse { //! fn logout(id: Identity) -> HttpResponse {
//! req.forget(); // <- remove identity //! id.forget(); // <- remove identity
//! HttpResponse::Ok().finish() //! HttpResponse::Ok().finish()
//! } //! }
//! //!
@ -42,118 +42,144 @@
//! // <- create identity middleware //! // <- create identity middleware
//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend //! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
//! .name("auth-cookie") //! .name("auth-cookie")
//! .secure(false), //! .secure(false))
//! .service(web::resource("/index.html").to(index)
//! .service(web::resource("/login.html").to(login)
//! .service(web::resource("/logout.html").to(logout)
//! )); //! ));
//! } //! }
//! ``` //! ```
use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use actix_service::{Service, Transform};
use cookie::{Cookie, CookieJar, Key, SameSite}; use cookie::{Cookie, CookieJar, Key, SameSite};
use futures::future::{err as FutErr, ok as FutOk, FutureResult}; use futures::future::{ok, Either, FutureResult};
use futures::Future; use futures::{Future, IntoFuture, Poll};
use time::Duration; use time::Duration;
use error::{Error, Result}; use crate::error::{Error, Result};
use http::header::{self, HeaderValue}; use crate::http::header::{self, HeaderValue};
use httprequest::HttpRequest; use crate::request::HttpRequest;
use httpresponse::HttpResponse; use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse};
use middleware::{Middleware, Response, Started}; use crate::FromRequest;
use crate::HttpMessage;
/// The helper trait to obtain your identity from a request. /// The extractor type to obtain your identity from a request.
/// ///
/// ```rust /// ```rust
/// use actix_web::middleware::identity::RequestIdentity;
/// use actix_web::*; /// use actix_web::*;
/// use actix_web::middleware::identity::Identity;
/// ///
/// fn index(req: HttpRequest) -> Result<String> { /// fn index(id: Identity) -> Result<String> {
/// // access request identity /// // access request identity
/// if let Some(id) = req.identity() { /// if let Some(id) = id.identity() {
/// Ok(format!("Welcome! {}", id)) /// Ok(format!("Welcome! {}", id))
/// } else { /// } else {
/// Ok("Welcome Anonymous!".to_owned()) /// Ok("Welcome Anonymous!".to_owned())
/// } /// }
/// } /// }
/// ///
/// fn login(mut req: HttpRequest) -> HttpResponse { /// fn login(id: Identity) -> HttpResponse {
/// req.remember("User1".to_owned()); // <- remember identity /// id.remember("User1".to_owned()); // <- remember identity
/// HttpResponse::Ok().finish() /// HttpResponse::Ok().finish()
/// } /// }
/// ///
/// fn logout(mut req: HttpRequest) -> HttpResponse { /// fn logout(id: Identity) -> HttpResponse {
/// req.forget(); // <- remove identity /// id.forget(); // <- remove identity
/// HttpResponse::Ok().finish() /// HttpResponse::Ok().finish()
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
pub trait RequestIdentity { #[derive(Clone)]
pub struct Identity(HttpRequest);
impl Identity {
/// Return the claimed identity of the user associated request or /// Return the claimed identity of the user associated request or
/// ``None`` if no identity can be found associated with the request. /// ``None`` if no identity can be found associated with the request.
fn identity(&self) -> Option<String>; pub fn identity(&self) -> Option<String> {
if let Some(id) = self.0.extensions().get::<IdentityItem>() {
id.id.clone()
} else {
None
}
}
/// Remember identity. /// Remember identity.
fn remember(&self, identity: String); pub fn remember(&self, identity: String) {
if let Some(id) = self.0.extensions_mut().get_mut::<IdentityItem>() {
id.id = Some(identity);
id.changed = true;
}
}
/// This method is used to 'forget' the current identity on subsequent /// This method is used to 'forget' the current identity on subsequent
/// requests. /// requests.
fn forget(&self); pub fn forget(&self) {
} if let Some(id) = self.0.extensions_mut().get_mut::<IdentityItem>() {
id.id = None;
impl<S> RequestIdentity for HttpRequest<S> { id.changed = true;
fn identity(&self) -> Option<String> {
if let Some(id) = self.extensions().get::<IdentityBox>() {
return id.0.identity().map(|s| s.to_owned());
}
None
}
fn remember(&self, identity: String) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.as_mut().remember(identity);
}
}
fn forget(&self) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.forget();
} }
} }
} }
/// An identity struct IdentityItem {
pub trait Identity: 'static { id: Option<String>,
/// Return the claimed identity of the user associated request or changed: bool,
/// ``None`` if no identity can be found associated with the request. }
fn identity(&self) -> Option<&str>;
/// Remember identity. /// Extractor implementation for Identity type.
fn remember(&mut self, key: String); ///
/// ```rust
/// # use actix_web::*;
/// use actix_web::middleware::identity::Identity;
///
/// fn index(id: Identity) -> String {
/// // access request identity
/// if let Some(id) = id.identity() {
/// format!("Welcome! {}", id)
/// } else {
/// "Welcome Anonymous!".to_owned()
/// }
/// }
/// # fn main() {}
/// ```
impl<P> FromRequest<P> for Identity {
type Error = Error;
type Future = Result<Identity, Error>;
type Config = ();
/// This method is used to 'forget' the current identity on subsequent #[inline]
/// requests. fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
fn forget(&mut self); Ok(Identity(req.clone()))
}
/// Write session to storage backend.
fn write(&mut self, resp: HttpResponse) -> Result<Response>;
} }
/// Identity policy definition. /// Identity policy definition.
pub trait IdentityPolicy<S>: Sized + 'static { pub trait IdentityPolicy: Sized + 'static {
/// The associated identity /// The return type of the middleware
type Identity: Identity; type Future: IntoFuture<Item = Option<String>, Error = Error>;
/// The return type of the middleware /// The return type of the middleware
type Future: Future<Item = Self::Identity, Error = Error>; type ResponseFuture: IntoFuture<Item = (), Error = Error>;
/// Parse the session from request and load data from a service identity. /// Parse the session from request and load data from a service identity.
fn from_request(&self, request: &HttpRequest<S>) -> Self::Future; fn from_request<P>(&self, request: &mut ServiceRequest<P>) -> Self::Future;
/// Write changes to response
fn to_response<B>(
&self,
identity: Option<String>,
changed: bool,
response: &mut ServiceResponse<B>,
) -> Self::ResponseFuture;
} }
/// Request identity middleware /// Request identity middleware
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
/// use actix_web::App; /// use actix_web::App;
/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().middleware(IdentityService::new( /// let app = App::new().middleware(IdentityService::new(
@ -165,68 +191,97 @@ pub trait IdentityPolicy<S>: Sized + 'static {
/// } /// }
/// ``` /// ```
pub struct IdentityService<T> { pub struct IdentityService<T> {
backend: T, backend: Rc<T>,
} }
impl<T> IdentityService<T> { impl<T> IdentityService<T> {
/// Create new identity service with specified backend. /// Create new identity service with specified backend.
pub fn new(backend: T) -> Self { pub fn new(backend: T) -> Self {
IdentityService { backend } IdentityService {
} backend: Rc::new(backend),
}
struct IdentityBox(Box<Identity>);
impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
let req = req.clone();
let fut = self.backend.from_request(&req).then(move |res| match res {
Ok(id) => {
req.extensions_mut().insert(IdentityBox(Box::new(id)));
FutOk(None)
}
Err(err) => FutErr(err),
});
Ok(Started::Future(Box::new(fut)))
}
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(ref mut id) = req.extensions_mut().get_mut::<IdentityBox>() {
id.0.as_mut().write(resp)
} else {
Ok(Response::Done(resp))
} }
} }
} }
#[doc(hidden)] impl<S, T, P, B> Transform<S> for IdentityService<T>
/// Identity that uses private cookies as identity storage. where
pub struct CookieIdentity { P: 'static,
changed: bool, S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
identity: Option<String>, S::Future: 'static,
inner: Rc<CookieIdentityInner>, T: IdentityPolicy,
B: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type InitError = ();
type Transform = IdentityServiceMiddleware<S, T>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(IdentityServiceMiddleware {
backend: self.backend.clone(),
service: Rc::new(RefCell::new(service)),
})
}
} }
impl Identity for CookieIdentity { pub struct IdentityServiceMiddleware<S, T> {
fn identity(&self) -> Option<&str> { backend: Rc<T>,
self.identity.as_ref().map(|s| s.as_ref()) service: Rc<RefCell<S>>,
}
impl<S, T, P, B> Service for IdentityServiceMiddleware<S, T>
where
P: 'static,
B: 'static,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
S::Future: 'static,
T: IdentityPolicy,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.borrow_mut().poll_ready()
} }
fn remember(&mut self, value: String) { fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
self.changed = true; let srv = self.service.clone();
self.identity = Some(value); let backend = self.backend.clone();
}
fn forget(&mut self) { Box::new(
self.changed = true; self.backend.from_request(&mut req).into_future().then(
self.identity = None; move |res| match res {
} Ok(id) => {
req.extensions_mut()
.insert(IdentityItem { id, changed: false });
fn write(&mut self, mut resp: HttpResponse) -> Result<Response> { Either::A(srv.borrow_mut().call(req).and_then(move |mut res| {
if self.changed { let id =
let _ = self.inner.set_cookie(&mut resp, self.identity.take()); res.request().extensions_mut().remove::<IdentityItem>();
}
Ok(Response::Done(resp)) if let Some(id) = id {
return Either::A(
backend
.to_response(id.id, id.changed, &mut res)
.into_future()
.then(move |t| match t {
Ok(_) => Ok(res),
Err(e) => Ok(res.error_response(e)),
}),
);
} else {
Either::B(ok(res))
}
}))
}
Err(err) => Either::B(ok(req.error_response(err))),
},
),
)
} }
} }
@ -253,7 +308,11 @@ impl CookieIdentityInner {
} }
} }
fn set_cookie(&self, resp: &mut HttpResponse, id: Option<String>) -> Result<()> { fn set_cookie<B>(
&self,
resp: &mut ServiceResponse<B>,
id: Option<String>,
) -> Result<()> {
let some = id.is_some(); let some = id.is_some();
{ {
let id = id.unwrap_or_else(String::new); let id = id.unwrap_or_else(String::new);
@ -291,7 +350,7 @@ impl CookieIdentityInner {
Ok(()) Ok(())
} }
fn load<S>(&self, req: &HttpRequest<S>) -> Option<String> { fn load<T>(&self, req: &ServiceRequest<T>) -> Option<String> {
if let Ok(cookies) = req.cookies() { if let Ok(cookies) = req.cookies() {
for cookie in cookies.iter() { for cookie in cookies.iter() {
if cookie.name() == self.name { if cookie.name() == self.name {
@ -384,16 +443,23 @@ impl CookieIdentityPolicy {
} }
} }
impl<S> IdentityPolicy<S> for CookieIdentityPolicy { impl IdentityPolicy for CookieIdentityPolicy {
type Identity = CookieIdentity; type Future = Result<Option<String>, Error>;
type Future = FutureResult<CookieIdentity, Error>; type ResponseFuture = Result<(), Error>;
fn from_request(&self, req: &HttpRequest<S>) -> Self::Future { fn from_request<P>(&self, req: &mut ServiceRequest<P>) -> Self::Future {
let identity = self.0.load(req); Ok(self.0.load(req))
FutOk(CookieIdentity { }
identity,
changed: false, fn to_response<B>(
inner: Rc::clone(&self.0), &self,
}) id: Option<String>,
changed: bool,
res: &mut ServiceResponse<B>,
) -> Self::ResponseFuture {
if changed {
let _ = self.0.set_cookie(res, id);
}
Ok(())
} }
} }

View File

@ -6,8 +6,11 @@ pub use self::compress::Compress;
mod defaultheaders; mod defaultheaders;
pub use self::defaultheaders::DefaultHeaders; pub use self::defaultheaders::DefaultHeaders;
#[cfg(feature = "session")] // #[cfg(feature = "session")]
pub use actix_session as session; // pub use actix_session as session;
mod logger; mod logger;
pub use self::logger::Logger; pub use self::logger::Logger;
#[cfg(feature = "session")]
pub mod identity;

View File

@ -82,8 +82,9 @@ impl<P> ServiceRequest<P> {
/// Create service response for error /// Create service response for error
#[inline] #[inline]
pub fn error_response<E: Into<Error>>(self, err: E) -> ServiceResponse { pub fn error_response<B, E: Into<Error>>(self, err: E) -> ServiceResponse<B> {
ServiceResponse::new(self.req, err.into().into()) let res: Response = err.into().into();
ServiceResponse::new(self.req, res.into_body())
} }
/// This method returns reference to the request head /// This method returns reference to the request head
@ -335,6 +336,12 @@ impl<B> ServiceResponse<B> {
} }
} }
/// Create service response for error
#[inline]
pub fn error_response<E: Into<Error>>(self, err: E) -> Self {
Self::from_err(err, self.request)
}
/// Get reference to original request /// Get reference to original request
#[inline] #[inline]
pub fn request(&self) -> &HttpRequest { pub fn request(&self) -> &HttpRequest {