From ff06958b32203966962f2de28442d45fea5e9fa6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 21 Jul 2022 03:50:22 +0200 Subject: [PATCH] improve httpauth ergonomics (#264) * improve httpauth ergonomics * update changelog * code and docs cleanup * docs * docs clean * remove AuthExtractor trait * update changelog --- actix-web-httpauth/CHANGES.md | 9 +- actix-web-httpauth/Cargo.toml | 13 +-- actix-web-httpauth/src/extractors/basic.rs | 88 +++++------------ actix-web-httpauth/src/extractors/bearer.rs | 60 +++--------- actix-web-httpauth/src/extractors/config.rs | 6 +- actix-web-httpauth/src/extractors/errors.rs | 42 ++++---- actix-web-httpauth/src/extractors/mod.rs | 96 +------------------ .../src/headers/authorization/errors.rs | 63 ++++++------ .../src/headers/authorization/header.rs | 67 +++++-------- .../src/headers/authorization/mod.rs | 6 +- .../src/headers/authorization/scheme/basic.rs | 14 +-- .../headers/authorization/scheme/bearer.rs | 29 +++--- .../src/headers/authorization/scheme/mod.rs | 5 +- actix-web-httpauth/src/headers/mod.rs | 2 +- .../www_authenticate/challenge/basic.rs | 21 ++-- .../challenge/bearer/builder.rs | 26 +++-- .../challenge/bearer/challenge.rs | 29 +++--- .../challenge/bearer/errors.rs | 19 ++-- .../www_authenticate/challenge/bearer/mod.rs | 19 +++- .../challenge/bearer/tests.rs | 14 --- .../src/headers/www_authenticate/header.rs | 12 ++- .../src/headers/www_authenticate/mod.rs | 6 +- actix-web-httpauth/src/lib.rs | 15 ++- actix-web-httpauth/src/middleware.rs | 86 +++++++++-------- actix-web-httpauth/src/utils.rs | 11 ++- 25 files changed, 296 insertions(+), 462 deletions(-) delete mode 100644 actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/tests.rs diff --git a/actix-web-httpauth/CHANGES.md b/actix-web-httpauth/CHANGES.md index cd3f20e04..90c695521 100644 --- a/actix-web-httpauth/CHANGES.md +++ b/actix-web-httpauth/CHANGES.md @@ -1,10 +1,15 @@ # Changes ## Unreleased - 2022-xx-xx -- `BasicAuth::user_id()` now returns a `&str`. [#249] -- `BasicAuth::password()` now returns a `&str`. [#249] +- Removed `AuthExtractor` trait; implement `FromRequest` for your custom auth types. [#264] +- `BasicAuth::user_id()` now returns `&str`. [#249] +- `BasicAuth::password()` now returns `Option<&str>`. [#249] +- `Basic::user_id()` now returns `&str`. [#264] +- `Basic::password()` now returns `Option<&str>`. [#264] +- `Bearer::token()` now returns `&str`. [#264] [#249]: https://github.com/actix/actix-extras/pull/249 +[#264]: https://github.com/actix/actix-extras/pull/264 ## 0.7.0 - 2022-07-19 diff --git a/actix-web-httpauth/Cargo.toml b/actix-web-httpauth/Cargo.toml index 98bc14721..d3a57a0e1 100644 --- a/actix-web-httpauth/Cargo.toml +++ b/actix-web-httpauth/Cargo.toml @@ -18,15 +18,16 @@ name = "actix_web_httpauth" path = "src/lib.rs" [dependencies] -actix-service = "2" actix-utils = "3" -actix-web = { version = "4", default_features = false } +actix-web = { version = "4.1", default_features = false } base64 = "0.13" -futures-util = { version = "0.3.7", default-features = false } -futures-core = { version = "0.3.7", default-features = false } +futures-core = "0.3.7" +futures-util = { version = "0.3.7", default-features = false, features = ["std"] } +log = "0.4" pin-project-lite = "0.2.7" [dev-dependencies] -actix-cors = "0.6.0" -actix-web = { version = "4", default_features = false, features = ["macros"] } +actix-cors = "0.6" +actix-service = "2" +actix-web = { version = "4.1", default_features = false, features = ["macros"] } diff --git a/actix-web-httpauth/src/extractors/basic.rs b/actix-web-httpauth/src/extractors/basic.rs index bad0df878..97890dda2 100644 --- a/actix-web-httpauth/src/extractors/basic.rs +++ b/actix-web-httpauth/src/extractors/basic.rs @@ -1,32 +1,27 @@ -//! Extractor for the "Basic" HTTP Authentication Scheme +//! Extractor for the "Basic" HTTP Authentication Scheme. use std::borrow::Cow; use actix_utils::future::{ready, Ready}; -use actix_web::dev::{Payload, ServiceRequest}; -use actix_web::http::header::Header; -use actix_web::{FromRequest, HttpRequest}; +use actix_web::{dev::Payload, http::header::Header, FromRequest, HttpRequest}; -use super::config::AuthExtractorConfig; -use super::errors::AuthenticationError; -use super::AuthExtractor; -use crate::headers::authorization::{Authorization, Basic}; -use crate::headers::www_authenticate::basic::Basic as Challenge; +use super::{config::AuthExtractorConfig, errors::AuthenticationError}; +use crate::headers::{ + authorization::{Authorization, Basic}, + www_authenticate::basic::Basic as Challenge, +}; -/// [`BasicAuth`] extractor configuration, -/// used for [`WWW-Authenticate`] header later. +/// [`BasicAuth`] extractor configuration used for [`WWW-Authenticate`] header later. /// -/// [`BasicAuth`]: ./struct.BasicAuth.html -/// [`WWW-Authenticate`]: -/// ../../headers/www_authenticate/struct.WwwAuthenticate.html +/// [`WWW-Authenticate`]: crate::headers::www_authenticate::WwwAuthenticate #[derive(Debug, Clone, Default)] pub struct Config(Challenge); impl Config { /// Set challenge `realm` attribute. /// - /// The "realm" attribute indicates the scope of protection in the manner - /// described in HTTP/1.1 [RFC2617](https://tools.ietf.org/html/rfc2617#section-1.2). + /// The "realm" attribute indicates the scope of protection in the manner described in HTTP/1.1 + /// [RFC 2617 §1.2](https://tools.ietf.org/html/rfc2617#section-1.2). pub fn realm(mut self, value: T) -> Config where T: Into>, @@ -50,14 +45,10 @@ impl AuthExtractorConfig for Config { } } -// Needs `fn main` to display complete example. -#[allow(clippy::needless_doctest_main)] /// Extractor for HTTP Basic auth. /// -/// # Example -/// +/// # Examples /// ``` -/// use actix_web::Result; /// use actix_web_httpauth::extractors::basic::BasicAuth; /// /// async fn index(auth: BasicAuth) -> String { @@ -65,41 +56,36 @@ impl AuthExtractorConfig for Config { /// } /// ``` /// -/// If authentication fails, this extractor fetches the [`Config`] instance -/// from the [app data] in order to properly form the `WWW-Authenticate` -/// response header. -/// -/// ## Example +/// If authentication fails, this extractor fetches the [`Config`] instance from the [app data] in +/// order to properly form the `WWW-Authenticate` response header. /// +/// # Examples /// ``` /// use actix_web::{web, App}; -/// use actix_web_httpauth::extractors::basic::{BasicAuth, Config}; +/// use actix_web_httpauth::extractors::basic::{self, BasicAuth}; /// /// async fn index(auth: BasicAuth) -> String { /// format!("Hello, {}!", auth.user_id()) /// } /// -/// fn main() { -/// let app = App::new() -/// .app_data(Config::default().realm("Restricted area")) -/// .service(web::resource("/index.html").route(web::get().to(index))); -/// } +/// App::new() +/// .app_data(basic::Config::default().realm("Restricted area")) +/// .service(web::resource("/index.html").route(web::get().to(index))); /// ``` /// -/// [`Config`]: ./struct.Config.html -/// [app data]: https://docs.rs/actix-web/1.0.0-beta.5/actix_web/struct.App.html#method.data +/// [app data]: https://docs.rs/actix-web/4/actix_web/struct.App.html#method.app_data #[derive(Debug, Clone)] pub struct BasicAuth(Basic); impl BasicAuth { /// Returns client's user-ID. pub fn user_id(&self) -> &str { - self.0.user_id().as_ref() + self.0.user_id() } /// Returns client's password. pub fn password(&self) -> Option<&str> { - self.0.password().map(|s| s.as_ref()) + self.0.password() } } @@ -111,35 +97,13 @@ impl FromRequest for BasicAuth { ready( Authorization::::parse(req) .map(|auth| BasicAuth(auth.into_scheme())) - .map_err(|_| { - // TODO: debug! the original error + .map_err(|err| { + log::debug!("`BasicAuth` extract error: {}", err); + let challenge = req .app_data::() .map(|config| config.0.clone()) - // TODO: Add trace! about `Default::default` call - .unwrap_or_else(Default::default); - - AuthenticationError::new(challenge) - }), - ) - } -} - -impl AuthExtractor for BasicAuth { - type Error = AuthenticationError; - type Future = Ready>; - - fn from_service_request(req: &ServiceRequest) -> Self::Future { - ready( - Authorization::::parse(req) - .map(|auth| BasicAuth(auth.into_scheme())) - .map_err(|_| { - // TODO: debug! the original error - let challenge = req - .app_data::() - .map(|config| config.0.clone()) - // TODO: Add trace! about `Default::default` call - .unwrap_or_else(Default::default); + .unwrap_or_default(); AuthenticationError::new(challenge) }), diff --git a/actix-web-httpauth/src/extractors/bearer.rs b/actix-web-httpauth/src/extractors/bearer.rs index 04b86ec16..e5610ff1e 100644 --- a/actix-web-httpauth/src/extractors/bearer.rs +++ b/actix-web-httpauth/src/extractors/bearer.rs @@ -1,19 +1,15 @@ -//! Extractor for the "Bearer" HTTP Authentication Scheme +//! Extractor for the "Bearer" HTTP Authentication Scheme. use std::{borrow::Cow, default::Default}; use actix_utils::future::{ready, Ready}; -use actix_web::{ - dev::{Payload, ServiceRequest}, - http::header::Header, - FromRequest, HttpRequest, -}; +use actix_web::{dev::Payload, http::header::Header, FromRequest, HttpRequest}; -use super::{config::AuthExtractorConfig, errors::AuthenticationError, AuthExtractor}; +use super::{config::AuthExtractorConfig, errors::AuthenticationError}; pub use crate::headers::www_authenticate::bearer::Error; use crate::headers::{authorization, www_authenticate::bearer}; -/// [BearerAuth](./struct/BearerAuth.html) extractor configuration. +/// [`BearerAuth`] extractor configuration. #[derive(Debug, Clone, Default)] pub struct Config(bearer::Bearer); @@ -31,7 +27,7 @@ impl Config { /// Set challenge `realm` attribute. /// /// The "realm" attribute indicates the scope of protection in the manner - /// described in HTTP/1.1 [RFC2617](https://tools.ietf.org/html/rfc2617#section-1.2). + /// described in HTTP/1.1 [RFC 2617](https://tools.ietf.org/html/rfc2617#section-1.2). pub fn realm>>(mut self, value: T) -> Config { self.0.realm = Some(value.into()); self @@ -52,12 +48,9 @@ impl AuthExtractorConfig for Config { } } -// Needs `fn main` to display complete example. -#[allow(clippy::needless_doctest_main)] /// Extractor for HTTP Bearer auth /// -/// # Example -/// +/// # Examples /// ``` /// use actix_web_httpauth::extractors::bearer::BearerAuth; /// @@ -70,25 +63,22 @@ impl AuthExtractorConfig for Config { /// from the [app data] in order to properly form the `WWW-Authenticate` /// response header. /// -/// ## Example -/// +/// # Examples /// ``` /// use actix_web::{web, App}; -/// use actix_web_httpauth::extractors::bearer::{BearerAuth, Config}; +/// use actix_web_httpauth::extractors::bearer::{self, BearerAuth}; /// /// async fn index(auth: BearerAuth) -> String { /// format!("Hello, {}!", auth.token()) /// } /// -/// fn main() { -/// let app = App::new() -/// .app_data( -/// Config::default() -/// .realm("Restricted area") -/// .scope("email photo"), -/// ) -/// .service(web::resource("/index.html").route(web::get().to(index))); -/// } +/// App::new() +/// .app_data( +/// bearer::Config::default() +/// .realm("Restricted area") +/// .scope("email photo"), +/// ) +/// .service(web::resource("/index.html").route(web::get().to(index))); /// ``` #[derive(Debug, Clone)] pub struct BearerAuth(authorization::Bearer); @@ -120,26 +110,6 @@ impl FromRequest for BearerAuth { } } -impl AuthExtractor for BearerAuth { - type Future = Ready>; - type Error = AuthenticationError; - - fn from_service_request(req: &ServiceRequest) -> Self::Future { - ready( - authorization::Authorization::::parse(req) - .map(|auth| BearerAuth(auth.into_scheme())) - .map_err(|_| { - let bearer = req - .app_data::() - .map(|config| config.0.clone()) - .unwrap_or_else(Default::default); - - AuthenticationError::new(bearer) - }), - ) - } -} - /// Extended error customization for HTTP `Bearer` auth. impl AuthenticationError { /// Attach `Error` to the current Authentication error. diff --git a/actix-web-httpauth/src/extractors/config.rs b/actix-web-httpauth/src/extractors/config.rs index 2faf869d9..761e989d1 100644 --- a/actix-web-httpauth/src/extractors/config.rs +++ b/actix-web-httpauth/src/extractors/config.rs @@ -1,10 +1,8 @@ use super::AuthenticationError; use crate::headers::www_authenticate::Challenge; -/// Trait implemented for types that provides configuration -/// for the authentication [extractors]. -/// -/// [extractors]: ./trait.AuthExtractor.html +/// Trait implemented for types that provides configuration for the authentication +/// [extractors](super::AuthExtractor). pub trait AuthExtractorConfig { /// Associated challenge type. type Inner: Challenge; diff --git a/actix-web-httpauth/src/extractors/errors.rs b/actix-web-httpauth/src/extractors/errors.rs index 7aab734a1..32bb6e5c1 100644 --- a/actix-web-httpauth/src/extractors/errors.rs +++ b/actix-web-httpauth/src/extractors/errors.rs @@ -1,16 +1,13 @@ -use std::error::Error; -use std::fmt; +use std::{error::Error, fmt}; -use actix_web::http::StatusCode; -use actix_web::{HttpResponse, ResponseError}; +use actix_web::{http::StatusCode, HttpResponse, ResponseError}; -use crate::headers::www_authenticate::Challenge; -use crate::headers::www_authenticate::WwwAuthenticate; +use crate::headers::www_authenticate::{Challenge, WwwAuthenticate}; /// Authentication error returned by authentication extractors. /// -/// Different extractors may extend `AuthenticationError` implementation -/// in order to provide access to inner challenge fields. +/// Different extractors may extend `AuthenticationError` implementation in order to provide access +/// inner challenge fields. #[derive(Debug)] pub struct AuthenticationError { challenge: C, @@ -35,8 +32,8 @@ impl AuthenticationError { /// Returns mutable reference to the inner status code. /// - /// Can be used to override returned status code, but by default - /// this lib tries to stick to the RFC, so it might be unreasonable. + /// Can be used to override returned status code, but by default this lib tries to stick to the + /// RFC, so it might be unreasonable. pub fn status_code_mut(&mut self) -> &mut StatusCode { &mut self.status_code } @@ -48,19 +45,18 @@ impl fmt::Display for AuthenticationError { } } -impl Error for AuthenticationError {} - -impl ResponseError for AuthenticationError { - fn error_response(&self) -> HttpResponse { - HttpResponse::build(self.status_code) - // TODO: Get rid of the `.clone()` - .insert_header(WwwAuthenticate(self.challenge.clone())) - .finish() - } +impl Error for AuthenticationError {} +impl ResponseError for AuthenticationError { fn status_code(&self) -> StatusCode { self.status_code } + + fn error_response(&self) -> HttpResponse { + HttpResponse::build(self.status_code()) + .insert_header(WwwAuthenticate(self.challenge.clone())) + .finish() + } } #[cfg(test)] @@ -72,12 +68,12 @@ mod tests { #[test] fn test_status_code_is_preserved_across_error_conversions() { - let ae: AuthenticationError = AuthenticationError::new(Basic::default()); + let ae = AuthenticationError::new(Basic::default()); let expected = ae.status_code; // Converting the AuthenticationError into a ResponseError should preserve the status code. - let e = Error::from(ae); - let re = e.as_response_error(); - assert_eq!(expected, re.status_code()); + let err = Error::from(ae); + let res_err = err.as_response_error(); + assert_eq!(expected, res_err.status_code()); } } diff --git a/actix-web-httpauth/src/extractors/mod.rs b/actix-web-httpauth/src/extractors/mod.rs index 9114b1570..d2098a940 100644 --- a/actix-web-httpauth/src/extractors/mod.rs +++ b/actix-web-httpauth/src/extractors/mod.rs @@ -1,15 +1,4 @@ -//! Type-safe authentication information extractors - -use std::{ - future::Future, - pin::Pin, - task::{Context, Poll}, -}; - -use actix_web::dev::ServiceRequest; -use actix_web::Error; -use futures_core::ready; -use pin_project_lite::pin_project; +//! Type-safe authentication information extractors. pub mod basic; pub mod bearer; @@ -18,86 +7,3 @@ mod errors; pub use self::config::AuthExtractorConfig; pub use self::errors::AuthenticationError; - -/// Trait implemented by types that can extract -/// HTTP authentication scheme credentials from the request. -/// -/// It is very similar to actix' `FromRequest` trait, -/// except it operates with a `ServiceRequest` struct instead, -/// therefore it can be used in the middlewares. -/// -/// You will not need it unless you want to implement your own -/// authentication scheme. -pub trait AuthExtractor: Sized { - /// The associated error which can be returned. - type Error: Into; - - /// Future that resolves into extracted credentials type. - type Future: Future>; - - /// Parse the authentication credentials from the actix' `ServiceRequest`. - fn from_service_request(req: &ServiceRequest) -> Self::Future; -} - -impl AuthExtractor for Option { - type Error = T::Error; - - type Future = AuthExtractorOptFut; - - fn from_service_request(req: &ServiceRequest) -> Self::Future { - let fut = T::from_service_request(req); - AuthExtractorOptFut { fut } - } -} - -pin_project! { - #[doc(hidden)] - pub struct AuthExtractorOptFut { - #[pin] - fut: F - } -} - -impl Future for AuthExtractorOptFut -where - F: Future>, -{ - type Output = Result, E>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let res = ready!(self.project().fut.poll(cx)); - Poll::Ready(Ok(res.ok())) - } -} - -impl AuthExtractor for Result { - type Error = T::Error; - - type Future = AuthExtractorResFut; - - fn from_service_request(req: &ServiceRequest) -> Self::Future { - AuthExtractorResFut { - fut: T::from_service_request(req), - } - } -} - -pin_project! { - #[doc(hidden)] - pub struct AuthExtractorResFut { - #[pin] - fut: F - } -} - -impl Future for AuthExtractorResFut -where - F: Future>, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let res = ready!(self.project().fut.poll(cx)); - Poll::Ready(Ok(res)) - } -} diff --git a/actix-web-httpauth/src/headers/authorization/errors.rs b/actix-web-httpauth/src/headers/authorization/errors.rs index bb79547c3..a203f6312 100644 --- a/actix-web-httpauth/src/headers/authorization/errors.rs +++ b/actix-web-httpauth/src/headers/authorization/errors.rs @@ -1,41 +1,42 @@ -use std::convert::From; -use std::error::Error; -use std::fmt; -use std::str; +use std::{convert::From, error::Error, fmt, str}; use actix_web::http::header; /// Possible errors while parsing `Authorization` header. /// -/// Should not be used directly unless you are implementing -/// your own [authentication scheme](./trait.Scheme.html). +/// Should not be used directly unless you are implementing your own +/// [authentication scheme](super::Scheme). #[derive(Debug)] pub enum ParseError { - /// Header value is malformed + /// Header value is malformed. Invalid, - /// Authentication scheme is missing + + /// Authentication scheme is missing. MissingScheme, - /// Required authentication field is missing + + /// Required authentication field is missing. MissingField(&'static str), - /// Unable to convert header into the str + + /// Unable to convert header into the str. ToStrError(header::ToStrError), - /// Malformed base64 string + + /// Malformed base64 string. Base64DecodeError(base64::DecodeError), - /// Malformed UTF-8 string + + /// Malformed UTF-8 string. Utf8Error(str::Utf8Error), } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let display = match self { - ParseError::Invalid => "Invalid header value".to_string(), - ParseError::MissingScheme => "Missing authorization scheme".to_string(), - ParseError::MissingField(_) => "Missing header field".to_string(), - ParseError::ToStrError(e) => e.to_string(), - ParseError::Base64DecodeError(e) => e.to_string(), - ParseError::Utf8Error(e) => e.to_string(), - }; - f.write_str(&display) + match self { + ParseError::Invalid => f.write_str("Invalid header value"), + ParseError::MissingScheme => f.write_str("Missing authorization scheme"), + ParseError::MissingField(field) => write!(f, "Missing header field ({})", field), + ParseError::ToStrError(err) => fmt::Display::fmt(err, f), + ParseError::Base64DecodeError(err) => fmt::Display::fmt(err, f), + ParseError::Utf8Error(err) => fmt::Display::fmt(err, f), + } } } @@ -45,25 +46,27 @@ impl Error for ParseError { ParseError::Invalid => None, ParseError::MissingScheme => None, ParseError::MissingField(_) => None, - ParseError::ToStrError(e) => Some(e), - ParseError::Base64DecodeError(e) => Some(e), - ParseError::Utf8Error(e) => Some(e), + ParseError::ToStrError(err) => Some(err), + ParseError::Base64DecodeError(err) => Some(err), + ParseError::Utf8Error(err) => Some(err), } } } impl From for ParseError { - fn from(e: header::ToStrError) -> Self { - ParseError::ToStrError(e) + fn from(err: header::ToStrError) -> Self { + ParseError::ToStrError(err) } } + impl From for ParseError { - fn from(e: base64::DecodeError) -> Self { - ParseError::Base64DecodeError(e) + fn from(err: base64::DecodeError) -> Self { + ParseError::Base64DecodeError(err) } } + impl From for ParseError { - fn from(e: str::Utf8Error) -> Self { - ParseError::Utf8Error(e) + fn from(err: str::Utf8Error) -> Self { + ParseError::Utf8Error(err) } } diff --git a/actix-web-httpauth/src/headers/authorization/header.rs b/actix-web-httpauth/src/headers/authorization/header.rs index 3f0351ac7..53cdb3978 100644 --- a/actix-web-httpauth/src/headers/authorization/header.rs +++ b/actix-web-httpauth/src/headers/authorization/header.rs @@ -1,27 +1,25 @@ use std::fmt; -use actix_web::error::ParseError; -use actix_web::http::header::{Header, HeaderName, HeaderValue, TryIntoHeaderValue, AUTHORIZATION}; -use actix_web::HttpMessage; +use actix_web::{ + error::ParseError, + http::header::{Header, HeaderName, HeaderValue, TryIntoHeaderValue, AUTHORIZATION}, + HttpMessage, +}; use crate::headers::authorization::scheme::Scheme; /// `Authorization` header, defined in [RFC 7235](https://tools.ietf.org/html/rfc7235#section-4.2) /// -/// The "Authorization" header field allows a user agent to authenticate -/// itself with an origin server -- usually, but not necessarily, after -/// receiving a 401 (Unauthorized) response. Its value consists of -/// credentials containing the authentication information of the user -/// agent for the realm of the resource being requested. +/// The "Authorization" header field allows a user agent to authenticate itself with an origin +/// server—usually, but not necessarily, after receiving a 401 (Unauthorized) response. Its value +/// consists of credentials containing the authentication information of the user agent for the +/// realm of the resource being requested. /// -/// `Authorization` header is generic over [authentication -/// scheme](./trait.Scheme.html). -/// -/// # Example +/// `Authorization` is generic over an [authentication scheme](Scheme). /// +/// # Examples /// ``` -/// # use actix_web::http::header::Header; -/// # use actix_web::{HttpRequest, Result}; +/// # use actix_web::{HttpRequest, Result, http::header::Header}; /// # use actix_web_httpauth::headers::authorization::{Authorization, Basic}; /// fn handler(req: HttpRequest) -> Result { /// let auth = Authorization::::parse(&req)?; @@ -29,49 +27,40 @@ use crate::headers::authorization::scheme::Scheme; /// Ok(format!("Hello, {}!", auth.as_ref().user_id())) /// } /// ``` -#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Authorization(S); -impl Authorization -where - S: Scheme, -{ - /// Consumes `Authorization` header and returns inner [`Scheme`] - /// implementation. - /// - /// [`Scheme`]: ./trait.Scheme.html +impl Authorization { + /// Consumes `Authorization` header and returns inner [`Scheme`] implementation. pub fn into_scheme(self) -> S { self.0 } } -impl From for Authorization -where - S: Scheme, -{ +impl From for Authorization { fn from(scheme: S) -> Authorization { Authorization(scheme) } } -impl AsRef for Authorization -where - S: Scheme, -{ +impl AsRef for Authorization { fn as_ref(&self) -> &S { &self.0 } } -impl AsMut for Authorization -where - S: Scheme, -{ +impl AsMut for Authorization { fn as_mut(&mut self) -> &mut S { &mut self.0 } } +impl fmt::Display for Authorization { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + impl Header for Authorization { #[inline] fn name() -> HeaderName { @@ -79,7 +68,7 @@ impl Header for Authorization { } fn parse(msg: &T) -> Result { - let header = msg.headers().get(AUTHORIZATION).ok_or(ParseError::Header)?; + let header = msg.headers().get(Self::name()).ok_or(ParseError::Header)?; let scheme = S::parse(header).map_err(|_| ParseError::Header)?; Ok(Authorization(scheme)) @@ -93,9 +82,3 @@ impl TryIntoHeaderValue for Authorization { self.0.try_into_value() } } - -impl fmt::Display for Authorization { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} diff --git a/actix-web-httpauth/src/headers/authorization/mod.rs b/actix-web-httpauth/src/headers/authorization/mod.rs index b02e0531d..cd9cb01c8 100644 --- a/actix-web-httpauth/src/headers/authorization/mod.rs +++ b/actix-web-httpauth/src/headers/authorization/mod.rs @@ -1,4 +1,4 @@ -//! `Authorization` header and various auth schemes +//! `Authorization` header and various auth schemes. mod errors; mod header; @@ -6,6 +6,4 @@ mod scheme; pub use self::errors::ParseError; pub use self::header::Authorization; -pub use self::scheme::basic::Basic; -pub use self::scheme::bearer::Bearer; -pub use self::scheme::Scheme; +pub use self::scheme::{basic::Basic, bearer::Bearer, Scheme}; diff --git a/actix-web-httpauth/src/headers/authorization/scheme/basic.rs b/actix-web-httpauth/src/headers/authorization/scheme/basic.rs index 0252f581b..da7a94f8b 100644 --- a/actix-web-httpauth/src/headers/authorization/scheme/basic.rs +++ b/actix-web-httpauth/src/headers/authorization/scheme/basic.rs @@ -8,7 +8,7 @@ use actix_web::{ use crate::headers::authorization::{errors::ParseError, Scheme}; /// Credentials for `Basic` authentication scheme, defined in [RFC 7617](https://tools.ietf.org/html/rfc7617) -#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Basic { user_id: Cow<'static, str>, password: Option>, @@ -18,8 +18,7 @@ impl Basic { /// Creates `Basic` credentials with provided `user_id` and optional /// `password`. /// - /// ## Example - /// + /// # Examples /// ``` /// # use actix_web_httpauth::headers::authorization::Basic; /// let credentials = Basic::new("Alladin", Some("open sesame")); @@ -36,13 +35,13 @@ impl Basic { } /// Returns client's user-ID. - pub fn user_id(&self) -> &Cow<'static, str> { - &self.user_id + pub fn user_id(&self) -> &str { + self.user_id.as_ref() } /// Returns client's password if provided. - pub fn password(&self) -> Option<&Cow<'static, str>> { - self.password.as_ref() + pub fn password(&self) -> Option<&str> { + self.password.as_deref() } } @@ -66,6 +65,7 @@ impl Scheme for Basic { .next() .ok_or(ParseError::MissingField("user_id")) .map(|user_id| user_id.to_string().into())?; + let password = credentials .next() .ok_or(ParseError::MissingField("password")) diff --git a/actix-web-httpauth/src/headers/authorization/scheme/bearer.rs b/actix-web-httpauth/src/headers/authorization/scheme/bearer.rs index 1efb1e24d..4fac1888c 100644 --- a/actix-web-httpauth/src/headers/authorization/scheme/bearer.rs +++ b/actix-web-httpauth/src/headers/authorization/scheme/bearer.rs @@ -1,16 +1,17 @@ -use std::borrow::Cow; -use std::fmt; +use std::{borrow::Cow, fmt}; -use actix_web::http::header::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue}; -use actix_web::web::{BufMut, BytesMut}; +use actix_web::{ + http::header::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue}, + web::{BufMut, BytesMut}, +}; -use crate::headers::authorization::errors::ParseError; -use crate::headers::authorization::scheme::Scheme; +use crate::headers::authorization::{errors::ParseError, scheme::Scheme}; -/// Credentials for `Bearer` authentication scheme, defined in [RFC6750](https://tools.ietf.org/html/rfc6750) +/// Credentials for `Bearer` authentication scheme, defined in [RFC 6750]. /// -/// Should be used in combination with -/// [`Authorization`](./struct.Authorization.html) header. +/// Should be used in combination with [`Authorization`](super::Authorization) header. +/// +/// [RFC 6750]: https://tools.ietf.org/html/rfc6750 #[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] pub struct Bearer { token: Cow<'static, str>, @@ -19,8 +20,7 @@ pub struct Bearer { impl Bearer { /// Creates new `Bearer` credentials with the token provided. /// - /// ## Example - /// + /// # Example /// ``` /// # use actix_web_httpauth::headers::authorization::Bearer; /// let credentials = Bearer::new("mF_9.B5f-4.1JqM"); @@ -35,8 +35,8 @@ impl Bearer { } /// Gets reference to the credentials token. - pub fn token(&self) -> &Cow<'static, str> { - &self.token + pub fn token(&self) -> &str { + self.token.as_ref() } } @@ -48,8 +48,9 @@ impl Scheme for Bearer { } let mut parts = header.to_str()?.splitn(2, ' '); + match parts.next() { - Some(scheme) if scheme == "Bearer" => (), + Some(scheme) if scheme == "Bearer" => {} _ => return Err(ParseError::MissingScheme), } diff --git a/actix-web-httpauth/src/headers/authorization/scheme/mod.rs b/actix-web-httpauth/src/headers/authorization/scheme/mod.rs index 8d22c112b..63a8ed64d 100644 --- a/actix-web-httpauth/src/headers/authorization/scheme/mod.rs +++ b/actix-web-httpauth/src/headers/authorization/scheme/mod.rs @@ -7,9 +7,8 @@ pub mod bearer; use crate::headers::authorization::errors::ParseError; -/// Authentication scheme for [`Authorization`](./struct.Authorization.html) -/// header. +/// Authentication scheme for [`Authorization`](super::Authorization) header. pub trait Scheme: TryIntoHeaderValue + Debug + Display + Clone + Send + Sync { - /// Try to parse the authentication scheme from the `Authorization` header. + /// Try to parse an authentication scheme from the `Authorization` header. fn parse(header: &HeaderValue) -> Result; } diff --git a/actix-web-httpauth/src/headers/mod.rs b/actix-web-httpauth/src/headers/mod.rs index 5e38e661a..bf446efd1 100644 --- a/actix-web-httpauth/src/headers/mod.rs +++ b/actix-web-httpauth/src/headers/mod.rs @@ -1,4 +1,4 @@ -//! Typed HTTP headers +//! Typed HTTP headers. pub mod authorization; pub mod www_authenticate; diff --git a/actix-web-httpauth/src/headers/www_authenticate/challenge/basic.rs b/actix-web-httpauth/src/headers/www_authenticate/challenge/basic.rs index a2fba66a5..1bef1d000 100644 --- a/actix-web-httpauth/src/headers/www_authenticate/challenge/basic.rs +++ b/actix-web-httpauth/src/headers/www_authenticate/challenge/basic.rs @@ -1,12 +1,11 @@ -//! Challenge for the "Basic" HTTP Authentication Scheme +//! Challenge for the "Basic" HTTP Authentication Scheme. -use std::borrow::Cow; -use std::default::Default; -use std::fmt; -use std::str; +use std::{borrow::Cow, fmt, str}; -use actix_web::http::header::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue}; -use actix_web::web::{BufMut, Bytes, BytesMut}; +use actix_web::{ + http::header::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue}, + web::{BufMut, Bytes, BytesMut}, +}; use super::Challenge; use crate::utils; @@ -14,8 +13,7 @@ use crate::utils; /// Challenge for [`WWW-Authenticate`] header with HTTP Basic auth scheme, /// described in [RFC 7617](https://tools.ietf.org/html/rfc7617) /// -/// ## Example -/// +/// # Examples /// ``` /// # use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer}; /// use actix_web_httpauth::headers::www_authenticate::basic::Basic; @@ -40,8 +38,7 @@ pub struct Basic { impl Basic { /// Creates new `Basic` challenge with an empty `realm` field. /// - /// ## Example - /// + /// # Examples /// ``` /// # use actix_web_httpauth::headers::www_authenticate::basic::Basic; /// let challenge = Basic::new(); @@ -52,7 +49,7 @@ impl Basic { /// Creates new `Basic` challenge from the provided `realm` field value. /// - /// ## Examples + /// # Examples /// /// ``` /// # use actix_web_httpauth::headers::www_authenticate::basic::Basic; diff --git a/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/builder.rs b/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/builder.rs index b4bf11455..8b8d9e8ff 100644 --- a/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/builder.rs +++ b/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/builder.rs @@ -4,15 +4,15 @@ use super::{Bearer, Error}; /// Builder for the [`Bearer`] challenge. /// -/// It is up to implementor to fill all required fields, -/// neither this `Builder` or [`Bearer`] does not provide any validation. -/// -/// [`Bearer`]: struct.Bearer.html +/// It is up to implementor to fill all required fields, neither this `Builder` nor [`Bearer`] +/// provide any validation. #[derive(Debug, Default)] pub struct BearerBuilder(Bearer); impl BearerBuilder { - /// Provides the `scope` attribute, as defined in [RFC6749, Section 3.3](https://tools.ietf.org/html/rfc6749#section-3.3) + /// Provides the `scope` attribute, as defined in [RFC 6749 §3.3]. + /// + /// [RFC 6749 §3.3]: https://tools.ietf.org/html/rfc6749#section-3.3 pub fn scope(mut self, value: T) -> Self where T: Into>, @@ -21,7 +21,9 @@ impl BearerBuilder { self } - /// Provides the `realm` attribute, as defined in [RFC2617](https://tools.ietf.org/html/rfc2617) + /// Provides the `realm` attribute, as defined in [RFC 2617]. + /// + /// [RFC 2617]: https://tools.ietf.org/html/rfc2617 pub fn realm(mut self, value: T) -> Self where T: Into>, @@ -30,13 +32,17 @@ impl BearerBuilder { self } - /// Provides the `error` attribute, as defined in [RFC6750, Section 3.1](https://tools.ietf.org/html/rfc6750#section-3.1) + /// Provides the `error` attribute, as defined in [RFC 6750, Section 3.1]. + /// + /// [RFC 6750 §3.1]: https://tools.ietf.org/html/rfc6750#section-3.1 pub fn error(mut self, value: Error) -> Self { self.0.error = Some(value); self } - /// Provides the `error_description` attribute, as defined in [RFC6750, Section 3](https://tools.ietf.org/html/rfc6750#section-3) + /// Provides the `error_description` attribute, as defined in [RFC 6750, Section 3]. + /// + /// [RFC 6750 §3]: https://tools.ietf.org/html/rfc6750#section-3 pub fn error_description(mut self, value: T) -> Self where T: Into>, @@ -45,9 +51,11 @@ impl BearerBuilder { self } - /// Provides the `error_uri` attribute, as defined in [RFC6750, Section 3](https://tools.ietf.org/html/rfc6750#section-3) + /// Provides the `error_uri` attribute, as defined in [RFC 6750 §3]. /// /// It is up to implementor to provide properly-formed absolute URI. + /// + /// [RFC 6750 §3](https://tools.ietf.org/html/rfc6750#section-3) pub fn error_uri(mut self, value: T) -> Self where T: Into>, diff --git a/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/challenge.rs b/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/challenge.rs index 82526c2a4..3c420cee5 100644 --- a/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/challenge.rs +++ b/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/challenge.rs @@ -1,19 +1,17 @@ -use std::borrow::Cow; -use std::fmt; -use std::str; +use std::{borrow::Cow, fmt, str}; -use actix_web::http::header::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue}; -use actix_web::web::{BufMut, Bytes, BytesMut}; +use actix_web::{ + http::header::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue}, + web::{BufMut, Bytes, BytesMut}, +}; use super::super::Challenge; use super::{BearerBuilder, Error}; use crate::utils; -/// Challenge for [`WWW-Authenticate`] header with HTTP Bearer auth scheme, -/// described in [RFC 6750](https://tools.ietf.org/html/rfc6750#section-3) -/// -/// ## Example +/// Challenge for [`WWW-Authenticate`] header with HTTP Bearer auth scheme, described in [RFC 6750]. /// +/// # Examples /// ``` /// # use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer}; /// use actix_web_httpauth::headers::www_authenticate::bearer::{ @@ -36,8 +34,9 @@ use crate::utils; /// } /// ``` /// -/// [`WWW-Authenticate`]: ../struct.WwwAuthenticate.html -#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Clone)] +/// [`WWW-Authenticate`]: crate::headers::www_authenticate::WwwAuthenticate +/// [RFC 6750]: https://tools.ietf.org/html/rfc6750#section-3 +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Bearer { pub(crate) scope: Option>, pub(crate) realm: Option>, @@ -49,8 +48,7 @@ pub struct Bearer { impl Bearer { /// Creates the builder for `Bearer` challenge. /// - /// ## Example - /// + /// # Examples /// ``` /// # use actix_web_httpauth::headers::www_authenticate::bearer::{Bearer}; /// let challenge = Bearer::build() @@ -71,10 +69,12 @@ impl Challenge for Bearer { .as_ref() .map_or(0, |desc| desc.len() + 20) + self.error_uri.as_ref().map_or(0, |url| url.len() + 12); + let capacity = 6 + self.realm.as_ref().map_or(0, |realm| realm.len() + 9) + self.scope.as_ref().map_or(0, |scope| scope.len() + 9) + desc_uri_required; + let mut buffer = BytesMut::with_capacity(capacity); buffer.put(&b"Bearer"[..]); @@ -94,9 +94,11 @@ impl Challenge for Bearer { let error_repr = error.as_str(); let remaining = buffer.remaining_mut(); let required = desc_uri_required + error_repr.len() + 9; // 9 is for `" error=\"\""` + if remaining < required { buffer.reserve(required); } + buffer.put(&b" error=\""[..]); utils::put_quoted(&mut buffer, error_repr); buffer.put_u8(b'"') @@ -121,6 +123,7 @@ impl Challenge for Bearer { impl fmt::Display for Bearer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { let bytes = self.to_bytes(); + let repr = str::from_utf8(&bytes) // Should not happen since challenges are crafted manually // from `&'static str`'s and Strings diff --git a/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/errors.rs b/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/errors.rs index fb2f9a3dd..fc4e60208 100644 --- a/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/errors.rs +++ b/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/errors.rs @@ -2,21 +2,20 @@ use std::fmt; use actix_web::http::StatusCode; -/// Bearer authorization error types, described in [RFC 6750](https://tools.ietf.org/html/rfc6750#section-3.1) -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +/// Bearer authorization error types, described in [RFC 6750]. +/// +/// [RFC 6750]: https://tools.ietf.org/html/rfc6750#section-3.1 +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Error { - /// The request is missing a required parameter, includes an unsupported - /// parameter or parameter value, repeats the same parameter, uses more - /// than one method for including an access token, or is otherwise - /// malformed. + /// The request is missing a required parameter, includes an unsupported parameter or parameter + /// value, repeats the same parameter, uses more than one method for including an access token, + /// or is otherwise malformed. InvalidRequest, - /// The access token provided is expired, revoked, malformed, or invalid - /// for other reasons. + /// The access token provided is expired, revoked, malformed, or invalid for other reasons. InvalidToken, - /// The request requires higher privileges than provided by the access - /// token. + /// The request requires higher privileges than provided by the access token. InsufficientScope, } diff --git a/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/mod.rs b/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/mod.rs index d51237c45..effb1db3b 100644 --- a/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/mod.rs +++ b/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/mod.rs @@ -1,4 +1,4 @@ -//! Challenge for the "Bearer" HTTP Authentication Scheme +//! Challenge for the "Bearer" HTTP Authentication Scheme. mod builder; mod challenge; @@ -9,4 +9,19 @@ pub use self::challenge::Bearer; pub use self::errors::Error; #[cfg(test)] -mod tests; +mod tests { + use super::*; + + #[test] + fn to_bytes() { + let b = Bearer::build() + .error(Error::InvalidToken) + .error_description("Subject 8740827c-2e0a-447b-9716-d73042e4039d not found") + .finish(); + + assert_eq!( + "Bearer error=\"invalid_token\" error_description=\"Subject 8740827c-2e0a-447b-9716-d73042e4039d not found\"", + format!("{}", b) + ); + } +} diff --git a/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/tests.rs b/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/tests.rs deleted file mode 100644 index 03f088e17..000000000 --- a/actix-web-httpauth/src/headers/www_authenticate/challenge/bearer/tests.rs +++ /dev/null @@ -1,14 +0,0 @@ -use super::*; - -#[test] -fn to_bytes() { - let b = Bearer::build() - .error(Error::InvalidToken) - .error_description("Subject 8740827c-2e0a-447b-9716-d73042e4039d not found") - .finish(); - - assert_eq!( - "Bearer error=\"invalid_token\" error_description=\"Subject 8740827c-2e0a-447b-9716-d73042e4039d not found\"", - format!("{}", b) - ); -} diff --git a/actix-web-httpauth/src/headers/www_authenticate/header.rs b/actix-web-httpauth/src/headers/www_authenticate/header.rs index e7b20be6b..5d3f84322 100644 --- a/actix-web-httpauth/src/headers/www_authenticate/header.rs +++ b/actix-web-httpauth/src/headers/www_authenticate/header.rs @@ -6,15 +6,17 @@ use actix_web::{ use super::Challenge; -/// `WWW-Authenticate` header, described in [RFC 7235](https://tools.ietf.org/html/rfc7235#section-4.1) +/// `WWW-Authenticate` header, described in [RFC 7235]. /// -/// This header is generic over [Challenge](./trait.Challenge.html) trait, -/// see [Basic](./basic/struct.Basic.html) and -/// [Bearer](./bearer/struct.Bearer.html) challenges for details. -#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Clone)] +/// This header is generic over the [`Challenge`] trait, see [`Basic`](super::basic::Basic) and +/// [`Bearer`](super::bearer::Bearer) challenges for details. +/// +/// [RFC 7235]: https://tools.ietf.org/html/rfc7235#section-4.1 +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WwwAuthenticate(pub C); impl Header for WwwAuthenticate { + #[inline] fn name() -> HeaderName { WWW_AUTHENTICATE } diff --git a/actix-web-httpauth/src/headers/www_authenticate/mod.rs b/actix-web-httpauth/src/headers/www_authenticate/mod.rs index 6262f050a..f7ed690cc 100644 --- a/actix-web-httpauth/src/headers/www_authenticate/mod.rs +++ b/actix-web-httpauth/src/headers/www_authenticate/mod.rs @@ -1,9 +1,7 @@ -//! `WWW-Authenticate` header and various auth challenges +//! `WWW-Authenticate` header and various auth challenges. mod challenge; mod header; -pub use self::challenge::basic; -pub use self::challenge::bearer; -pub use self::challenge::Challenge; +pub use self::challenge::{basic, bearer, Challenge}; pub use self::header::WwwAuthenticate; diff --git a/actix-web-httpauth/src/lib.rs b/actix-web-httpauth/src/lib.rs index 929bc95d7..628ea8e73 100644 --- a/actix-web-httpauth/src/lib.rs +++ b/actix-web-httpauth/src/lib.rs @@ -1,4 +1,4 @@ -//! HTTP authentication schemes for [actix-web](https://actix.rs). +//! HTTP authentication schemes for [Actix Web](https://actix.rs). //! //! Provides: //! - Typed [Authorization] and [WWW-Authenticate] headers @@ -6,14 +6,13 @@ //! - [Middleware] for easier authorization checking //! //! ## Supported schemes +//! - `Bearer` as defined in [RFC 6750](https://tools.ietf.org/html/rfc6750). +//! - `Basic` as defined in [RFC 7617](https://tools.ietf.org/html/rfc7617). //! -//! - `Basic`, as defined in [RFC7617](https://tools.ietf.org/html/rfc7617) -//! - `Bearer`, as defined in [RFC6750](https://tools.ietf.org/html/rfc6750) -//! -//! [Authorization]: `crate::headers::authorization::Authorization` -//! [WWW-Authenticate]: `crate::headers::www_authenticate::WwwAuthenticate` -//! [Extractors]: https://actix.rs/docs/extractors/ -//! [Middleware]: ./middleware +//! [Authorization]: `self::headers::authorization::Authorization` +//! [WWW-Authenticate]: `self::headers::www_authenticate::WwwAuthenticate` +//! [Extractors]: https://actix.rs/docs/extractors +//! [Middleware]: self::middleware #![forbid(unsafe_code)] #![deny(rust_2018_idioms, nonstandard_style)] diff --git a/actix-web-httpauth/src/middleware.rs b/actix-web-httpauth/src/middleware.rs index 68a41ea8b..4eb72608c 100644 --- a/actix-web-httpauth/src/middleware.rs +++ b/actix-web-httpauth/src/middleware.rs @@ -12,12 +12,12 @@ use std::{ use actix_web::{ body::{EitherBody, MessageBody}, dev::{Service, ServiceRequest, ServiceResponse, Transform}, - Error, + Error, FromRequest, }; use futures_core::ready; use futures_util::future::{self, FutureExt as _, LocalBoxFuture, TryFutureExt as _}; -use crate::extractors::{basic, bearer, AuthExtractor}; +use crate::extractors::{basic, bearer}; /// Middleware for checking HTTP authentication. /// @@ -29,7 +29,7 @@ use crate::extractors::{basic, bearer, AuthExtractor}; #[derive(Debug, Clone)] pub struct HttpAuthentication where - T: AuthExtractor, + T: FromRequest, { process_fn: Arc, _extractor: PhantomData, @@ -37,7 +37,7 @@ where impl HttpAuthentication where - T: AuthExtractor, + T: FromRequest, F: Fn(ServiceRequest, T) -> O, O: Future>, { @@ -58,12 +58,10 @@ where { /// Construct `HttpAuthentication` middleware for the HTTP "Basic" authentication scheme. /// - /// # Example + /// # Examples /// ``` - /// # use actix_web::Error; - /// # use actix_web::dev::ServiceRequest; - /// # use actix_web_httpauth::middleware::HttpAuthentication; - /// # use actix_web_httpauth::extractors::basic::BasicAuth; + /// # use actix_web::{Error, dev::ServiceRequest}; + /// # use actix_web_httpauth::{extractors::basic::BasicAuth, middleware::HttpAuthentication}; /// // In this example validator returns immediately, but since it is required to return /// // anything that implements `IntoFuture` trait, it can be extended to query database or to /// // do something else in a async manner. @@ -89,20 +87,23 @@ where { /// Construct `HttpAuthentication` middleware for the HTTP "Bearer" authentication scheme. /// - /// # Example + /// # Examples /// ``` - /// # use actix_web::Error; - /// # use actix_web::dev::ServiceRequest; - /// # use actix_web_httpauth::middleware::HttpAuthentication; - /// # use actix_web_httpauth::extractors::bearer::{Config, BearerAuth}; - /// # use actix_web_httpauth::extractors::{AuthenticationError, AuthExtractorConfig}; - /// async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result { + /// # use actix_web::{Error, dev::ServiceRequest}; + /// # use actix_web_httpauth::{ + /// # extractors::{AuthenticationError, AuthExtractorConfig, bearer::{self, BearerAuth}}, + /// # middleware::HttpAuthentication, + /// # }; + /// async fn validator( + /// req: ServiceRequest, + /// credentials: BearerAuth + /// ) -> Result { /// if credentials.token() == "mF_9.B5f-4.1JqM" { /// Ok(req) /// } else { - /// let config = req.app_data::() - /// .map(|data| data.clone()) - /// .unwrap_or_else(Default::default) + /// let config = req.app_data::() + /// .cloned() + /// .unwrap_or_default() /// .scope("urn:example:channel=HBO&urn:example:rating=G,PG-13"); /// /// Err((AuthenticationError::from(config).into(), req)) @@ -122,7 +123,7 @@ where S::Future: 'static, F: Fn(ServiceRequest, T) -> O + 'static, O: Future> + 'static, - T: AuthExtractor + 'static, + T: FromRequest + 'static, B: MessageBody + 'static, { type Response = ServiceResponse>; @@ -143,7 +144,7 @@ where #[doc(hidden)] pub struct AuthenticationMiddleware where - T: AuthExtractor, + T: FromRequest, { service: Rc, process_fn: Arc, @@ -156,18 +157,17 @@ where S::Future: 'static, F: Fn(ServiceRequest, T) -> O + 'static, O: Future> + 'static, - T: AuthExtractor + 'static, + T: FromRequest + 'static, B: MessageBody + 'static, { type Response = ServiceResponse>; type Error = S::Error; type Future = LocalBoxFuture<'static, Result>; - actix_service::forward_ready!(service); + actix_web::dev::forward_ready!(service); fn call(&self, req: ServiceRequest) -> Self::Future { let process_fn = Arc::clone(&self.process_fn); - let service = Rc::clone(&self.service); async move { @@ -193,7 +193,7 @@ where struct Extract { req: Option, - f: Option>>, + fut: Option>>, _extractor: PhantomData T>, } @@ -201,7 +201,7 @@ impl Extract { pub fn new(req: ServiceRequest) -> Self { Extract { req: Some(req), - f: None, + fut: None, _extractor: PhantomData, } } @@ -209,25 +209,25 @@ impl Extract { impl Future for Extract where - T: AuthExtractor, + T: FromRequest, T::Future: 'static, T::Error: 'static, { type Output = Result<(ServiceRequest, T), (Error, ServiceRequest)>; fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { - if self.f.is_none() { - let req = self.req.as_ref().expect("Extract future was polled twice!"); - let f = T::from_service_request(req).map_err(Into::into); - self.f = Some(f.boxed_local()); + if self.fut.is_none() { + let req = self.req.as_mut().expect("Extract future was polled twice!"); + let fut = req.extract::().map_err(Into::into); + self.fut = Some(fut.boxed_local()); } - let f = self - .f + let fut = self + .fut .as_mut() .expect("Extraction future should be initialized at this point"); - let credentials = ready!(f.as_mut().poll(ctx)).map_err(|err| { + let credentials = ready!(fut.as_mut().poll(ctx)).map_err(|err| { ( err, // returning request allows a proper error response to be created @@ -242,15 +242,17 @@ where #[cfg(test)] mod tests { - use actix_service::{into_service, Service}; - use actix_web::error::ErrorForbidden; - use actix_web::http::StatusCode; - use actix_web::test::TestRequest; - use actix_web::{error, web, App, HttpResponse}; + use actix_service::into_service; + use actix_web::{ + dev::Service, + error::{self, ErrorForbidden}, + http::StatusCode, + test::TestRequest, + web, App, HttpResponse, + }; use super::*; - use crate::extractors::basic::BasicAuth; - use crate::extractors::bearer::BearerAuth; + use crate::extractors::{basic::BasicAuth, bearer::BearerAuth}; /// This is a test for https://github.com/actix/actix-extras/issues/10 #[actix_web::test] @@ -343,7 +345,7 @@ mod tests { Ok::(req.into_response(HttpResponse::Ok().finish())) })), process_fn: Arc::new( - |req, auth: Result::Error>| { + |req, auth: Result::Error>| { assert!(auth.is_err()); async { Ok(req) } }, diff --git a/actix-web-httpauth/src/utils.rs b/actix-web-httpauth/src/utils.rs index 01c941f67..05d61cdaa 100644 --- a/actix-web-httpauth/src/utils.rs +++ b/actix-web-httpauth/src/utils.rs @@ -27,12 +27,13 @@ impl<'a> Iterator for Quoted<'a> { fn next(&mut self) -> Option { match self.state { State::YieldStr => match self.inner.next() { - Some(s) => { + Some(val) => { self.state = State::YieldQuote; - Some(s) + Some(val) } None => None, }, + State::YieldQuote => match self.inner.peek() { Some(_) => { self.state = State::YieldStr; @@ -44,9 +45,9 @@ impl<'a> Iterator for Quoted<'a> { } } -/// Tries to quote the quotes in the passed `value` -pub fn put_quoted(buf: &mut BytesMut, value: &str) { - for part in Quoted::new(value) { +/// Escapes the quotes in `val`. +pub fn put_quoted(buf: &mut BytesMut, val: &str) { + for part in Quoted::new(val) { buf.extend_from_slice(part.as_bytes()); } }