10 Commits
Author SHA1 Message Date
MightyPork fa3503e19a Bump version 2023-05-09 09:41:04 +02:00
silwol 49d214bfd6 Upgrade dependencies 2023-05-08 11:49:57 +02:00
MightyPork 2827c9bbe5 optimize Method 2021-05-25 10:08:22 +02:00
MightyPork bae4c13597 improvements around HttpMethod, more tests 2021-05-25 01:05:54 +02:00
MightyPorkandGitea a1f3c6ebfb Merge branch 'pr-method-lifetime' of slamb/digest_auth_rs into master 2021-05-24 21:46:46 +00:00
Scott Lamb 2e61615da0 allow non-'static HTTP method names
Background: RTSP uses HTTP digest authentication (see RFC 2326 section
1.4). I'm using the rtsp-types crate. It works fine with digest_auth,
except that [rtsp_types::Method::Extension](https://docs.rs/rtsp-types/0.0.1/rtsp_types/enum.Method.html#variant.Extension)
has a non-static lifetime.
2021-05-23 20:08:39 -07:00
MightyPork 750294cd97 add changelog 2021-01-24 11:05:25 +01:00
MightyPork e01454d943 bump version 2021-01-23 18:46:53 +01:00
MightyPorkandGitea 16a02aabed Merge branch 'upgrade-deps' of jplatte/digest_auth_rs into master 2021-01-23 17:40:35 +00:00
Jonas Platte f9c4ba2966 Upgrade dependencies 2021-01-07 14:24:48 +01:00
7 changed files with 332 additions and 47 deletions
+15
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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();
}
}