Skip to content

Commit 0e08c70

Browse files
committed
add www-authenticate
1 parent 345224f commit 0e08c70

File tree

1 file changed

+169
-0
lines changed

1 file changed

+169
-0
lines changed

src/auth/www_authenticate.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
use crate::auth::AuthenticationScheme;
2+
use crate::bail2 as bail;
3+
use crate::headers::{HeaderName, HeaderValue, Headers, WWW_AUTHENTICATE};
4+
5+
/// Define the authentication method that should be used to gain access to a
6+
/// resource.
7+
///
8+
/// # Specifications
9+
///
10+
/// - [RFC 7235, section 4.1: WWW-Authenticate](https://tools.ietf.org/html/rfc7235#section-4.1)
11+
///
12+
/// # Implementation Notes
13+
///
14+
/// This implementation only encodes and parses a single authentication method,
15+
/// further authorization methods are ignored. It also always passes the utf-8 encoding flag.
16+
///
17+
/// # Examples
18+
///
19+
/// ```
20+
/// # fn main() -> http_types::Result<()> {
21+
/// #
22+
/// use http_types::Response;
23+
/// use http_types::auth::{AuthenticationScheme, WwwAuthenticate};
24+
///
25+
/// let scheme = AuthenticationScheme::Basic;
26+
/// let realm = "Access to the staging site";
27+
/// let authz = WwwAuthenticate::new(scheme, realm.into());
28+
///
29+
/// let mut res = Response::new(200);
30+
/// authz.apply(&mut res);
31+
///
32+
/// let authz = WwwAuthenticate::from_headers(res)?.unwrap();
33+
///
34+
/// assert_eq!(authz.scheme(), AuthenticationScheme::Basic);
35+
/// assert_eq!(authz.realm(), realm);
36+
/// #
37+
/// # Ok(()) }
38+
/// ```
39+
#[derive(Debug)]
40+
pub struct WwwAuthenticate {
41+
scheme: AuthenticationScheme,
42+
realm: String,
43+
}
44+
45+
impl WwwAuthenticate {
46+
/// Create a new instance of `WwwAuthenticate`.
47+
pub fn new(scheme: AuthenticationScheme, realm: String) -> Self {
48+
Self { scheme, realm }
49+
}
50+
51+
/// Create a new instance from headers.
52+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
53+
let headers = match headers.as_ref().get(WWW_AUTHENTICATE) {
54+
Some(headers) => headers,
55+
None => return Ok(None),
56+
};
57+
58+
// If we successfully parsed the header then there's always at least one
59+
// entry. We want the last entry.
60+
let value = headers.iter().last().unwrap();
61+
62+
let mut iter = value.as_str().splitn(2, ' ');
63+
let scheme = iter.next();
64+
let credential = iter.next();
65+
let (scheme, realm) = match (scheme, credential) {
66+
(None, _) => bail!(400, "Could not find scheme"),
67+
(Some(_), None) => bail!(400, "Could not find realm"),
68+
(Some(scheme), Some(realm)) => (scheme.parse()?, realm.to_owned()),
69+
};
70+
71+
let realm = realm.trim_start();
72+
let realm = match realm.strip_prefix(r#"realm=""#) {
73+
Some(realm) => realm,
74+
None => bail!(400, "realm not found"),
75+
};
76+
77+
let mut chars = realm.chars();
78+
let mut closing_quote = false;
79+
let realm = (&mut chars)
80+
.take_while(|c| {
81+
if c == &'"' {
82+
closing_quote = true;
83+
false
84+
} else {
85+
true
86+
}
87+
})
88+
.collect();
89+
if !closing_quote {
90+
bail!(400, r"Expected a closing quote");
91+
}
92+
93+
Ok(Some(Self { scheme, realm }))
94+
}
95+
96+
/// Sets the header.
97+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
98+
headers.as_mut().insert(self.name(), self.value());
99+
}
100+
101+
/// Get the `HeaderName`.
102+
pub fn name(&self) -> HeaderName {
103+
WWW_AUTHENTICATE
104+
}
105+
106+
/// Get the `HeaderValue`.
107+
pub fn value(&self) -> HeaderValue {
108+
let output = format!(r#"{} realm="{}", charset="UTF-8""#, self.scheme, self.realm);
109+
110+
// SAFETY: the internal string is validated to be ASCII.
111+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
112+
}
113+
114+
/// Get the authorization scheme.
115+
pub fn scheme(&self) -> AuthenticationScheme {
116+
self.scheme
117+
}
118+
119+
/// Set the authorization scheme.
120+
pub fn set_scheme(&mut self, scheme: AuthenticationScheme) {
121+
self.scheme = scheme;
122+
}
123+
124+
/// Get the authorization realm.
125+
pub fn realm(&self) -> &str {
126+
self.realm.as_str()
127+
}
128+
129+
/// Set the authorization realm.
130+
pub fn set_realm(&mut self, realm: String) {
131+
self.realm = realm;
132+
}
133+
}
134+
135+
#[cfg(test)]
136+
mod test {
137+
use super::*;
138+
use crate::headers::Headers;
139+
140+
#[test]
141+
fn smoke() -> crate::Result<()> {
142+
let scheme = AuthenticationScheme::Basic;
143+
let realm = "Access to the staging site";
144+
let authz = WwwAuthenticate::new(scheme, realm.into());
145+
146+
let mut headers = Headers::new();
147+
authz.apply(&mut headers);
148+
149+
assert_eq!(
150+
headers["WWW-Authenticate"],
151+
r#"Basic realm="Access to the staging site", charset="UTF-8""#
152+
);
153+
154+
let authz = WwwAuthenticate::from_headers(headers)?.unwrap();
155+
156+
assert_eq!(authz.scheme(), AuthenticationScheme::Basic);
157+
assert_eq!(authz.realm(), realm);
158+
Ok(())
159+
}
160+
161+
#[test]
162+
fn bad_request_on_parse_error() -> crate::Result<()> {
163+
let mut headers = Headers::new();
164+
headers.insert(WWW_AUTHENTICATE, "<nori ate the tag. yum.>");
165+
let err = WwwAuthenticate::from_headers(headers).unwrap_err();
166+
assert_eq!(err.status(), 400);
167+
Ok(())
168+
}
169+
}

0 commit comments

Comments
 (0)