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:
parent
aad60744ed
commit
0ebfd790fc
@ -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"
|
||||
|
10
README.md
10
README.md
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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("\"");
|
||||
}
|
||||
|
||||
|
@ -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("\"");
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
121
src/utils.rs
121
src/utils.rs
@ -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");
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user