implement ToString for WwwAuthenticateHeader for server-side use

pull/4/head
Ondřej Hruška 5 years ago
parent f4c6e4ab3d
commit f2346416cc
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 3
      README.md
  2. 191
      src/digest.rs
  3. 9
      src/enums.rs

@ -1,3 +1,4 @@
Rust implementation of Digest Auth hashing algorithms, as defined in IETF RFC 2069, 2617, and 7616. 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. This crate provides the authentication header parsing and generation code.

@ -6,6 +6,7 @@ use std::str::FromStr;
use crate::enums::{Algorithm, AlgorithmType, Charset, HttpMethod, Qop, QopAlgo}; use crate::enums::{Algorithm, AlgorithmType, Charset, HttpMethod, Qop, QopAlgo};
use crate::{Error::*, Result}; use crate::{Error::*, Result};
use std::borrow::Cow;
/// slash quoting for digest strings /// slash quoting for digest strings
trait QuoteForDigest { trait QuoteForDigest {
@ -18,12 +19,44 @@ impl QuoteForDigest for &str {
} }
} }
impl<'a> QuoteForDigest for Cow<'a, str> {
fn quote_for_digest(&self) -> String {
self.as_ref().quote_for_digest()
}
}
impl QuoteForDigest for String { impl QuoteForDigest for String {
fn quote_for_digest(&self) -> String { fn quote_for_digest(&self) -> String {
self.replace("\\", "\\\\").replace("\"", "\\\"") self.replace("\\", "\\\\").replace("\"", "\\\"")
} }
} }
/// Join a Vec of Display items using a separator
fn join_vec<T : ToString>(vec : &Vec<T>, sep : &str) -> String {
vec.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(sep)
}
enum NamedTag<'a> {
Quoted(&'a str, Cow<'a, str>),
Plain(&'a str, Cow<'a, str>)
}
impl Display for NamedTag<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
NamedTag::Quoted(name, content) => {
write!(f, "{}=\"{}\"", name, content.quote_for_digest())
}
NamedTag::Plain(name, content) => {
write!(f, "{}={}", name, content)
}
}
}
}
/// Login attempt context /// Login attempt context
/// ///
/// All fields are borrowed to reduce runtime overhead; this struct should not be stored anywhere, /// All fields are borrowed to reduce runtime overhead; this struct should not be stored anywhere,
@ -199,6 +232,48 @@ impl WwwAuthenticateHeader {
} }
} }
impl Display for WwwAuthenticateHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut entries = Vec::<NamedTag>::new();
f.write_str("Digest ")?;
entries.push(NamedTag::Quoted("realm", (&self.realm).into()));
if let Some(ref qops) = self.qop {
entries.push(NamedTag::Quoted("qop", join_vec(qops, ", ").into()));
}
if let Some(ref domains) = self.domain {
entries.push(NamedTag::Quoted("domain", join_vec(domains, " ").into()));
}
if self.stale {
entries.push(NamedTag::Plain("stale", "true".into()));
}
entries.push(NamedTag::Plain("algorithm", self.algorithm.to_string().into()));
entries.push(NamedTag::Quoted("nonce", (&self.nonce).into()));
if let Some(ref opaque) = self.opaque {
entries.push(NamedTag::Quoted("opaque", (opaque).into()));
}
entries.push(NamedTag::Plain("charset", self.charset.to_string().into()));
if self.userhash {
entries.push(NamedTag::Plain("userhash", "true".into()));
}
for (i, e) in entries.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str(&e.to_string())?;
}
Ok(())
}
}
/// Helper func that parses the key-value string received from server /// Helper func that parses the key-value string received from server
fn parse_header_map(input: &str) -> Result<HashMap<String, String>> { fn parse_header_map(input: &str) -> Result<HashMap<String, String>> {
#[derive(Debug)] #[derive(Debug)]
@ -355,12 +430,7 @@ impl<'a> AuthorizationHeader<'a> {
QopAlgo::AUTH QopAlgo::AUTH
} else { } else {
// parser bug - prompt.qop should have been None // parser bug - prompt.qop should have been None
return Err(BadQopOptions( return Err(BadQopOptions(join_vec(vec, ", ")));
vec.iter()
.map(ToString::to_string)
.collect::<Vec<String>>()
.join(","),
));
} }
} }
}; };
@ -483,44 +553,29 @@ impl<'a> Display for AuthorizationHeader<'a> {
f.write_str("Digest ")?; f.write_str("Digest ")?;
//TODO charset shenanigans with username* (UTF-8 charset) //TODO charset shenanigans with username* (UTF-8 charset)
f.write_fmt(format_args!( write!(f, "username=\"{uname}\", realm=\"{realm}\", nonce=\"{nonce}\", uri=\"{uri}\"",
"username=\"{}\"", uname = self.username.quote_for_digest(),
self.username.quote_for_digest() realm = self.prompt.realm.quote_for_digest(),
))?; nonce = self.prompt.nonce.quote_for_digest(),
uri = self.uri)?;
f.write_fmt(format_args!(
", realm=\"{}\"",
self.prompt.realm.quote_for_digest()
))?;
f.write_fmt(format_args!(
", nonce=\"{}\"",
self.prompt.nonce.quote_for_digest()
))?;
f.write_fmt(format_args!(", uri=\"{}\"", self.uri))?;
if self.prompt.qop.is_some() && self.cnonce.is_some() { if self.prompt.qop.is_some() && self.cnonce.is_some() {
f.write_fmt(format_args!( write!(f, ", qop={qop}, nc={nc:08x}, cnonce=\"{cnonce}\"",
", qop={qop}, nc={nc:08x}, cnonce=\"{cnonce}\"",
qop = self.qop.as_ref().unwrap(), qop = self.qop.as_ref().unwrap(),
nc = self.nc,
cnonce = self.cnonce.as_ref().unwrap().quote_for_digest(), cnonce = self.cnonce.as_ref().unwrap().quote_for_digest(),
nc = self.nc )?;
))?;
} }
f.write_fmt(format_args!( write!(f, ", response=\"{}\"", self.response.quote_for_digest())?;
", response=\"{}\"",
self.response.quote_for_digest()
))?;
if let Some(opaque) = &self.prompt.opaque { if let Some(opaque) = &self.prompt.opaque {
f.write_fmt(format_args!(", opaque=\"{}\"", opaque.quote_for_digest()))?; write!(f, ", opaque=\"{}\"", opaque.quote_for_digest())?;
} }
// algorithm can be omitted if it is the default value (or in legacy compat mode) // algorithm can be omitted if it is the default value (or in legacy compat mode)
if self.qop.is_some() || self.prompt.algorithm.algo != AlgorithmType::MD5 { if self.qop.is_some() || self.prompt.algorithm.algo != AlgorithmType::MD5 {
f.write_fmt(format_args!(", algorithm={}", self.prompt.algorithm))?; write!(f, ", algorithm={}", self.prompt.algorithm)?;
} }
if self.prompt.userhash { if self.prompt.userhash {
@ -545,7 +600,6 @@ mod tests {
#[test] #[test]
fn test_parse_header_map() { fn test_parse_header_map() {
{
let src = r#" let src = r#"
realm="api@example.org", realm="api@example.org",
qop="auth", qop="auth",
@ -573,18 +627,23 @@ mod tests {
assert_eq!(map.get("userhash").unwrap(), "true"); assert_eq!(map.get("userhash").unwrap(), "true");
} }
#[test]
fn test_parse_header_map2()
{ {
let src = r#"realm="api@example.org""#; let src = r#"realm="api@example.org""#;
let map = parse_header_map(src).unwrap(); let map = parse_header_map(src).unwrap();
assert_eq!(map.get("realm").unwrap(), "api@example.org"); assert_eq!(map.get("realm").unwrap(), "api@example.org");
} }
{ #[test]
fn test_parse_header_map3() {
let src = r#"realm=api@example.org"#; let src = r#"realm=api@example.org"#;
let map = parse_header_map(src).unwrap(); let map = parse_header_map(src).unwrap();
assert_eq!(map.get("realm").unwrap(), "api@example.org"); assert_eq!(map.get("realm").unwrap(), "api@example.org");
} }
#[test]
fn test_parse_header_map4() {
{ {
let src = ""; let src = "";
let map = parse_header_map(src).unwrap(); let map = parse_header_map(src).unwrap();
@ -594,12 +653,11 @@ mod tests {
#[test] #[test]
fn test_www_hdr_parse() { fn test_www_hdr_parse() {
{
// most things are parsed here... // most things are parsed here...
let src = r#" let src = r#"
realm="api@example.org", realm="api@example.org",
qop="auth", qop="auth",
domain="/my/nice/url /login /logout" domain="/my/nice/url /login /logout",
algorithm=SHA-512-256, algorithm=SHA-512-256,
nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK",
opaque="HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS", opaque="HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS",
@ -630,7 +688,62 @@ mod tests {
) )
} }
{ #[test]
fn test_www_hdr_tostring() {
let mut hdr = WwwAuthenticateHeader {
domain: Some(vec![
"/my/nice/url".to_string(),
"/login".to_string(),
"/logout".to_string(),
]),
realm: "api@example.org".to_string(),
nonce: "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK".to_string(),
opaque: Some("HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS".to_string()),
stale: false,
algorithm: Algorithm::new(AlgorithmType::SHA2_512_256, false),
qop: Some(vec![Qop::AUTH]),
userhash: true,
charset: Charset::UTF8,
nc: 0,
};
assert_eq!(
r#"Digest realm="api@example.org",
qop="auth",
domain="/my/nice/url /login /logout",
algorithm=SHA-512-256,
nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK",
opaque="HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS",
charset=UTF-8,
userhash=true"#.replace(",\n ", ", "), hdr.to_string());
hdr.stale=true;
hdr.userhash=false;
hdr.opaque = None;
hdr.qop = None;
assert_eq!(
r#"Digest realm="api@example.org",
domain="/my/nice/url /login /logout",
stale=true,
algorithm=SHA-512-256,
nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK",
charset=UTF-8"#.replace(",\n ", ", "), hdr.to_string());
hdr.qop = Some(vec![Qop::AUTH, Qop::AUTH_INT]);
assert_eq!(
r#"Digest realm="api@example.org",
qop="auth, auth-int",
domain="/my/nice/url /login /logout",
stale=true,
algorithm=SHA-512-256,
nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK",
charset=UTF-8"#.replace(",\n ", ", "), hdr.to_string());
}
#[test]
fn test_www_hdr_parse2() {
// verify some defaults // verify some defaults
let src = r#" let src = r#"
realm="a long realm with\\, weird \" characters", realm="a long realm with\\, weird \" characters",
@ -658,7 +771,8 @@ mod tests {
) )
} }
{ #[test]
fn test_www_hdr_parse3() {
// check that it correctly ignores leading Digest // check that it correctly ignores leading Digest
let src = r#"Digest realm="aaa", nonce="bbb""#; let src = r#"Digest realm="aaa", nonce="bbb""#;
@ -680,7 +794,6 @@ mod tests {
} }
) )
} }
}
#[test] #[test]
fn test_rfc2069() { fn test_rfc2069() {

@ -154,6 +154,15 @@ impl FromStr for Charset {
} }
} }
impl Display for Charset {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Charset::ASCII => "ASCII",
Charset::UTF8 => "UTF-8",
})
}
}
/// 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)] #[derive(Debug)]
pub enum HttpMethod { pub enum HttpMethod {

Loading…
Cancel
Save