Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa3503e19a | ||
|
|
49d214bfd6 | ||
|
|
2827c9bbe5
|
||
|
|
bae4c13597
|
||
|
|
a1f3c6ebfb | ||
|
|
2e61615da0 | ||
|
|
750294cd97 | ||
|
|
e01454d943 | ||
|
|
16a02aabed | ||
|
|
f9c4ba2966 |
@@ -0,0 +1,15 @@
|
||||
# 0.3.1
|
||||
|
||||
- Update deps
|
||||
|
||||
# 0.3.0
|
||||
|
||||
- Added lifetime parameter to `HttpMethod`
|
||||
- Changed `HttpMethod` to `HttpMethod<'a>(Cow<'a, str>)`
|
||||
- Added unit tests
|
||||
- Added support for the `http` crate's `Method` struct (optional feature)
|
||||
|
||||
# 0.2.4
|
||||
|
||||
- Update dependencies
|
||||
|
||||
+12
-5
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "digest_auth"
|
||||
version = "0.2.3"
|
||||
version = "0.3.1"
|
||||
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
|
||||
edition = "2018"
|
||||
description = "Implementation of the Digest Auth algorithm as defined in IETF RFC 2069, 2617, and 7616, intended for HTTP clients"
|
||||
@@ -15,8 +15,15 @@ categories = [
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.7"
|
||||
rand = "0.8"
|
||||
hex = "0.4"
|
||||
sha2 = "0.8"
|
||||
md-5 = "0.8"
|
||||
digest = "0.8"
|
||||
sha2 = "0.10"
|
||||
md-5 = "0.10"
|
||||
digest = "0.10"
|
||||
|
||||
[dependencies.http]
|
||||
version = "0.2.4"
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -2,3 +2,5 @@ Rust implementation of Digest Auth hashing algorithms,
|
||||
as defined in IETF RFC 2069, 2617, and 7616.
|
||||
|
||||
This crate provides the authentication header parsing and generation code.
|
||||
|
||||
Please see the docs and tests for examples.
|
||||
|
||||
+2
-2
@@ -161,7 +161,7 @@ pub struct AuthContext<'a> {
|
||||
/// May be left out if not using auth-int
|
||||
pub body: Option<Cow<'a, [u8]>>,
|
||||
/// HTTP method used (defaults to GET)
|
||||
pub method: HttpMethod,
|
||||
pub method: HttpMethod<'a>,
|
||||
/// Spoofed client nonce (use only for tests; a random nonce is generated automatically)
|
||||
pub cnonce: Option<Cow<'a, str>>,
|
||||
}
|
||||
@@ -202,7 +202,7 @@ impl<'a> AuthContext<'a> {
|
||||
password: PW,
|
||||
uri: UR,
|
||||
body: Option<BD>,
|
||||
method: HttpMethod,
|
||||
method: HttpMethod<'a>,
|
||||
) -> Self
|
||||
where
|
||||
UN: Into<Cow<'a, str>>,
|
||||
|
||||
+278
-22
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
use crate::{Error, Error::*, Result};
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
@@ -5,7 +7,8 @@ use std::str::FromStr;
|
||||
|
||||
use digest::{Digest, DynDigest};
|
||||
use md5::Md5;
|
||||
use sha2::{Sha256, Sha512Trunc256};
|
||||
use sha2::{Sha256, Sha512_256};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Algorithm type
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
@@ -34,11 +37,11 @@ impl Algorithm {
|
||||
let mut hash: Box<dyn DynDigest> = match self.algo {
|
||||
AlgorithmType::MD5 => Box::new(Md5::new()),
|
||||
AlgorithmType::SHA2_256 => Box::new(Sha256::new()),
|
||||
AlgorithmType::SHA2_512_256 => Box::new(Sha512Trunc256::new()),
|
||||
AlgorithmType::SHA2_512_256 => Box::new(Sha512_256::new()),
|
||||
};
|
||||
|
||||
hash.input(bytes);
|
||||
hex::encode(hash.result())
|
||||
hash.update(bytes);
|
||||
hex::encode(hash.finalize())
|
||||
}
|
||||
|
||||
/// Calculate a hash of string's bytes using the selected algorithm
|
||||
@@ -127,10 +130,9 @@ pub enum QopAlgo<'a> {
|
||||
}
|
||||
|
||||
// casting back...
|
||||
impl<'a> Into<Option<Qop>> for QopAlgo<'a> {
|
||||
/// Convert to ?Qop
|
||||
fn into(self) -> Option<Qop> {
|
||||
match self {
|
||||
impl<'a> From<QopAlgo<'a>> for Option<Qop> {
|
||||
fn from(algo: QopAlgo<'a>) -> Self {
|
||||
match algo {
|
||||
QopAlgo::NONE => None,
|
||||
QopAlgo::AUTH => Some(Qop::AUTH),
|
||||
QopAlgo::AUTH_INT(_) => Some(Qop::AUTH_INT),
|
||||
@@ -167,28 +169,282 @@ impl Display for Charset {
|
||||
}
|
||||
|
||||
/// HTTP method (used when generating the response hash for some Qop options)
|
||||
#[derive(Debug)]
|
||||
pub enum HttpMethod {
|
||||
GET,
|
||||
POST,
|
||||
HEAD,
|
||||
OTHER(&'static str),
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct HttpMethod<'a>(pub Cow<'a, str>);
|
||||
|
||||
// Well-known methods are provided as convenient associated constants
|
||||
impl<'a> HttpMethod<'a> {
|
||||
pub const GET : Self = HttpMethod(Cow::Borrowed("GET"));
|
||||
pub const POST : Self = HttpMethod(Cow::Borrowed("POST"));
|
||||
pub const PUT : Self = HttpMethod(Cow::Borrowed("PUT"));
|
||||
pub const DELETE : Self = HttpMethod(Cow::Borrowed("DELETE"));
|
||||
pub const HEAD : Self = HttpMethod(Cow::Borrowed("HEAD"));
|
||||
pub const OPTIONS : Self = HttpMethod(Cow::Borrowed("OPTIONS"));
|
||||
pub const CONNECT : Self = HttpMethod(Cow::Borrowed("CONNECT"));
|
||||
pub const PATCH : Self = HttpMethod(Cow::Borrowed("PATCH"));
|
||||
pub const TRACE : Self = HttpMethod(Cow::Borrowed("TRACE"));
|
||||
}
|
||||
|
||||
impl Default for HttpMethod {
|
||||
impl<'a> Default for HttpMethod<'a> {
|
||||
fn default() -> Self {
|
||||
HttpMethod::GET
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for HttpMethod {
|
||||
impl<'a> Display for HttpMethod<'a> {
|
||||
/// Convert to uppercase string
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
HttpMethod::GET => "GET",
|
||||
HttpMethod::POST => "POST",
|
||||
HttpMethod::HEAD => "HEAD",
|
||||
HttpMethod::OTHER(s) => s,
|
||||
})
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for HttpMethod<'a> {
|
||||
fn from(s: &'a str) -> Self {
|
||||
Self(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8]> for HttpMethod<'a> {
|
||||
fn from(s: &'a [u8]) -> Self {
|
||||
Self(String::from_utf8_lossy(s).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<String> for HttpMethod<'a> {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Cow<'a, str>> for HttpMethod<'a> {
|
||||
fn from(s: Cow<'a, str>) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
impl From<http::Method> for HttpMethod<'static> {
|
||||
fn from(method: http::Method) -> Self {
|
||||
match method.as_str() {
|
||||
// Avoid cloning when possible
|
||||
"GET" => Self::GET,
|
||||
"POST" => Self::POST,
|
||||
"PUT" => Self::PUT,
|
||||
"DELETE" => Self::DELETE,
|
||||
"HEAD" => Self::HEAD,
|
||||
"OPTIONS" => Self::OPTIONS,
|
||||
"CONNECT" => Self::CONNECT,
|
||||
"PATCH" => Self::PATCH,
|
||||
"TRACE" => Self::TRACE,
|
||||
// Clone custom strings. This is inefficient, but the inner string is private
|
||||
other => Self(other.to_owned().into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
impl<'a> From<&'a http::Method> for HttpMethod<'a> {
|
||||
fn from(method: &'a http::Method) -> HttpMethod<'a> {
|
||||
Self(method.as_str().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::error::Error::{BadCharset, BadQop, UnknownAlgorithm};
|
||||
use crate::{Algorithm, AlgorithmType, Charset, HttpMethod, Qop, QopAlgo};
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_algorithm_type() {
|
||||
// String parsing
|
||||
assert_eq!(
|
||||
Ok(Algorithm::new(AlgorithmType::MD5, false)),
|
||||
Algorithm::from_str("MD5")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Algorithm::new(AlgorithmType::MD5, true)),
|
||||
Algorithm::from_str("MD5-sess")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Algorithm::new(AlgorithmType::SHA2_256, false)),
|
||||
Algorithm::from_str("SHA-256")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Algorithm::new(AlgorithmType::SHA2_256, true)),
|
||||
Algorithm::from_str("SHA-256-sess")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Algorithm::new(AlgorithmType::SHA2_512_256, false)),
|
||||
Algorithm::from_str("SHA-512-256")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Algorithm::new(AlgorithmType::SHA2_512_256, true)),
|
||||
Algorithm::from_str("SHA-512-256-sess")
|
||||
);
|
||||
assert_eq!(
|
||||
Err(UnknownAlgorithm("OTHER_ALGORITHM".to_string())),
|
||||
Algorithm::from_str("OTHER_ALGORITHM")
|
||||
);
|
||||
|
||||
// String building
|
||||
assert_eq!(
|
||||
"MD5".to_string(),
|
||||
Algorithm::new(AlgorithmType::MD5, false).to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
"MD5-sess".to_string(),
|
||||
Algorithm::new(AlgorithmType::MD5, true).to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
"SHA-256".to_string(),
|
||||
Algorithm::new(AlgorithmType::SHA2_256, false).to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
"SHA-256-sess".to_string(),
|
||||
Algorithm::new(AlgorithmType::SHA2_256, true).to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
"SHA-512-256".to_string(),
|
||||
Algorithm::new(AlgorithmType::SHA2_512_256, false).to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
"SHA-512-256-sess".to_string(),
|
||||
Algorithm::new(AlgorithmType::SHA2_512_256, true).to_string()
|
||||
);
|
||||
|
||||
// Default
|
||||
assert_eq!(
|
||||
Algorithm::new(AlgorithmType::MD5, false),
|
||||
Default::default()
|
||||
);
|
||||
|
||||
// Hash calculation
|
||||
assert_eq!(
|
||||
"e2fc714c4727ee9395f324cd2e7f331f".to_string(),
|
||||
Algorithm::new(AlgorithmType::MD5, false).hash("abcd".as_bytes())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"e2fc714c4727ee9395f324cd2e7f331f".to_string(),
|
||||
Algorithm::new(AlgorithmType::MD5, false).hash_str("abcd")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589".to_string(),
|
||||
Algorithm::new(AlgorithmType::SHA2_256, false).hash("abcd".as_bytes())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"d2891c7978be0e24948f37caa415b87cb5cbe2b26b7bad9dc6391b8a6f6ddcc9".to_string(),
|
||||
Algorithm::new(AlgorithmType::SHA2_512_256, false).hash("abcd".as_bytes())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qop() {
|
||||
assert_eq!(Ok(Qop::AUTH), Qop::from_str("auth"));
|
||||
assert_eq!(Ok(Qop::AUTH_INT), Qop::from_str("auth-int"));
|
||||
assert_eq!(Err(BadQop("banana".to_string())), Qop::from_str("banana"));
|
||||
|
||||
assert_eq!("auth".to_string(), Qop::AUTH.to_string());
|
||||
assert_eq!("auth-int".to_string(), Qop::AUTH_INT.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qop_algo() {
|
||||
assert_eq!(Option::<Qop>::None, QopAlgo::NONE.into());
|
||||
assert_eq!(Some(Qop::AUTH), QopAlgo::AUTH.into());
|
||||
assert_eq!(
|
||||
Some(Qop::AUTH_INT),
|
||||
QopAlgo::AUTH_INT("foo".as_bytes()).into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_charset() {
|
||||
assert_eq!(Ok(Charset::UTF8), Charset::from_str("UTF-8"));
|
||||
assert_eq!(Err(BadCharset("ASCII".into())), Charset::from_str("ASCII"));
|
||||
|
||||
assert_eq!("UTF-8".to_string(), Charset::UTF8.to_string());
|
||||
assert_eq!("ASCII".to_string(), Charset::ASCII.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_method() {
|
||||
// Well known 'static
|
||||
assert_eq!(HttpMethod::GET, "GET".into());
|
||||
assert_eq!(HttpMethod::POST, "POST".into());
|
||||
assert_eq!(HttpMethod::PUT, "PUT".into());
|
||||
assert_eq!(HttpMethod::DELETE, "DELETE".into());
|
||||
assert_eq!(HttpMethod::HEAD, "HEAD".into());
|
||||
assert_eq!(HttpMethod::OPTIONS, "OPTIONS".into());
|
||||
assert_eq!(HttpMethod::CONNECT, "CONNECT".into());
|
||||
assert_eq!(HttpMethod::PATCH, "PATCH".into());
|
||||
assert_eq!(HttpMethod::TRACE, "TRACE".into());
|
||||
// As bytes
|
||||
assert_eq!(HttpMethod::GET, "GET".as_bytes().into());
|
||||
assert_eq!(
|
||||
HttpMethod(Cow::Borrowed("ěščř")),
|
||||
"ěščř".as_bytes().into()
|
||||
);
|
||||
assert_eq!(
|
||||
HttpMethod(Cow::Owned("AB�".to_string())), // Lossy conversion
|
||||
(&[65u8, 66, 156][..]).into()
|
||||
);
|
||||
// Well known String
|
||||
assert_eq!(HttpMethod::GET, String::from("GET").into());
|
||||
// Custom String
|
||||
assert_eq!(
|
||||
HttpMethod(Cow::Borrowed("NonsenseMethod")),
|
||||
"NonsenseMethod".into()
|
||||
);
|
||||
assert_eq!(
|
||||
HttpMethod(Cow::Owned("NonsenseMethod".to_string())),
|
||||
"NonsenseMethod".to_string().into()
|
||||
);
|
||||
// Custom Cow
|
||||
assert_eq!(HttpMethod::HEAD, Cow::Borrowed("HEAD").into());
|
||||
assert_eq!(
|
||||
HttpMethod(Cow::Borrowed("NonsenseMethod")),
|
||||
Cow::Borrowed("NonsenseMethod").into()
|
||||
);
|
||||
// to string
|
||||
assert_eq!("GET".to_string(), HttpMethod::GET.to_string());
|
||||
assert_eq!("POST".to_string(), HttpMethod::POST.to_string());
|
||||
assert_eq!("PUT".to_string(), HttpMethod::PUT.to_string());
|
||||
assert_eq!("DELETE".to_string(), HttpMethod::DELETE.to_string());
|
||||
assert_eq!("HEAD".to_string(), HttpMethod::HEAD.to_string());
|
||||
assert_eq!("OPTIONS".to_string(), HttpMethod::OPTIONS.to_string());
|
||||
assert_eq!("CONNECT".to_string(), HttpMethod::CONNECT.to_string());
|
||||
assert_eq!("PATCH".to_string(), HttpMethod::PATCH.to_string());
|
||||
assert_eq!("TRACE".to_string(), HttpMethod::TRACE.to_string());
|
||||
|
||||
assert_eq!(
|
||||
"NonsenseMethod".to_string(),
|
||||
HttpMethod(Cow::Borrowed("NonsenseMethod")).to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
"NonsenseMethod".to_string(),
|
||||
HttpMethod(Cow::Owned("NonsenseMethod".to_string())).to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
#[test]
|
||||
fn test_http_crate() {
|
||||
assert_eq!(HttpMethod::GET, http::Method::GET.clone().into());
|
||||
assert_eq!(
|
||||
HttpMethod(Cow::Owned("BANANA".to_string())),
|
||||
http::Method::from_str("BANANA").unwrap().into()
|
||||
);
|
||||
|
||||
assert_eq!(HttpMethod::GET, (&http::Method::GET).into());
|
||||
let x = http::Method::from_str("BANANA").unwrap();
|
||||
assert_eq!(
|
||||
HttpMethod(Cow::Borrowed("BANANA")),
|
||||
(&x).into()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::result;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
BadCharset(String),
|
||||
UnknownAlgorithm(String),
|
||||
|
||||
+22
-17
@@ -62,9 +62,13 @@ pub fn parse(www_authorize: &str) -> Result<WwwAuthenticateHeader> {
|
||||
WwwAuthenticateHeader::parse(www_authorize)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_respond() {
|
||||
let src = r#"
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{AuthContext, Error};
|
||||
|
||||
#[test]
|
||||
fn test_parse_respond() {
|
||||
let src = r#"
|
||||
Digest
|
||||
realm="http-auth@example.org",
|
||||
qop="auth, auth-int",
|
||||
@@ -73,17 +77,17 @@ fn test_parse_respond() {
|
||||
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
|
||||
"#;
|
||||
|
||||
let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html");
|
||||
context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ");
|
||||
let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html");
|
||||
context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ");
|
||||
|
||||
let mut prompt = crate::parse(src).unwrap();
|
||||
let answer = prompt.respond(&context).unwrap();
|
||||
let mut prompt = crate::parse(src).unwrap();
|
||||
let answer = prompt.respond(&context).unwrap();
|
||||
|
||||
let str = answer.to_string().replace(", ", ",\n ");
|
||||
let str = answer.to_string().replace(", ", ",\n "); // This is only for easier reading
|
||||
|
||||
assert_eq!(
|
||||
str,
|
||||
r#"
|
||||
assert_eq!(
|
||||
str,
|
||||
r#"
|
||||
Digest username="Mufasa",
|
||||
realm="http-auth@example.org",
|
||||
nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
|
||||
@@ -95,11 +99,12 @@ Digest username="Mufasa",
|
||||
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS",
|
||||
algorithm=MD5
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cast_error() {
|
||||
let _m: Box<dyn std::error::Error> = Error::UnknownAlgorithm("Uhhh".into()).into();
|
||||
#[test]
|
||||
fn test_cast_error() {
|
||||
let _m: Box<dyn std::error::Error> = Error::UnknownAlgorithm("Uhhh".into()).into();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user