format code, version bump

pull/4/head
Ondřej Hruška 5 years ago
parent 891c7a183d
commit abda2133bf
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 2
      Cargo.toml
  2. 157
      src/digest.rs

@ -1,6 +1,6 @@
[package] [package]
name = "digest_auth" name = "digest_auth"
version = "0.2.0" version = "0.2.1"
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"

@ -32,7 +32,7 @@ impl QuoteForDigest for String {
} }
/// Join a Vec of Display items using a separator /// Join a Vec of Display items using a separator
fn join_vec<T : ToString>(vec : &Vec<T>, sep : &str) -> String { fn join_vec<T: ToString>(vec: &Vec<T>, sep: &str) -> String {
vec.iter() vec.iter()
.map(ToString::to_string) .map(ToString::to_string)
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -41,7 +41,7 @@ fn join_vec<T : ToString>(vec : &Vec<T>, sep : &str) -> String {
enum NamedTag<'a> { enum NamedTag<'a> {
Quoted(&'a str, Cow<'a, str>), Quoted(&'a str, Cow<'a, str>),
Plain(&'a str, Cow<'a, str>) Plain(&'a str, Cow<'a, str>),
} }
impl Display for NamedTag<'_> { impl Display for NamedTag<'_> {
@ -50,14 +50,11 @@ impl Display for NamedTag<'_> {
NamedTag::Quoted(name, content) => { NamedTag::Quoted(name, content) => {
write!(f, "{}=\"{}\"", name, content.quote_for_digest()) write!(f, "{}=\"{}\"", name, content.quote_for_digest())
} }
NamedTag::Plain(name, content) => { NamedTag::Plain(name, content) => write!(f, "{}={}", name, content),
write!(f, "{}={}", name, content)
}
} }
} }
} }
/// 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)]
@ -148,7 +145,6 @@ fn parse_header_map(input: &str) -> Result<HashMap<String, String>> {
Ok(parsed) Ok(parsed)
} }
/// 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,
@ -174,25 +170,28 @@ impl<'a> AuthContext<'a> {
/// Construct a new context with the GET verb and no payload body. /// Construct a new context with the GET verb and no payload body.
/// See the other constructors if this does not fit your situation. /// See the other constructors if this does not fit your situation.
pub fn new<UN, PW, UR>(username: UN, password: PW, uri: UR) -> Self pub fn new<UN, PW, UR>(username: UN, password: PW, uri: UR) -> Self
where UN: Into<Cow<'a, str>>, where
PW: Into<Cow<'a, str>>, UN: Into<Cow<'a, str>>,
UR: Into<Cow<'a, str>> PW: Into<Cow<'a, str>>,
UR: Into<Cow<'a, str>>,
{ {
Self::new_with_method(username, password, uri, Option::<&'a[u8]>::None, HttpMethod::GET) Self::new_with_method(
username,
password,
uri,
Option::<&'a [u8]>::None,
HttpMethod::GET,
)
} }
/// Construct a new context with the POST verb and a payload body (may be None). /// Construct a new context with the POST verb and a payload body (may be None).
/// See the other constructors if this does not fit your situation. /// See the other constructors if this does not fit your situation.
pub fn new_post<UN, PW, UR, BD>( pub fn new_post<UN, PW, UR, BD>(username: UN, password: PW, uri: UR, body: Option<BD>) -> Self
username: UN, where
password: PW, UN: Into<Cow<'a, str>>,
uri: UR, PW: Into<Cow<'a, str>>,
body: Option<BD>, UR: Into<Cow<'a, str>>,
) -> Self BD: Into<Cow<'a, [u8]>>,
where UN: Into<Cow<'a, str>>,
PW: Into<Cow<'a, str>>,
UR: Into<Cow<'a, str>>,
BD: Into<Cow<'a, [u8]>>,
{ {
Self::new_with_method(username, password, uri, body, HttpMethod::POST) Self::new_with_method(username, password, uri, body, HttpMethod::POST)
} }
@ -205,16 +204,17 @@ impl<'a> AuthContext<'a> {
body: Option<BD>, body: Option<BD>,
method: HttpMethod, method: HttpMethod,
) -> Self ) -> Self
where UN: Into<Cow<'a, str>>, where
PW: Into<Cow<'a, str>>, UN: Into<Cow<'a, str>>,
UR: Into<Cow<'a, str>>, PW: Into<Cow<'a, str>>,
BD: Into<Cow<'a, [u8]>> UR: Into<Cow<'a, str>>,
BD: Into<Cow<'a, [u8]>>,
{ {
Self { Self {
username : username.into(), username: username.into(),
password : password.into(), password: password.into(),
uri : uri.into(), uri: uri.into(),
body : body.map(Into::into), body: body.map(Into::into),
method, method,
cnonce: None, cnonce: None,
} }
@ -223,7 +223,7 @@ impl<'a> AuthContext<'a> {
/// Set cnonce to the given value /// Set cnonce to the given value
pub fn set_custom_cnonce<CN>(&mut self, cnonce: CN) pub fn set_custom_cnonce<CN>(&mut self, cnonce: CN)
where where
CN: Into<Cow<'a, str>> CN: Into<Cow<'a, str>>,
{ {
self.cnonce = Some(cnonce.into()); self.cnonce = Some(cnonce.into());
} }
@ -269,10 +269,7 @@ impl FromStr for WwwAuthenticateHeader {
impl WwwAuthenticateHeader { impl WwwAuthenticateHeader {
/// Generate an [`AuthorizationHeader`](struct.AuthorizationHeader.html) to be sent to the server in a new request. /// Generate an [`AuthorizationHeader`](struct.AuthorizationHeader.html) to be sent to the server in a new request.
/// The [`self.nc`](struct.AuthorizationHeader.html#structfield.nc) field is incremented. /// The [`self.nc`](struct.AuthorizationHeader.html#structfield.nc) field is incremented.
pub fn respond( pub fn respond(&mut self, secrets: &AuthContext) -> Result<AuthorizationHeader> {
&mut self,
secrets: &AuthContext,
) -> Result<AuthorizationHeader> {
AuthorizationHeader::from_prompt(self, secrets) AuthorizationHeader::from_prompt(self, secrets)
} }
@ -357,7 +354,10 @@ impl Display for WwwAuthenticateHeader {
entries.push(NamedTag::Plain("stale", "true".into())); entries.push(NamedTag::Plain("stale", "true".into()));
} }
entries.push(NamedTag::Plain("algorithm", self.algorithm.to_string().into())); entries.push(NamedTag::Plain(
"algorithm",
self.algorithm.to_string().into(),
));
entries.push(NamedTag::Quoted("nonce", (&self.nonce).into())); entries.push(NamedTag::Quoted("nonce", (&self.nonce).into()));
if let Some(ref opaque) = self.opaque { if let Some(ref opaque) = self.opaque {
entries.push(NamedTag::Quoted("opaque", (opaque).into())); entries.push(NamedTag::Quoted("opaque", (opaque).into()));
@ -369,7 +369,9 @@ impl Display for WwwAuthenticateHeader {
} }
for (i, e) in entries.iter().enumerate() { for (i, e) in entries.iter().enumerate() {
if i > 0 { f.write_str(", ")?; } if i > 0 {
f.write_str(", ")?;
}
f.write_str(&e.to_string())?; f.write_str(&e.to_string())?;
} }
@ -385,15 +387,15 @@ impl Display for WwwAuthenticateHeader {
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct AuthorizationHeader { pub struct AuthorizationHeader {
/// Authorization realm /// Authorization realm
pub realm : String, pub realm: String,
/// Server nonce /// Server nonce
pub nonce: String, pub nonce: String,
/// Server opaque /// Server opaque
pub opaque: Option<String>, pub opaque: Option<String>,
/// Flag that userhash was used /// Flag that userhash was used
pub userhash : bool, pub userhash: bool,
/// Hash algorithm /// Hash algorithm
pub algorithm : Algorithm, pub algorithm: Algorithm,
/// Computed digest /// Computed digest
pub response: String, pub response: String,
/// Username or hash (owned because of the computed hash) /// Username or hash (owned because of the computed hash)
@ -428,7 +430,6 @@ impl AuthorizationHeader {
prompt: &mut WwwAuthenticateHeader, prompt: &mut WwwAuthenticateHeader,
context: &AuthContext, context: &AuthContext,
) -> Result<AuthorizationHeader> { ) -> Result<AuthorizationHeader> {
let qop = match &prompt.qop { let qop = match &prompt.qop {
None => None, None => None,
Some(vec) => { Some(vec) => {
@ -453,11 +454,15 @@ impl AuthorizationHeader {
opaque: prompt.opaque.clone(), opaque: prompt.opaque.clone(),
userhash: prompt.userhash, userhash: prompt.userhash,
algorithm: prompt.algorithm, algorithm: prompt.algorithm,
response : String::default(), response: String::default(),
username : String::default(), username: String::default(),
uri: context.uri.as_ref().into(), uri: context.uri.as_ref().into(),
qop, qop,
cnonce: context.cnonce.as_ref().map(AsRef::as_ref).map(ToOwned::to_owned), // Will be generated if needed, if build_hash is set and this is None cnonce: context
.cnonce
.as_ref()
.map(AsRef::as_ref)
.map(ToOwned::to_owned), // Will be generated if needed, if build_hash is set and this is None
nc: prompt.nc, nc: prompt.nc,
}; };
@ -479,8 +484,7 @@ impl AuthorizationHeader {
/// - cnonce (if it was None before) /// - cnonce (if it was None before)
/// - username copied from context /// - username copied from context
/// - response /// - response
pub fn digest(&mut self, context : &AuthContext) pub fn digest(&mut self, context: &AuthContext) {
{
// figure out which QOP option to use // figure out which QOP option to use
let qop_algo = match self.qop { let qop_algo = match self.qop {
None => QopAlgo::NONE, None => QopAlgo::NONE,
@ -492,9 +496,7 @@ impl AuthorizationHeader {
QopAlgo::AUTH QopAlgo::AUTH
} }
} }
Some(Qop::AUTH) => { Some(Qop::AUTH) => QopAlgo::AUTH,
QopAlgo::AUTH
}
}; };
let h = &self.algorithm; let h = &self.algorithm;
@ -554,7 +556,7 @@ impl AuthorizationHeader {
username = context.username, username = context.username,
realm = self.realm realm = self.realm
) )
.as_bytes(), .as_bytes(),
) )
} else { } else {
context.username.as_ref().to_owned() context.username.as_ref().to_owned()
@ -653,7 +655,7 @@ impl AuthorizationHeader {
if auth.qop.is_some() { if auth.qop.is_some() {
if !auth.cnonce.is_some() { if !auth.cnonce.is_some() {
return Err(MissingRequired("cnonce", input.into())) return Err(MissingRequired("cnonce", input.into()));
} }
} else { } else {
// cnonce must not be set if qop is not given, clear it. // cnonce must not be set if qop is not given, clear it.
@ -676,9 +678,15 @@ impl Display for AuthorizationHeader {
entries.push(NamedTag::Quoted("uri", (&self.uri).into())); entries.push(NamedTag::Quoted("uri", (&self.uri).into()));
if self.qop.is_some() && self.cnonce.is_some() { if self.qop.is_some() && self.cnonce.is_some() {
entries.push(NamedTag::Plain("qop", self.qop.as_ref().unwrap().to_string().into())); entries.push(NamedTag::Plain(
"qop",
self.qop.as_ref().unwrap().to_string().into(),
));
entries.push(NamedTag::Plain("nc", format!("{:08x}", self.nc).into())); entries.push(NamedTag::Plain("nc", format!("{:08x}", self.nc).into()));
entries.push(NamedTag::Quoted("cnonce", self.cnonce.as_ref().unwrap().into())); entries.push(NamedTag::Quoted(
"cnonce",
self.cnonce.as_ref().unwrap().into(),
));
} }
entries.push(NamedTag::Quoted("response", (&self.response).into())); entries.push(NamedTag::Quoted("response", (&self.response).into()));
@ -689,7 +697,10 @@ impl Display for AuthorizationHeader {
// 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.algorithm.algo != AlgorithmType::MD5 { if self.qop.is_some() || self.algorithm.algo != AlgorithmType::MD5 {
entries.push(NamedTag::Plain("algorithm", self.algorithm.to_string().into())); entries.push(NamedTag::Plain(
"algorithm",
self.algorithm.to_string().into(),
));
} }
if self.userhash { if self.userhash {
@ -697,7 +708,9 @@ impl Display for AuthorizationHeader {
} }
for (i, e) in entries.iter().enumerate() { for (i, e) in entries.iter().enumerate() {
if i > 0 { f.write_str(", ")?; } if i > 0 {
f.write_str(", ")?;
}
f.write_str(&e.to_string())?; f.write_str(&e.to_string())?;
} }
@ -756,8 +769,7 @@ mod tests {
} }
#[test] #[test]
fn test_parse_header_map2() 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");
@ -836,38 +848,47 @@ mod tests {
}; };
assert_eq!( assert_eq!(
r#"Digest realm="api@example.org", r#"Digest 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",
charset=UTF-8, charset=UTF-8,
userhash=true"#.replace(",\n ", ", "), hdr.to_string()); userhash=true"#
.replace(",\n ", ", "),
hdr.to_string()
);
hdr.stale=true; hdr.stale = true;
hdr.userhash=false; hdr.userhash = false;
hdr.opaque = None; hdr.opaque = None;
hdr.qop = None; hdr.qop = None;
assert_eq!( assert_eq!(
r#"Digest realm="api@example.org", r#"Digest realm="api@example.org",
domain="/my/nice/url /login /logout", domain="/my/nice/url /login /logout",
stale=true, stale=true,
algorithm=SHA-512-256, algorithm=SHA-512-256,
nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK",
charset=UTF-8"#.replace(",\n ", ", "), hdr.to_string()); charset=UTF-8"#
.replace(",\n ", ", "),
hdr.to_string()
);
hdr.qop = Some(vec![Qop::AUTH, Qop::AUTH_INT]); hdr.qop = Some(vec![Qop::AUTH, Qop::AUTH_INT]);
assert_eq!( assert_eq!(
r#"Digest realm="api@example.org", r#"Digest realm="api@example.org",
qop="auth, auth-int", qop="auth, auth-int",
domain="/my/nice/url /login /logout", domain="/my/nice/url /login /logout",
stale=true, stale=true,
algorithm=SHA-512-256, algorithm=SHA-512-256,
nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK",
charset=UTF-8"#.replace(",\n ", ", "), hdr.to_string()); charset=UTF-8"#
.replace(",\n ", ", "),
hdr.to_string()
);
} }
#[test] #[test]
@ -949,7 +970,7 @@ Digest username="Mufasa",
response="1949323746fe6a43ef61f9606e7febea", response="1949323746fe6a43ef61f9606e7febea",
opaque="5ccc069c403ebaf9f0171e9517f40e41" opaque="5ccc069c403ebaf9f0171e9517f40e41"
"# "#
.trim() .trim()
); );
// Try round trip // Try round trip
@ -992,7 +1013,7 @@ Digest username="Mufasa",
opaque="5ccc069c403ebaf9f0171e9517f40e41", opaque="5ccc069c403ebaf9f0171e9517f40e41",
algorithm=MD5 algorithm=MD5
"# "#
.trim() .trim()
); );
// Try round trip // Try round trip
@ -1033,7 +1054,7 @@ Digest username="Mufasa",
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS",
algorithm=MD5 algorithm=MD5
"# "#
.trim() .trim()
); );
// Try round trip // Try round trip
@ -1085,7 +1106,7 @@ Digest username="Mufasa",
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS",
algorithm=SHA-256 algorithm=SHA-256
"# "#
.trim() .trim()
); );
// Try round trip // Try round trip

Loading…
Cancel
Save