Skip to content

Commit 08f7472

Browse files
committed
Add Authorization header
1 parent 5748ce4 commit 08f7472

File tree

2 files changed

+139
-6
lines changed

2 files changed

+139
-6
lines changed

src/auth/authentication_scheme.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use std::fmt::{self, Display};
22
use std::str::FromStr;
33

4-
use crate::bail;
4+
use crate::format_err;
55

66
/// HTTP Mutual Authentication Algorithms
7-
#[derive(Debug, PartialEq, Eq)]
7+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
88
#[non_exhaustive]
99
pub enum AuthenticationScheme {
1010
/// [RFC7617](https://tools.ietf.org/html/rfc7617) Basic auth
@@ -13,13 +13,13 @@ pub enum AuthenticationScheme {
1313
Bearer,
1414
/// [RFC7616](https://tools.ietf.org/html/rfc7616) Digest auth
1515
Digest,
16-
/// [RFC7486](https://tools.ietf.org/html/rfc7486) HTTP Origin-Bound Authentication
16+
/// [RFC7486](https://tools.ietf.org/html/rfc7486) HTTP Origin-Bound Authentication (HOBA)
1717
Hoba,
1818
/// [RFC8120](https://tools.ietf.org/html/rfc8120) Mutual auth
1919
Mutual,
2020
/// [RFC4559](https://tools.ietf.org/html/rfc4559) Negotiate auth
2121
Negotiate,
22-
/// [RFC5849](https://tools.ietf.org/html/rfc5849) Oauth
22+
/// [RFC5849](https://tools.ietf.org/html/rfc5849) OAuth
2323
OAuth,
2424
/// [RFC7804](https://tools.ietf.org/html/rfc7804) SCRAM SHA1 auth
2525
ScramSha1,
@@ -64,7 +64,11 @@ impl FromStr for AuthenticationScheme {
6464
"scram-sha-1" => Ok(Self::ScramSha1),
6565
"scram-sha-256" => Ok(Self::ScramSha256),
6666
"vapid" => Ok(Self::Vapid),
67-
s => bail!("`{}` is not a recognized authentication scheme", s),
67+
s => {
68+
let mut err = format_err!("`{}` is not a recognized authentication scheme", s);
69+
err.set_status(400);
70+
Err(err)
71+
}
6872
}
6973
}
7074
}

src/auth/authorization.rs

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,132 @@
1+
use crate::auth::AuthenticationScheme;
2+
use crate::bail;
3+
use crate::headers::{HeaderName, HeaderValue, Headers, AUTHORIZATION};
4+
15
/// Credentials to authenticate a user agent with a server.
6+
///
7+
/// # Specifications
8+
///
9+
/// - [RFC 7235, section 4.2: Authorization](https://tools.ietf.org/html/rfc7232#section-3.4)
10+
///
11+
/// # Examples
12+
///
13+
/// ```
14+
/// # fn main() -> http_types::Result<()> {
15+
/// #
16+
/// use http_types::Response;
17+
/// use http_types::auth::{AuthenticationScheme, Authorization};
18+
///
19+
/// let scheme = AuthenticationScheme::Basic;
20+
/// let credentials = "0xdeadbeef202020";
21+
/// let authz = Authorization::new(scheme, credentials.into());
22+
///
23+
/// let mut res = Response::new(200);
24+
/// authz.apply(&mut res);
25+
///
26+
/// let authz = Authorization::from_headers(res)?.unwrap();
27+
///
28+
/// assert_eq!(authz.scheme(), AuthenticationScheme::Basic);
29+
/// assert_eq!(authz.credentials(), credentials);
30+
/// #
31+
/// # Ok(()) }
32+
/// ```
233
#[derive(Debug)]
3-
pub struct Authorization;
34+
pub struct Authorization {
35+
scheme: AuthenticationScheme,
36+
credentials: String,
37+
}
38+
39+
impl Authorization {
40+
/// Create a new instance of `Authorization`.
41+
pub fn new(scheme: AuthenticationScheme, credentials: String) -> Self {
42+
Self {
43+
scheme,
44+
credentials,
45+
}
46+
}
47+
48+
/// Create a new instance from headers.
49+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
50+
let headers = match headers.as_ref().get(AUTHORIZATION) {
51+
Some(headers) => headers,
52+
None => return Ok(None),
53+
};
54+
55+
// If we successfully parsed the header then there's always at least one
56+
// entry. We want the last entry.
57+
let value = headers.iter().last().unwrap();
58+
59+
let mut iter = value.as_str().splitn(2, ' ');
60+
let scheme = iter.next();
61+
let credential = iter.next();
62+
let (scheme, credentials) = match (scheme, credential) {
63+
(None, _) => bail!("Could not find scheme"),
64+
(Some(_), None) => bail!("Could not find credentials"),
65+
(Some(scheme), Some(credentials)) => (scheme.parse()?, credentials.to_owned()),
66+
};
67+
68+
Ok(Some(Self {
69+
scheme,
70+
credentials,
71+
}))
72+
}
73+
74+
/// Sets the `If-Match` header.
75+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
76+
headers.as_mut().insert(self.name(), self.value());
77+
}
78+
79+
/// Get the `HeaderName`.
80+
pub fn name(&self) -> HeaderName {
81+
AUTHORIZATION
82+
}
83+
84+
/// Get the `HeaderValue`.
85+
pub fn value(&self) -> HeaderValue {
86+
let output = format!("{} {}", self.scheme, self.credentials);
87+
88+
// SAFETY: the internal string is validated to be ASCII.
89+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
90+
}
91+
92+
/// Get the authorization scheme.
93+
pub fn scheme(&self) -> AuthenticationScheme {
94+
self.scheme
95+
}
96+
97+
/// Get the authorization credentials.
98+
pub fn credentials(&self) -> &str {
99+
self.credentials.as_str()
100+
}
101+
}
102+
103+
#[cfg(test)]
104+
mod test {
105+
use super::*;
106+
use crate::headers::Headers;
107+
108+
#[test]
109+
fn smoke() -> crate::Result<()> {
110+
let scheme = AuthenticationScheme::Basic;
111+
let credentials = "0xdeadbeef202020";
112+
let authz = Authorization::new(scheme, credentials.into());
113+
114+
let mut headers = Headers::new();
115+
authz.apply(&mut headers);
116+
117+
let authz = Authorization::from_headers(headers)?.unwrap();
118+
119+
assert_eq!(authz.scheme(), AuthenticationScheme::Basic);
120+
assert_eq!(authz.credentials(), credentials);
121+
Ok(())
122+
}
123+
124+
#[test]
125+
fn bad_request_on_parse_error() -> crate::Result<()> {
126+
let mut headers = Headers::new();
127+
headers.insert(AUTHORIZATION, "<nori ate the tag. yum.>");
128+
let err = Authorization::from_headers(headers).unwrap_err();
129+
assert_eq!(err.status(), 400);
130+
Ok(())
131+
}
132+
}

0 commit comments

Comments
 (0)