1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-02-02 10:59:03 +01:00

Release 0.3.0

This commit is contained in:
svartalf 2019-06-05 18:52:47 +03:00
parent aad60744ed
commit 0ebfd790fc
8 changed files with 167 additions and 113 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web-httpauth"
version = "0.3.0-alpha.2"
version = "0.3.0"
authors = ["svartalf <self@svartalf.info>"]
description = "HTTP authentication schemes for actix-web"
readme = "README.md"
@ -14,7 +14,7 @@ exclude = [".travis.yml", ".gitignore"]
edition = "2018"
[dependencies]
actix-web = { version = "1.0.0-beta.5", default_features = false }
actix-web = { version = "^1.0", default_features = false }
futures = "0.1"
actix-service = "0.4.0"
bytes = "0.4"

View File

@ -2,13 +2,19 @@
[![Latest Version](https://img.shields.io/crates/v/actix-web-httpauth.svg)](https://crates.io/crates/actix-web-httpauth)
[![Latest Version](https://docs.rs/actix-web-httpauth/badge.svg)](https://docs.rs/actix-web-httpauth)
[![dependency status](https://deps.rs/crate/actix-web-httpauth/0.3.0/status.svg)](https://deps.rs/crate/actix-web-httpauth/0.3.0)
![Build Status](https://travis-ci.org/svartalf/actix-web-httpauth.svg?branch=master)
![Apache 2.0 OR MIT licensed](https://img.shields.io/badge/license-Apache2.0%2FMIT-blue.svg)
HTTP authentication schemes for [actix-web](https://github.com/actix/actix-web) framework.
All supported schemas are actix [Extractors](https://docs.rs/actix-web/0.6.7/actix_web/trait.FromRequest.html),
and can be used both in middlewares and request handlers, check the `examples/` folder.
Provides:
* typed [Authorization] and [WWW-Authenticate] headers
* [extractors] for an [Authorization] header
* [middleware] for easier authorization checking
All supported schemas are actix [Extractors](https://docs.rs/actix-web/1.0.0/actix_web/trait.FromRequest.html),
and can be used both in the middlewares and request handlers.
## Supported schemes

View File

@ -10,7 +10,6 @@ use bytes::{BufMut, BytesMut};
use crate::headers::authorization::errors::ParseError;
use crate::headers::authorization::Scheme;
use crate::utils;
/// Credentials for `Basic` authentication scheme, defined in [RFC 7617](https://tools.ietf.org/html/rfc7617)
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
@ -107,13 +106,14 @@ impl IntoHeaderValue for Basic {
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
let mut credentials = BytesMut::with_capacity(
self.user_id.len()
+ 1
+ 1 // ':'
+ self.password.as_ref().map_or(0, |pwd| pwd.len()),
);
utils::put_cow(&mut credentials, &self.user_id);
credentials.extend_from_slice(self.user_id.as_bytes());
credentials.put_u8(b':');
if let Some(ref password) = self.password {
utils::put_cow(&mut credentials, password);
credentials.extend_from_slice(password.as_bytes());
}
// TODO: It would be nice not to allocate new `String` here but write

View File

@ -8,7 +8,6 @@ use bytes::{BufMut, BytesMut};
use crate::headers::authorization::errors::ParseError;
use crate::headers::authorization::scheme::Scheme;
use crate::utils;
/// Credentials for `Bearer` authentication scheme, defined in [RFC6750](https://tools.ietf.org/html/rfc6750)
///
@ -82,7 +81,7 @@ impl IntoHeaderValue for Bearer {
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
let mut buffer = BytesMut::with_capacity(7 + self.token.len());
buffer.put("Bearer ");
utils::put_cow(&mut buffer, &self.token);
buffer.extend_from_slice(self.token.as_bytes());
HeaderValue::from_shared(buffer.freeze())
}

View File

@ -85,7 +85,7 @@ impl Challenge for Basic {
buffer.put("Basic");
if let Some(ref realm) = self.realm {
buffer.put(" realm=\"");
utils::put_cow(&mut buffer, realm);
utils::put_quoted(&mut buffer, realm);
buffer.put("\"");
}

View File

@ -82,13 +82,13 @@ impl Challenge for Bearer {
if let Some(ref realm) = self.realm {
buffer.put(" realm=\"");
utils::put_cow(&mut buffer, realm);
utils::put_quoted(&mut buffer, realm);
buffer.put("\"");
}
if let Some(ref scope) = self.scope {
buffer.put(" scope=\"");
utils::put_cow(&mut buffer, scope);
utils::put_quoted(&mut buffer, scope);
buffer.put("\"");
}
@ -100,19 +100,19 @@ impl Challenge for Bearer {
buffer.reserve(required);
}
buffer.put(" error=\"");
buffer.put(error_repr);
utils::put_quoted(&mut buffer, error_repr);
buffer.put("\"")
}
if let Some(ref error_description) = self.error_description {
buffer.put(" error_description=\"");
utils::put_cow(&mut buffer, error_description);
utils::put_quoted(&mut buffer, error_description);
buffer.put("\"");
}
if let Some(ref error_uri) = self.error_uri {
buffer.put(" error_uri=\"");
utils::put_cow(&mut buffer, error_uri);
utils::put_quoted(&mut buffer, error_uri);
buffer.put("\"");
}

View File

@ -16,27 +16,31 @@ use crate::extractors::{basic, bearer, AuthExtractor};
/// If there is no `Authorization` header in the request,
/// this middleware returns an error immediately,
/// without calling the `F` callback.
/// Otherwise, it will pass parsed credentials into it.
///
/// Otherwise, it will pass both the request and
/// the parsed credentials into it.
/// In case of successful validation `F` callback
/// is required to return the `ServiceRequest` back.
pub struct HttpAuthentication<T, F>
where
T: AuthExtractor,
{
validator_fn: Rc<F>,
process_fn: Rc<F>,
_extractor: PhantomData<T>,
}
impl<T, F, O> HttpAuthentication<T, F>
where
T: AuthExtractor,
F: FnMut(&mut ServiceRequest, T) -> O,
O: IntoFuture<Item = (), Error = Error>,
F: FnMut(ServiceRequest, T) -> O,
O: IntoFuture<Item = ServiceRequest, Error = Error>,
{
/// Construct `HttpAuthentication` middleware
/// with the provided auth extractor `T` and
/// validation callback `F`.
pub fn with_fn(validator_fn: F) -> HttpAuthentication<T, F> {
pub fn with_fn(process_fn: F) -> HttpAuthentication<T, F> {
HttpAuthentication {
validator_fn: Rc::new(validator_fn),
process_fn: Rc::new(process_fn),
_extractor: PhantomData,
}
}
@ -44,15 +48,15 @@ where
impl<F, O> HttpAuthentication<basic::BasicAuth, F>
where
F: FnMut(&mut ServiceRequest, basic::BasicAuth) -> O,
O: IntoFuture<Item = (), Error = Error>,
F: FnMut(ServiceRequest, basic::BasicAuth) -> O,
O: IntoFuture<Item = ServiceRequest, Error = Error>,
{
/// Construct `HttpAuthentication` middleware for the HTTP "Basic"
/// authentication scheme.
///
/// ## Example
///
/// ```
/// ```rust
/// # use actix_web::Error;
/// # use actix_web::dev::ServiceRequest;
/// # use futures::future::{self, FutureResult};
@ -64,39 +68,40 @@ where
/// // it can be extended to query database
/// // or to do something else in a async manner.
/// fn validator(
/// req: &mut ServiceRequest,
/// credentials: BasicAuth,
/// ) -> FutureResult<(), Error> {
/// req: ServiceRequest,
/// credentials: BasicAuth,
/// ) -> FutureResult<ServiceRequest, Error> {
/// // All users are great and more than welcome!
/// future::ok(())
/// future::ok(req)
/// }
///
/// let middleware = HttpAuthentication::basic(validator);
/// ```
pub fn basic(validator_fn: F) -> Self {
Self::with_fn(validator_fn)
pub fn basic(process_fn: F) -> Self {
Self::with_fn(process_fn)
}
}
impl<F, O> HttpAuthentication<bearer::BearerAuth, F>
where
F: FnMut(&mut ServiceRequest, bearer::BearerAuth) -> O,
O: IntoFuture<Item = (), Error = Error>,
F: FnMut(ServiceRequest, bearer::BearerAuth) -> O,
O: IntoFuture<Item = ServiceRequest, Error = Error>,
{
/// Construct `HttpAuthentication` middleware for the HTTP "Bearer"
/// authentication scheme.
///
/// ## Example
///
/// ```
/// ```rust
/// # use actix_web::Error;
/// # use actix_web::dev::ServiceRequest;
/// # use futures::future::{self, FutureResult};
/// # use actix_web_httpauth::middleware::HttpAuthentication;
/// # use actix_web_httpauth::extractors::bearer::{Config, BearerAuth};
/// # use actix_web_httpauth::extractors::{AuthenticationError, AuthExtractorConfig};
/// fn validator(req: &mut ServiceRequest, credentials: BearerAuth) -> FutureResult<(), Error> {
/// fn validator(req: ServiceRequest, credentials: BearerAuth) -> FutureResult<ServiceRequest, Error> {
/// if credentials.token() == "mF_9.B5f-4.1JqM" {
/// future::ok(())
/// future::ok(req)
/// } else {
/// let config = req.app_data::<Config>()
/// .map(|data| data.get_ref().clone())
@ -109,8 +114,8 @@ where
///
/// let middleware = HttpAuthentication::bearer(validator);
/// ```
pub fn bearer(validator_fn: F) -> Self {
Self::with_fn(validator_fn)
pub fn bearer(process_fn: F) -> Self {
Self::with_fn(process_fn)
}
}
@ -122,8 +127,8 @@ where
Error = Error,
> + 'static,
S::Future: 'static,
F: Fn(&mut ServiceRequest, T) -> O + 'static,
O: IntoFuture<Item = (), Error = Error> + 'static,
F: Fn(ServiceRequest, T) -> O + 'static,
O: IntoFuture<Item = ServiceRequest, Error = Error> + 'static,
T: AuthExtractor + 'static,
{
type Request = ServiceRequest;
@ -136,7 +141,7 @@ where
fn new_transform(&self, service: S) -> Self::Future {
future::ok(AuthenticationMiddleware {
service: Some(service),
validator_fn: self.validator_fn.clone(),
process_fn: self.process_fn.clone(),
_extractor: PhantomData,
})
}
@ -148,7 +153,7 @@ where
T: AuthExtractor,
{
service: Option<S>,
validator_fn: Rc<F>,
process_fn: Rc<F>,
_extractor: PhantomData<T>,
}
@ -160,8 +165,8 @@ where
Error = Error,
> + 'static,
S::Future: 'static,
F: Fn(&mut ServiceRequest, T) -> O + 'static,
O: IntoFuture<Item = (), Error = Error> + 'static,
F: Fn(ServiceRequest, T) -> O + 'static,
O: IntoFuture<Item = ServiceRequest, Error = Error> + 'static,
T: AuthExtractor + 'static,
{
type Request = ServiceRequest;
@ -177,7 +182,7 @@ where
}
fn call(&mut self, req: Self::Request) -> Self::Future {
let validator_fn = self.validator_fn.clone();
let process_fn = self.process_fn.clone();
let mut service = self
.service
.take()
@ -185,7 +190,7 @@ where
let f = Extract::new(req)
.and_then(move |(req, credentials)| {
Validate::new(req, validator_fn, credentials)
(process_fn)(req, credentials)
})
.and_then(move |req| service.call(req));
@ -238,60 +243,3 @@ where
Ok(Async::Ready((req, credentials)))
}
}
struct Validate<F, T> {
req: Option<ServiceRequest>,
validation_f: Option<Box<dyn Future<Item = (), Error = Error>>>,
validator_fn: Rc<F>,
credentials: Option<T>,
}
impl<F, T> Validate<F, T> {
pub fn new(
req: ServiceRequest,
validator_fn: Rc<F>,
credentials: T,
) -> Self {
Validate {
req: Some(req),
credentials: Some(credentials),
validator_fn,
validation_f: None,
}
}
}
impl<F, T, O> Future for Validate<F, T>
where
F: Fn(&mut ServiceRequest, T) -> O,
O: IntoFuture<Item = (), Error = Error> + 'static,
{
type Item = ServiceRequest;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if self.validation_f.is_none() {
let req = self
.req
.as_mut()
.expect("Unable to get the mutable access to the request");
let credentials = self
.credentials
.take()
.expect("Validate future was polled in some weird manner");
let f = (self.validator_fn)(req, credentials).into_future();
self.validation_f = Some(Box::new(f));
}
let f = self
.validation_f
.as_mut()
.expect("Validation future should exist at this moment");
// We do not care about returned `Ok(())`
futures::try_ready!(f.poll());
let req = self.req.take().expect("Validate future was polled already");
Ok(Async::Ready(req))
}
}

View File

@ -1,14 +1,115 @@
use std::borrow::Cow;
use std::str;
use bytes::{BufMut, BytesMut};
use bytes::BytesMut;
// `bytes::Buf` is not implemented for `Cow<'static, str>`, implementing it by
// ourselves.
#[inline]
#[allow(clippy::ptr_arg)] // Totally okay to accept the reference to Cow here
pub fn put_cow(buf: &mut BytesMut, value: &Cow<'static, str>) {
match value {
Cow::Borrowed(str) => buf.put(str),
Cow::Owned(ref string) => buf.put(string),
enum State {
YieldStr,
YieldQuote,
}
struct Quoted<'a> {
inner: ::std::iter::Peekable<str::Split<'a, char>>,
state: State,
}
impl<'a> Quoted<'a> {
pub fn new(s: &'a str) -> Quoted {
Quoted {
inner: s.split('"').peekable(),
state: State::YieldStr,
}
}
}
impl<'a> Iterator for Quoted<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
match self.state {
State::YieldStr => {
match self.inner.next() {
Some(s) => {
self.state = State::YieldQuote;
Some(s)
},
None => None,
}
},
State::YieldQuote => {
match self.inner.peek() {
Some(_) => {
self.state = State::YieldStr;
Some("\\\"")
},
None => None,
}
}
}
}
}
/// Tries to quote the quotes in the passed `value`
pub fn put_quoted(buf: &mut BytesMut, value: &str) {
for part in Quoted::new(value) {
buf.extend_from_slice(part.as_bytes());
}
}
#[cfg(test)]
mod tests {
use std::str;
use bytes::BytesMut;
use super::put_quoted;
#[test]
fn test_quote_str() {
let input = "a \"quoted\" string";
let mut output = BytesMut::new();
put_quoted(&mut output, input);
let result = str::from_utf8(&output).unwrap();
assert_eq!(result, "a \\\"quoted\\\" string");
}
#[test]
fn test_without_quotes() {
let input = "non-quoted string";
let mut output = BytesMut::new();
put_quoted(&mut output, input);
let result = str::from_utf8(&output).unwrap();
assert_eq!(result, "non-quoted string");
}
#[test]
fn test_starts_with_quote() {
let input = "\"first-quoted string";
let mut output = BytesMut::new();
put_quoted(&mut output, input);
let result = str::from_utf8(&output).unwrap();
assert_eq!(result, "\\\"first-quoted string");
}
#[test]
fn test_ends_with_quote() {
let input = "last-quoted string\"";
let mut output = BytesMut::new();
put_quoted(&mut output, input);
let result = str::from_utf8(&output).unwrap();
assert_eq!(result, "last-quoted string\\\"");
}
#[test]
fn test_double_quote() {
let input = "quote\"\"string";
let mut output = BytesMut::new();
put_quoted(&mut output, input);
let result = str::from_utf8(&output).unwrap();
assert_eq!(result, "quote\\\"\\\"string");
}
}