From dde31288bdfb00cf325ae1f669d5fc098cee98ea Mon Sep 17 00:00:00 2001 From: equal-l2 Date: Sat, 18 Jul 2020 05:26:31 +0900 Subject: Update to actix 2 and futures 0.3 --- src/auth.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 80 insertions(+), 25 deletions(-) (limited to 'src/auth.rs') diff --git a/src/auth.rs b/src/auth.rs index 6081a9d..31826d8 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,6 +1,7 @@ +use actix_web::dev::{Body, Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::http::{header, StatusCode}; -use actix_web::middleware::{Middleware, Response}; use actix_web::{HttpRequest, HttpResponse, Result}; +use futures::future; use sha2::{Digest, Sha256, Sha512}; use crate::errors::{self, ContextualError}; @@ -87,16 +88,61 @@ pub fn get_hash(text: &str) -> Vec { hasher.finalize().to_vec() } -impl Middleware for Auth { - fn response( - &self, - req: &HttpRequest, - resp: HttpResponse, - ) -> Result { - let required_auth = &req.state().auth; +pub struct AuthMiddleware { + service: S, +} + +impl Transform for Auth +where + S: Service< + Request = ServiceRequest, + Response = ServiceResponse, + Error = actix_web::Error, + >, + S::Future: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = S::Error; + type Future = future::Ready>; + type Transform = AuthMiddleware; + type InitError = (); + fn new_transform(&self, service: S) -> Self::Future { + future::ok(AuthMiddleware { service }) + } +} + +impl Service for AuthMiddleware +where + S: Service< + Request = ServiceRequest, + Response = ServiceResponse, + Error = actix_web::Error, + >, + S::Future: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = S::Error; + type Future = + std::pin::Pin>>>; + + fn poll_ready( + &mut self, + cx: &mut std::task::Context, + ) -> std::task::Poll> { + self.service.poll_ready(cx) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + let (req, pl) = req.into_parts(); + let required_auth = &req.app_data::().unwrap().auth; if required_auth.is_empty() { - return Ok(Response::Done(resp)); + let resp = self + .service + .call(ServiceRequest::from_parts(req, pl).unwrap_or_else(|_| unreachable!())); + return Box::pin(async { resp.await }); } if let Some(auth_headers) = req.headers().get(header::AUTHORIZATION) { @@ -104,30 +150,38 @@ impl Middleware for Auth { Ok(auth_req) => auth_req, Err(err) => { let auth_err = ContextualError::HTTPAuthenticationError(Box::new(err)); - return Ok(Response::Done(HttpResponse::BadRequest().body( - build_unauthorized_response(&req, auth_err, true, StatusCode::BAD_REQUEST), + let body = + build_unauthorized_response(&req, auth_err, true, StatusCode::BAD_REQUEST); + return Box::pin(future::ok(ServiceResponse::new( + req, + HttpResponse::BadRequest().body(body), ))); } }; if match_auth(auth_req, required_auth) { - return Ok(Response::Done(resp)); + let resp = self + .service + .call(ServiceRequest::from_parts(req, pl).unwrap_or_else(|_| unreachable!())); + return Box::pin(async { resp.await }); } } - Ok(Response::Done( + let body = build_unauthorized_response( + &req, + ContextualError::InvalidHTTPCredentials, + true, + StatusCode::UNAUTHORIZED, + ); + Box::pin(future::ok(ServiceResponse::new( + req, HttpResponse::Unauthorized() .header( header::WWW_AUTHENTICATE, header::HeaderValue::from_static("Basic realm=\"miniserve\""), ) - .body(build_unauthorized_response( - &req, - ContextualError::InvalidHTTPCredentials, - true, - StatusCode::UNAUTHORIZED, - )), - )) + .body(body), + ))) } } @@ -135,18 +189,19 @@ impl Middleware for Auth { /// The reason why log_error_chain is optional is to handle cases where the auth pop-up appears and when the user clicks Cancel. /// In those case, we do not log the error to the terminal since it does not really matter. fn build_unauthorized_response( - req: &HttpRequest, + req: &HttpRequest, error: ContextualError, log_error_chain: bool, error_code: StatusCode, ) -> String { + let state = req.app_data::().unwrap(); let error = ContextualError::HTTPAuthenticationError(Box::new(error)); if log_error_chain { errors::log_error_chain(error.to_string()); } - let return_path = match &req.state().random_route { - Some(random_route) => format!("/{}", random_route), + let return_path = match state.random_route { + Some(ref random_route) => format!("/{}", random_route), None => "/".to_string(), }; @@ -156,8 +211,8 @@ fn build_unauthorized_response( &return_path, None, None, - req.state().default_color_scheme, - req.state().default_color_scheme, + state.default_color_scheme, + state.default_color_scheme, false, false, ) -- cgit v1.2.3 From bd6575cc5fa8c973eb845c0143b4a5ea181ed3f9 Mon Sep 17 00:00:00 2001 From: equal-l2 Date: Tue, 21 Jul 2020 06:54:58 +0900 Subject: Use actix-web-httpauth for authentication middleware --- src/auth.rs | 151 ++++++++++++------------------------------------------------ 1 file changed, 30 insertions(+), 121 deletions(-) (limited to 'src/auth.rs') diff --git a/src/auth.rs b/src/auth.rs index 31826d8..91c7bb0 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,14 +1,12 @@ -use actix_web::dev::{Body, Service, ServiceRequest, ServiceResponse, Transform}; +use actix_web::dev::ServiceRequest; use actix_web::http::{header, StatusCode}; use actix_web::{HttpRequest, HttpResponse, Result}; -use futures::future; +use actix_web_httpauth::extractors::basic::BasicAuth; use sha2::{Digest, Sha256, Sha512}; use crate::errors::{self, ContextualError}; use crate::renderer; -pub struct Auth; - #[derive(Clone, Debug)] /// HTTP Basic authentication parameters pub struct BasicAuthParams { @@ -16,6 +14,15 @@ pub struct BasicAuthParams { pub password: String, } +impl From for BasicAuthParams { + fn from(auth: BasicAuth) -> Self { + Self { + username: auth.user_id().to_string(), + password: auth.password().unwrap_or(&"".into()).to_string(), + } + } +} + #[derive(Clone, Debug, PartialEq)] /// `password` field of `RequiredAuth` pub enum RequiredAuthPassword { @@ -31,29 +38,6 @@ pub struct RequiredAuth { pub password: RequiredAuthPassword, } -/// Decode a HTTP basic auth string into a tuple of username and password. -pub fn parse_basic_auth( - authorization_header: &header::HeaderValue, -) -> Result { - let basic_removed = authorization_header - .to_str() - .map_err(|e| { - ContextualError::ParseError("HTTP authentication header".to_string(), e.to_string()) - })? - .replace("Basic ", ""); - let decoded = base64::decode(&basic_removed).map_err(ContextualError::Base64DecodeError)?; - let decoded_str = String::from_utf8_lossy(&decoded); - let credentials: Vec<&str> = decoded_str.splitn(2, ':').collect(); - - // If argument parsing went fine, it means the HTTP credentials string is well formatted - // So we can safely unpack the username and the password - - Ok(BasicAuthParams { - username: credentials[0].to_owned(), - password: credentials[1].to_owned(), - }) -} - /// Return `true` if `basic_auth` is matches any of `required_auth` pub fn match_auth(basic_auth: BasicAuthParams, required_auth: &[RequiredAuth]) -> bool { required_auth @@ -88,100 +72,25 @@ pub fn get_hash(text: &str) -> Vec { hasher.finalize().to_vec() } -pub struct AuthMiddleware { - service: S, -} - -impl Transform for Auth -where - S: Service< - Request = ServiceRequest, - Response = ServiceResponse, - Error = actix_web::Error, - >, - S::Future: 'static, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = S::Error; - type Future = future::Ready>; - type Transform = AuthMiddleware; - type InitError = (); - fn new_transform(&self, service: S) -> Self::Future { - future::ok(AuthMiddleware { service }) - } -} - -impl Service for AuthMiddleware -where - S: Service< - Request = ServiceRequest, - Response = ServiceResponse, - Error = actix_web::Error, - >, - S::Future: 'static, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = S::Error; - type Future = - std::pin::Pin>>>; - - fn poll_ready( - &mut self, - cx: &mut std::task::Context, - ) -> std::task::Poll> { - self.service.poll_ready(cx) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - let (req, pl) = req.into_parts(); - let required_auth = &req.app_data::().unwrap().auth; - - if required_auth.is_empty() { - let resp = self - .service - .call(ServiceRequest::from_parts(req, pl).unwrap_or_else(|_| unreachable!())); - return Box::pin(async { resp.await }); - } - - if let Some(auth_headers) = req.headers().get(header::AUTHORIZATION) { - let auth_req = match parse_basic_auth(auth_headers) { - Ok(auth_req) => auth_req, - Err(err) => { - let auth_err = ContextualError::HTTPAuthenticationError(Box::new(err)); - let body = - build_unauthorized_response(&req, auth_err, true, StatusCode::BAD_REQUEST); - return Box::pin(future::ok(ServiceResponse::new( - req, - HttpResponse::BadRequest().body(body), - ))); - } - }; - - if match_auth(auth_req, required_auth) { - let resp = self - .service - .call(ServiceRequest::from_parts(req, pl).unwrap_or_else(|_| unreachable!())); - return Box::pin(async { resp.await }); - } - } - - let body = build_unauthorized_response( - &req, - ContextualError::InvalidHTTPCredentials, - true, - StatusCode::UNAUTHORIZED, - ); - Box::pin(future::ok(ServiceResponse::new( - req, - HttpResponse::Unauthorized() - .header( - header::WWW_AUTHENTICATE, - header::HeaderValue::from_static("Basic realm=\"miniserve\""), - ) - .body(body), - ))) +pub async fn handle_auth(req: ServiceRequest, cred: BasicAuth) -> Result { + let (req, pl) = req.into_parts(); + let required_auth = &req.app_data::().unwrap().auth; + + if match_auth(cred.into(), required_auth) { + Ok(ServiceRequest::from_parts(req, pl).unwrap_or_else(|_| unreachable!())) + } else { + Err(HttpResponse::Unauthorized() + .header( + header::WWW_AUTHENTICATE, + header::HeaderValue::from_static("Basic realm=\"miniserve\""), + ) + .body(build_unauthorized_response( + &req, + ContextualError::InvalidHTTPCredentials, + true, + StatusCode::UNAUTHORIZED, + )) + .into()) } } -- cgit v1.2.3