mirror of
https://git.asonix.dog/asonix/http-signature-normalization.git
synced 2024-11-25 10:51:01 +00:00
Add logging in actix crate
This commit is contained in:
parent
4feff4a894
commit
7af4f07a97
6 changed files with 92 additions and 28 deletions
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "http-signature-normalization"
|
name = "http-signature-normalization"
|
||||||
description = "An HTTP Signatures library that leaves the signing to you"
|
description = "An HTTP Signatures library that leaves the signing to you"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
authors = ["asonix <asonix@asonix.dog>"]
|
authors = ["asonix <asonix@asonix.dog>"]
|
||||||
license-file = "LICENSE"
|
license-file = "LICENSE"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "http-signature-normalization-actix"
|
name = "http-signature-normalization-actix"
|
||||||
description = "An HTTP Signatures library that leaves the signing to you"
|
description = "An HTTP Signatures library that leaves the signing to you"
|
||||||
version = "0.3.0-alpha.6"
|
version = "0.3.0-alpha.7"
|
||||||
authors = ["asonix <asonix@asonix.dog>"]
|
authors = ["asonix <asonix@asonix.dog>"]
|
||||||
license-file = "LICENSE"
|
license-file = "LICENSE"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -12,7 +12,7 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
[features]
|
[features]
|
||||||
default = ["sha-2", "sha-3"]
|
default = ["sha-2", "sha-3"]
|
||||||
digest = ["base64", "log"]
|
digest = ["base64"]
|
||||||
sha-2 = ["digest", "sha2"]
|
sha-2 = ["digest", "sha2"]
|
||||||
sha-3 = ["digest", "sha3"]
|
sha-3 = ["digest", "sha3"]
|
||||||
|
|
||||||
|
@ -31,8 +31,8 @@ base64 = { version = "0.11", optional = true }
|
||||||
bytes = "0.5.4"
|
bytes = "0.5.4"
|
||||||
chrono = "0.4.6"
|
chrono = "0.4.6"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
http-signature-normalization = { version = "0.4.0", path = ".." }
|
http-signature-normalization = { version = "0.4.1", path = ".." }
|
||||||
log = { version = "0.4", optional = true }
|
log = "0.4"
|
||||||
sha2 = { version = "0.8", optional = true }
|
sha2 = { version = "0.8", optional = true }
|
||||||
sha3 = { version = "0.8", optional = true }
|
sha3 = { version = "0.8", optional = true }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! Types for setting up Digest middleware verification
|
//! Types for setting up Digest middleware verification
|
||||||
|
|
||||||
|
use super::{DigestPart, DigestVerify};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{Body, Payload, Service, ServiceRequest, ServiceResponse, Transform},
|
dev::{Body, Payload, Service, ServiceRequest, ServiceResponse, Transform},
|
||||||
error::PayloadError,
|
error::PayloadError,
|
||||||
|
@ -12,6 +13,7 @@ use futures::{
|
||||||
stream::once,
|
stream::once,
|
||||||
Stream, StreamExt,
|
Stream, StreamExt,
|
||||||
};
|
};
|
||||||
|
use log::{debug, warn};
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
future::Future,
|
future::Future,
|
||||||
|
@ -20,8 +22,6 @@ use std::{
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{DigestPart, DigestVerify};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
/// A type implementing FromRequest that can be used in route handler to guard for verified
|
/// A type implementing FromRequest that can be used in route handler to guard for verified
|
||||||
/// digests
|
/// digests
|
||||||
|
@ -76,12 +76,17 @@ impl FromRequest for DigestVerified {
|
||||||
type Config = ();
|
type Config = ();
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
ready(
|
let res = req
|
||||||
req.extensions()
|
.extensions()
|
||||||
.get::<Self>()
|
.get::<Self>()
|
||||||
.map(|s| *s)
|
.map(|s| *s)
|
||||||
.ok_or(VerifyError),
|
.ok_or(VerifyError);
|
||||||
)
|
|
||||||
|
if res.is_err() {
|
||||||
|
debug!("Failed to fetch DigestVerified from request");
|
||||||
|
}
|
||||||
|
|
||||||
|
ready(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +139,10 @@ where
|
||||||
if let Some(digest) = req.headers().get("Digest") {
|
if let Some(digest) = req.headers().get("Digest") {
|
||||||
let vec = match parse_digest(digest) {
|
let vec = match parse_digest(digest) {
|
||||||
Some(vec) => vec,
|
Some(vec) => vec,
|
||||||
None => return Box::pin(err(VerifyError.into())),
|
None => {
|
||||||
|
warn!("Digest header could not be parsed");
|
||||||
|
return Box::pin(err(VerifyError.into()));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let mut payload = req.take_payload();
|
let mut payload = req.take_payload();
|
||||||
let service = self.0.clone();
|
let service = self.0.clone();
|
||||||
|
@ -159,6 +167,7 @@ where
|
||||||
|
|
||||||
service.borrow_mut().call(req).await
|
service.borrow_mut().call(req).await
|
||||||
} else {
|
} else {
|
||||||
|
warn!("Digest could not be verified");
|
||||||
Err(VerifyError.into())
|
Err(VerifyError.into())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -244,15 +244,39 @@ pub struct Config {
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
/// An error when preparing to verify a request
|
/// An error when preparing to verify a request
|
||||||
pub enum PrepareVerifyError {
|
pub enum PrepareVerifyError {
|
||||||
#[error("Signature error, {0}")]
|
#[error("Header is missing")]
|
||||||
/// An error validating the request
|
/// Header is missing
|
||||||
Sig(#[from] http_signature_normalization::PrepareVerifyError),
|
Missing,
|
||||||
|
|
||||||
|
#[error("Header is expired")]
|
||||||
|
/// Header is expired
|
||||||
|
Expired,
|
||||||
|
|
||||||
|
#[error("Couldn't parse required field, {0}")]
|
||||||
|
/// Couldn't parse required field
|
||||||
|
ParseField(&'static str),
|
||||||
|
|
||||||
#[error("Failed to read header, {0}")]
|
#[error("Failed to read header, {0}")]
|
||||||
/// An error converting the header to a string for validation
|
/// An error converting the header to a string for validation
|
||||||
Header(#[from] ToStrError),
|
Header(#[from] ToStrError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<http_signature_normalization::PrepareVerifyError> for PrepareVerifyError {
|
||||||
|
fn from(e: http_signature_normalization::PrepareVerifyError) -> Self {
|
||||||
|
use http_signature_normalization as hsn;
|
||||||
|
|
||||||
|
match e {
|
||||||
|
hsn::PrepareVerifyError::Parse(parse_error) => {
|
||||||
|
PrepareVerifyError::ParseField(parse_error.missing_field())
|
||||||
|
}
|
||||||
|
hsn::PrepareVerifyError::Validate(validate_error) => match validate_error {
|
||||||
|
hsn::verify::ValidateError::Missing => PrepareVerifyError::Missing,
|
||||||
|
hsn::verify::ValidateError::Expired => PrepareVerifyError::Expired,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/// Create a new Config with a default expiration of 10 seconds
|
/// Create a new Config with a default expiration of 10 seconds
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
//! Types for verifying requests with Actix Web
|
//! Types for verifying requests with Actix Web
|
||||||
|
|
||||||
|
use crate::{Config, PrepareVerifyError, SignatureVerify};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{Body, Payload, Service, ServiceRequest, ServiceResponse, Transform},
|
dev::{Body, Payload, Service, ServiceRequest, ServiceResponse, Transform},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
Error, FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
|
Error, FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
|
||||||
};
|
};
|
||||||
use futures::future::{err, ok, ready, Ready};
|
use futures::future::{err, ok, ready, Ready};
|
||||||
|
use log::{debug, warn};
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
future::Future,
|
future::Future,
|
||||||
|
@ -14,8 +16,6 @@ use std::{
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Config, SignatureVerify};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
/// A marker type that can be used to guard routes when the signature middleware is set to
|
/// A marker type that can be used to guard routes when the signature middleware is set to
|
||||||
/// 'optional'
|
/// 'optional'
|
||||||
|
@ -73,7 +73,7 @@ where
|
||||||
/// By default, this middleware expects to verify Signature headers, and requires the presence
|
/// By default, this middleware expects to verify Signature headers, and requires the presence
|
||||||
/// of the header
|
/// of the header
|
||||||
pub fn new(verify_signature: T, config: Config) -> Self {
|
pub fn new(verify_signature: T, config: Config) -> Self {
|
||||||
VerifySignature(verify_signature, config, HeaderKind::Signature, true)
|
VerifySignature(verify_signature, config, HeaderKind::Signature, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify Authorization headers instead of Signature headers
|
/// Verify Authorization headers instead of Signature headers
|
||||||
|
@ -87,7 +87,7 @@ where
|
||||||
/// is passed through. This can be used to set a global middleware, and then guard each route
|
/// is passed through. This can be used to set a global middleware, and then guard each route
|
||||||
/// handler with the [`SignatureVerified`] type.
|
/// handler with the [`SignatureVerified`] type.
|
||||||
pub fn optional(self) -> Self {
|
pub fn optional(self) -> Self {
|
||||||
VerifySignature(self.0, self.1, self.2, false)
|
VerifySignature(self.0, self.1, self.2, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,22 @@ where
|
||||||
|
|
||||||
let unverified = match res {
|
let unverified = match res {
|
||||||
Ok(unverified) => unverified,
|
Ok(unverified) => unverified,
|
||||||
Err(_) => return Box::pin(err(VerifyError.into())),
|
Err(PrepareVerifyError::Expired) => {
|
||||||
|
warn!("Header is expired");
|
||||||
|
return Box::pin(err(VerifyError.into()));
|
||||||
|
}
|
||||||
|
Err(PrepareVerifyError::Missing) => {
|
||||||
|
debug!("Header is missing");
|
||||||
|
return Box::pin(err(VerifyError.into()));
|
||||||
|
}
|
||||||
|
Err(PrepareVerifyError::ParseField(field)) => {
|
||||||
|
debug!("Failed to parse field {}", field);
|
||||||
|
return Box::pin(err(VerifyError.into()));
|
||||||
|
}
|
||||||
|
Err(PrepareVerifyError::Header(e)) => {
|
||||||
|
debug!("Failed to parse header {}", e);
|
||||||
|
return Box::pin(err(VerifyError.into()));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let algorithm = unverified.algorithm().map(|a| a.clone());
|
let algorithm = unverified.algorithm().map(|a| a.clone());
|
||||||
|
@ -129,6 +144,7 @@ where
|
||||||
req.extensions_mut().insert(SignatureVerified(key_id));
|
req.extensions_mut().insert(SignatureVerified(key_id));
|
||||||
service.borrow_mut().call(req).await
|
service.borrow_mut().call(req).await
|
||||||
} else {
|
} else {
|
||||||
|
warn!("Signature is invalid");
|
||||||
Err(VerifyError.into())
|
Err(VerifyError.into())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -151,12 +167,17 @@ impl FromRequest for SignatureVerified {
|
||||||
type Config = ();
|
type Config = ();
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
ready(
|
let res = req
|
||||||
req.extensions()
|
.extensions()
|
||||||
.get::<Self>()
|
.get::<Self>()
|
||||||
.map(|s| s.clone())
|
.map(|s| s.clone())
|
||||||
.ok_or(VerifyError),
|
.ok_or(VerifyError);
|
||||||
)
|
|
||||||
|
if res.is_err() {
|
||||||
|
debug!("Failed to fetch SignatureVerified from request");
|
||||||
|
}
|
||||||
|
|
||||||
|
ready(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,10 +241,13 @@ where
|
||||||
return self.handle(req);
|
return self.handle(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("Authorization or Signature headers are missing");
|
||||||
Box::pin(err(VerifyError.into()))
|
Box::pin(err(VerifyError.into()))
|
||||||
} else if self.3 {
|
} else if self.3 {
|
||||||
|
debug!("Headers are missing but Optional is true, continuing");
|
||||||
Box::pin(self.0.borrow_mut().call(req))
|
Box::pin(self.0.borrow_mut().call(req))
|
||||||
} else {
|
} else {
|
||||||
|
debug!("Authorization or Signature headers are missing");
|
||||||
Box::pin(err(VerifyError.into()))
|
Box::pin(err(VerifyError.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,13 @@ pub enum ValidateError {
|
||||||
/// was invalid.
|
/// was invalid.
|
||||||
pub struct ParseSignatureError(&'static str);
|
pub struct ParseSignatureError(&'static str);
|
||||||
|
|
||||||
|
impl ParseSignatureError {
|
||||||
|
/// Get the name of the missing field
|
||||||
|
pub fn missing_field(&self) -> &'static str {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Unverified {
|
impl Unverified {
|
||||||
/// Get the Key ID from an Unverified type
|
/// Get the Key ID from an Unverified type
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in a new issue