Compare commits

..

No commits in common. 'master' and 'v0.2.4' have entirely different histories.

  1. 15
      CHANGELOG.md
  2. 15
      Cargo.toml
  3. 2
      README.md
  4. 4
      src/digest.rs
  5. 296
      src/enums.rs
  6. 2
      src/error.rs
  7. 39
      src/lib.rs

@ -1,15 +0,0 @@
# 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

@ -1,6 +1,6 @@
[package] [package]
name = "digest_auth" name = "digest_auth"
version = "0.3.1" version = "0.2.4"
authors = ["Ondřej Hruška <ondra@ondrovo.com>"] authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
edition = "2018" edition = "2018"
description = "Implementation of the Digest Auth algorithm as defined in IETF RFC 2069, 2617, and 7616, intended for HTTP clients" description = "Implementation of the Digest Auth algorithm as defined in IETF RFC 2069, 2617, and 7616, intended for HTTP clients"
@ -17,13 +17,6 @@ license = "MIT"
[dependencies] [dependencies]
rand = "0.8" rand = "0.8"
hex = "0.4" hex = "0.4"
sha2 = "0.10" sha2 = "0.9"
md-5 = "0.10" md-5 = "0.9"
digest = "0.10" digest = "0.9"
[dependencies.http]
version = "0.2.4"
optional = true
[features]
default = []

@ -2,5 +2,3 @@ Rust implementation of Digest Auth hashing algorithms,
as defined in IETF RFC 2069, 2617, and 7616. as defined in IETF RFC 2069, 2617, and 7616.
This crate provides the authentication header parsing and generation code. This crate provides the authentication header parsing and generation code.
Please see the docs and tests for examples.

@ -161,7 +161,7 @@ pub struct AuthContext<'a> {
/// May be left out if not using auth-int /// May be left out if not using auth-int
pub body: Option<Cow<'a, [u8]>>, pub body: Option<Cow<'a, [u8]>>,
/// HTTP method used (defaults to GET) /// HTTP method used (defaults to GET)
pub method: HttpMethod<'a>, pub method: HttpMethod,
/// Spoofed client nonce (use only for tests; a random nonce is generated automatically) /// Spoofed client nonce (use only for tests; a random nonce is generated automatically)
pub cnonce: Option<Cow<'a, str>>, pub cnonce: Option<Cow<'a, str>>,
} }
@ -202,7 +202,7 @@ impl<'a> AuthContext<'a> {
password: PW, password: PW,
uri: UR, uri: UR,
body: Option<BD>, body: Option<BD>,
method: HttpMethod<'a>, method: HttpMethod,
) -> Self ) -> Self
where where
UN: Into<Cow<'a, str>>, UN: Into<Cow<'a, str>>,

@ -1,5 +1,3 @@
#![allow(clippy::upper_case_acronyms)]
use crate::{Error, Error::*, Result}; use crate::{Error, Error::*, Result};
use std::fmt; use std::fmt;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
@ -7,8 +5,7 @@ use std::str::FromStr;
use digest::{Digest, DynDigest}; use digest::{Digest, DynDigest};
use md5::Md5; use md5::Md5;
use sha2::{Sha256, Sha512_256}; use sha2::{Sha256, Sha512Trunc256};
use std::borrow::Cow;
/// Algorithm type /// Algorithm type
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
@ -37,7 +34,7 @@ impl Algorithm {
let mut hash: Box<dyn DynDigest> = match self.algo { let mut hash: Box<dyn DynDigest> = match self.algo {
AlgorithmType::MD5 => Box::new(Md5::new()), AlgorithmType::MD5 => Box::new(Md5::new()),
AlgorithmType::SHA2_256 => Box::new(Sha256::new()), AlgorithmType::SHA2_256 => Box::new(Sha256::new()),
AlgorithmType::SHA2_512_256 => Box::new(Sha512_256::new()), AlgorithmType::SHA2_512_256 => Box::new(Sha512Trunc256::new()),
}; };
hash.update(bytes); hash.update(bytes);
@ -130,9 +127,10 @@ pub enum QopAlgo<'a> {
} }
// casting back... // casting back...
impl<'a> From<QopAlgo<'a>> for Option<Qop> { impl<'a> Into<Option<Qop>> for QopAlgo<'a> {
fn from(algo: QopAlgo<'a>) -> Self { /// Convert to ?Qop
match algo { fn into(self) -> Option<Qop> {
match self {
QopAlgo::NONE => None, QopAlgo::NONE => None,
QopAlgo::AUTH => Some(Qop::AUTH), QopAlgo::AUTH => Some(Qop::AUTH),
QopAlgo::AUTH_INT(_) => Some(Qop::AUTH_INT), QopAlgo::AUTH_INT(_) => Some(Qop::AUTH_INT),
@ -169,282 +167,28 @@ impl Display for Charset {
} }
/// HTTP method (used when generating the response hash for some Qop options) /// HTTP method (used when generating the response hash for some Qop options)
#[derive(Debug, PartialEq, Clone)] #[derive(Debug)]
pub struct HttpMethod<'a>(pub Cow<'a, str>); pub enum HttpMethod {
GET,
// Well-known methods are provided as convenient associated constants POST,
impl<'a> HttpMethod<'a> { HEAD,
pub const GET : Self = HttpMethod(Cow::Borrowed("GET")); OTHER(&'static str),
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<'a> Default for HttpMethod<'a> { impl Default for HttpMethod {
fn default() -> Self { fn default() -> Self {
HttpMethod::GET HttpMethod::GET
} }
} }
impl<'a> Display for HttpMethod<'a> { impl Display for HttpMethod {
/// Convert to uppercase string /// Convert to uppercase string
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(&self.0) f.write_str(match self {
} HttpMethod::GET => "GET",
} HttpMethod::POST => "POST",
HttpMethod::HEAD => "HEAD",
impl<'a> From<&'a str> for HttpMethod<'a> { HttpMethod::OTHER(s) => s,
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<EFBFBD>".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,7 +1,7 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::result; use std::result;
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub enum Error { pub enum Error {
BadCharset(String), BadCharset(String),
UnknownAlgorithm(String), UnknownAlgorithm(String),

@ -62,13 +62,9 @@ pub fn parse(www_authorize: &str) -> Result<WwwAuthenticateHeader> {
WwwAuthenticateHeader::parse(www_authorize) WwwAuthenticateHeader::parse(www_authorize)
} }
#[cfg(test)] #[test]
mod test { fn test_parse_respond() {
use crate::{AuthContext, Error}; let src = r#"
#[test]
fn test_parse_respond() {
let src = r#"
Digest Digest
realm="http-auth@example.org", realm="http-auth@example.org",
qop="auth, auth-int", qop="auth, auth-int",
@ -77,17 +73,17 @@ mod test {
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS" opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
"#; "#;
let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html"); let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html");
context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"); context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ");
let mut prompt = crate::parse(src).unwrap(); let mut prompt = crate::parse(src).unwrap();
let answer = prompt.respond(&context).unwrap(); let answer = prompt.respond(&context).unwrap();
let str = answer.to_string().replace(", ", ",\n "); // This is only for easier reading let str = answer.to_string().replace(", ", ",\n ");
assert_eq!( assert_eq!(
str, str,
r#" r#"
Digest username="Mufasa", Digest username="Mufasa",
realm="http-auth@example.org", realm="http-auth@example.org",
nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
@ -99,12 +95,11 @@ Digest username="Mufasa",
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS",
algorithm=MD5 algorithm=MD5
"# "#
.trim() .trim()
); );
} }
#[test] #[test]
fn test_cast_error() { fn test_cast_error() {
let _m: Box<dyn std::error::Error> = Error::UnknownAlgorithm("Uhhh".into()).into(); let _m: Box<dyn std::error::Error> = Error::UnknownAlgorithm("Uhhh".into()).into();
}
} }

Loading…
Cancel
Save