format code, version bump
This commit is contained in:
+1
-1
@@ -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"
|
||||||
|
|||||||
+63
-42
@@ -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,13 +50,10 @@ 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>> {
|
||||||
@@ -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,22 +170,25 @@ 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
|
||||||
|
UN: Into<Cow<'a, str>>,
|
||||||
PW: Into<Cow<'a, str>>,
|
PW: Into<Cow<'a, str>>,
|
||||||
UR: 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,
|
|
||||||
body: Option<BD>,
|
|
||||||
) -> Self
|
|
||||||
where UN: Into<Cow<'a, str>>,
|
|
||||||
PW: Into<Cow<'a, str>>,
|
PW: Into<Cow<'a, str>>,
|
||||||
UR: Into<Cow<'a, str>>,
|
UR: Into<Cow<'a, str>>,
|
||||||
BD: Into<Cow<'a, [u8]>>,
|
BD: Into<Cow<'a, [u8]>>,
|
||||||
@@ -205,10 +204,11 @@ impl<'a> AuthContext<'a> {
|
|||||||
body: Option<BD>,
|
body: Option<BD>,
|
||||||
method: HttpMethod,
|
method: HttpMethod,
|
||||||
) -> Self
|
) -> Self
|
||||||
where UN: Into<Cow<'a, str>>,
|
where
|
||||||
|
UN: Into<Cow<'a, str>>,
|
||||||
PW: Into<Cow<'a, str>>,
|
PW: Into<Cow<'a, str>>,
|
||||||
UR: Into<Cow<'a, str>>,
|
UR: Into<Cow<'a, str>>,
|
||||||
BD: Into<Cow<'a, [u8]>>
|
BD: Into<Cow<'a, [u8]>>,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
username: username.into(),
|
username: username.into(),
|
||||||
@@ -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())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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) => {
|
||||||
@@ -457,7 +458,11 @@ impl AuthorizationHeader {
|
|||||||
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;
|
||||||
@@ -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");
|
||||||
@@ -843,7 +855,10 @@ r#"Digest realm="api@example.org",
|
|||||||
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;
|
||||||
@@ -856,7 +871,10 @@ r#"Digest realm="api@example.org",
|
|||||||
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]);
|
||||||
|
|
||||||
@@ -867,7 +885,10 @@ r#"Digest realm="api@example.org",
|
|||||||
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]
|
||||||
|
|||||||
Reference in New Issue
Block a user