use std::sync::Mutex;
#[derive(Debug)]
pub(crate) struct Authentication {
username: String,
password: String,
prompt: Mutex<Option<digest_auth::WwwAuthenticateHeader>>,
}
impl Clone for Authentication {
fn clone(&self) -> Self {
Authentication {
username: self.username.clone(),
password: self.password.clone(),
prompt: Mutex::new(self.prompt.lock().unwrap().as_ref().cloned()),
}
}
}
impl Authentication {
pub fn new(username: &str, password: &str) -> Self {
Self {
username: username.to_owned(),
password: password.to_owned(),
prompt: Mutex::new(None),
}
}
pub fn should_retry(&self, parts: &http::response::Parts) -> bool {
let header = match parts.headers.get(http::header::WWW_AUTHENTICATE) {
Some(value) => value.as_bytes(),
None => return false,
};
let header = match std::str::from_utf8(header) {
Ok(str) => str,
Err(_) => return false,
};
let header = match digest_auth::WwwAuthenticateHeader::parse(header) {
Ok(header) => header,
Err(_) => return false,
};
self.prompt.lock().unwrap().replace(header);
parts.status == 401
}
pub fn authorization_for(
&self,
method: &http::Method,
path_and_query: &http::uri::PathAndQuery,
body: &[u8],
) -> Option<String> {
let ctx = digest_auth::AuthContext::new_with_method(
&self.username,
&self.password,
path_and_query.as_str(),
if !body.is_empty() { Some(body) } else { None },
match method.as_str() {
"GET" => digest_auth::HttpMethod::GET,
"HEAD" => digest_auth::HttpMethod::HEAD,
"POST" => digest_auth::HttpMethod::POST,
"DELETE" => digest_auth::HttpMethod::OTHER("DELETE"),
"TRACE" => digest_auth::HttpMethod::OTHER("TRACE"),
"CONNECT" => digest_auth::HttpMethod::OTHER("CONNECT"),
"PATCH" => digest_auth::HttpMethod::OTHER("PATCH"),
_ => digest_auth::HttpMethod::POST, },
);
self.prompt
.lock()
.unwrap()
.as_mut()
.and_then(|prompt| prompt.respond(&ctx).ok())
.map(|h| h.to_header_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
const AUTH_HEADER_1: &'static str = r#"Digest realm="AXIS_ACCC8EF7DE6B", nonce="h20V+wGvBQA=b6c0ce8666d2d4b2688858d7a31386d9d337e072", algorithm=MD5, qop="auth""#;
const AUTH_HEADER_2: &'static str = r#"Digest realm="AXIS_ACCC8EF7D108", nonce="kNEbGQKvBQA=cc9e12e5fbcf71667f58f1a4dbbbc952a1cd7d97", algorithm=MD5, qop="auth""#;
#[test]
fn usage() {
let auth = Authentication::new("user", "pass");
assert_eq!(
auth.authorization_for(
&http::Method::GET,
&http::uri::PathAndQuery::from_static("/foo?bar"),
&[]
),
None
);
let resp = http::Response::builder()
.status(http::StatusCode::OK)
.body(())
.unwrap();
assert_eq!(auth.should_retry(&resp.into_parts().0), false);
assert!(auth.prompt.lock().unwrap().is_none());
let resp = http::Response::builder()
.status(http::StatusCode::UNAUTHORIZED)
.body(())
.unwrap();
assert_eq!(auth.should_retry(&resp.into_parts().0), false);
assert!(auth.prompt.lock().unwrap().is_none());
let resp = http::Response::builder()
.status(http::StatusCode::UNAUTHORIZED)
.header(
http::header::WWW_AUTHENTICATE,
http::HeaderValue::from_bytes(&b"\xfe\xfd"[..]).unwrap(),
)
.body(())
.unwrap();
assert_eq!(auth.should_retry(&resp.into_parts().0), false);
assert!(auth.prompt.lock().unwrap().is_none());
let resp = http::Response::builder()
.status(http::StatusCode::UNAUTHORIZED)
.header(
http::header::WWW_AUTHENTICATE,
http::HeaderValue::from_static("bogus"),
)
.body(())
.unwrap();
assert_eq!(auth.should_retry(&resp.into_parts().0), false);
assert!(auth.prompt.lock().unwrap().is_none());
let resp = http::Response::builder()
.status(http::StatusCode::UNAUTHORIZED)
.header(
http::header::WWW_AUTHENTICATE,
http::HeaderValue::from_static(AUTH_HEADER_1),
)
.body(())
.unwrap();
assert_eq!(auth.should_retry(&resp.into_parts().0), true);
assert!(auth.prompt.lock().unwrap().is_some());
let prompt = auth.prompt.lock().unwrap().as_ref().cloned().unwrap();
assert_ne!(
auth.authorization_for(
&http::Method::GET,
&http::uri::PathAndQuery::from_static("/foo?bar"),
&[]
),
None
);
assert_ne!(auth.prompt.lock().unwrap().as_ref().unwrap(), &prompt);
let prompt = auth.prompt.lock().unwrap().as_ref().cloned().unwrap();
let resp = http::Response::builder()
.status(http::StatusCode::OK)
.header(
http::header::WWW_AUTHENTICATE,
http::HeaderValue::from_static(AUTH_HEADER_2),
)
.body(())
.unwrap();
assert_eq!(auth.should_retry(&resp.into_parts().0), false);
assert_ne!(auth.prompt.lock().unwrap().as_ref().unwrap(), &prompt);
}
#[test]
fn clone() {
let auth = Authentication::new("user", "pass");
let resp = http::Response::builder()
.status(http::StatusCode::OK)
.header(
http::header::WWW_AUTHENTICATE,
http::HeaderValue::from_static(AUTH_HEADER_1),
)
.body(())
.unwrap();
auth.should_retry(&resp.into_parts().0);
assert!(auth.prompt.lock().unwrap().is_some());
let auth2 = auth.clone();
assert_eq!(
auth.prompt.lock().unwrap().as_ref(),
auth2.prompt.lock().unwrap().as_ref()
);
auth.authorization_for(
&http::Method::OPTIONS,
&http::uri::PathAndQuery::from_static("/doesnt_matter"),
&[],
);
assert_ne!(
auth.prompt.lock().unwrap().as_ref(),
auth2.prompt.lock().unwrap().as_ref()
);
}
}