Skip to content

Commit 9414c5b

Browse files
committed
Add HTTP BasicAuth
1 parent 08f7472 commit 9414c5b

File tree

4 files changed

+184
-1
lines changed

4 files changed

+184
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ serde = { version = "1.0.106", features = ["derive"] }
4141
serde_urlencoded = "0.7.0"
4242
rand = "0.7.3"
4343
serde_qs = "0.7.0"
44+
base64 = "0.13.0"
4445

4546
[dev-dependencies]
4647
http = "0.2.0"

src/auth/authorization.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ impl Authorization {
7171
}))
7272
}
7373

74-
/// Sets the `If-Match` header.
74+
/// Sets the header.
7575
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
7676
headers.as_mut().insert(self.name(), self.value());
7777
}
@@ -94,10 +94,20 @@ impl Authorization {
9494
self.scheme
9595
}
9696

97+
/// Set the authorization scheme.
98+
pub fn set_scheme(&mut self, scheme: AuthenticationScheme) {
99+
self.scheme = scheme;
100+
}
101+
97102
/// Get the authorization credentials.
98103
pub fn credentials(&self) -> &str {
99104
self.credentials.as_str()
100105
}
106+
107+
/// Set the authorization credentials.
108+
pub fn set_credentials(&mut self, credentials: String) {
109+
self.credentials = credentials;
110+
}
101111
}
102112

103113
#[cfg(test)]

src/auth/basic_auth.rs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
use crate::auth::{AuthenticationScheme, Authorization};
2+
use crate::format_err;
3+
use crate::headers::{HeaderName, HeaderValue, Headers, AUTHORIZATION};
4+
use crate::Status;
5+
6+
/// HTTP Basic authorization.
7+
///
8+
/// # Specifications
9+
///
10+
/// - [RFC7617](https://tools.ietf.org/html/rfc7617)
11+
///
12+
/// # Examples
13+
///
14+
/// ```
15+
/// # fn main() -> http_types::Result<()> {
16+
/// #
17+
/// use http_types::Response;
18+
/// use http_types::auth::{AuthenticationScheme, BasicAuth};
19+
///
20+
/// let username = "nori";
21+
/// let password = "secret_fish!!";
22+
/// let authz = BasicAuth::new(username, Some(password));
23+
///
24+
/// let mut res = Response::new(200);
25+
/// authz.apply(&mut res);
26+
///
27+
/// let authz = BasicAuth::from_headers(res)?.unwrap();
28+
///
29+
/// assert_eq!(authz.username(), username);
30+
/// assert_eq!(authz.password(), Some(password));
31+
/// #
32+
/// # Ok(()) }
33+
/// ```
34+
#[derive(Debug)]
35+
pub struct BasicAuth {
36+
username: String,
37+
password: Option<String>,
38+
}
39+
40+
impl BasicAuth {
41+
/// Create a new instance of `BasicAuth`.
42+
pub fn new<U, P>(username: U, password: Option<P>) -> Self
43+
where
44+
U: AsRef<str>,
45+
P: AsRef<str>,
46+
{
47+
let username = username.as_ref().to_owned();
48+
let password = password.map(|p| p.as_ref().to_owned());
49+
Self { username, password }
50+
}
51+
52+
/// Create a new instance from headers.
53+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
54+
let auth = match Authorization::from_headers(headers)? {
55+
Some(auth) => auth,
56+
None => return Ok(None),
57+
};
58+
59+
let scheme = auth.scheme();
60+
if !matches!(scheme, AuthenticationScheme::Basic) {
61+
let mut err = format_err!("Expected basic auth scheme found `{}`", scheme);
62+
err.set_status(400);
63+
return Err(err);
64+
}
65+
66+
let bytes = base64::decode(auth.credentials()).status(400)?;
67+
let credentials = String::from_utf8(bytes).status(400)?;
68+
69+
let mut iter = credentials.splitn(2, ':');
70+
let username = iter.next();
71+
let password = iter.next();
72+
73+
let (username, password) = match (username, password) {
74+
(Some(username), Some(password)) => (username.to_string(), Some(password.to_string())),
75+
(Some(username), None) => (username.to_string(), None),
76+
(None, _) => {
77+
let mut err = format_err!("Expected basic auth to contain a username");
78+
err.set_status(400);
79+
return Err(err);
80+
}
81+
};
82+
83+
Ok(Some(Self { username, password }))
84+
}
85+
86+
/// Sets the header.
87+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
88+
headers.as_mut().insert(self.name(), self.value());
89+
}
90+
91+
/// Get the `HeaderName`.
92+
pub fn name(&self) -> HeaderName {
93+
AUTHORIZATION
94+
}
95+
96+
/// Get the `HeaderValue`.
97+
pub fn value(&self) -> HeaderValue {
98+
let scheme = AuthenticationScheme::Basic;
99+
let credentials = match self.password.as_ref() {
100+
Some(password) => base64::encode(format!("{}:{}", self.username, password)),
101+
None => base64::encode(self.username.clone()),
102+
};
103+
let auth = Authorization::new(scheme, credentials);
104+
auth.value()
105+
}
106+
107+
/// Get the username.
108+
pub fn username(&self) -> &str {
109+
self.username.as_str()
110+
}
111+
112+
/// Get the password.
113+
pub fn password(&self) -> Option<&str> {
114+
self.password.as_deref()
115+
}
116+
}
117+
118+
#[cfg(test)]
119+
mod test {
120+
use super::*;
121+
use crate::headers::Headers;
122+
123+
#[test]
124+
fn smoke() -> crate::Result<()> {
125+
let username = "nori";
126+
let password = "secret_fish!!";
127+
let authz = BasicAuth::new(username, Some(password));
128+
129+
let mut headers = Headers::new();
130+
authz.apply(&mut headers);
131+
132+
let authz = BasicAuth::from_headers(headers)?.unwrap();
133+
134+
assert_eq!(authz.username(), username);
135+
assert_eq!(authz.password(), Some(password));
136+
Ok(())
137+
}
138+
139+
#[test]
140+
fn bad_request_on_parse_error() -> crate::Result<()> {
141+
let mut headers = Headers::new();
142+
headers.insert(AUTHORIZATION, "<nori ate the tag. yum.>");
143+
let err = BasicAuth::from_headers(headers).unwrap_err();
144+
assert_eq!(err.status(), 400);
145+
Ok(())
146+
}
147+
}

src/auth/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,32 @@
11
//! HTTP authentication and authorization.
2+
//!
3+
//! # Examples
4+
//!
5+
//! ```
6+
//! # fn main() -> http_types::Result<()> {
7+
//! #
8+
//! use http_types::Response;
9+
//! use http_types::auth::{AuthenticationScheme, BasicAuth};
10+
//!
11+
//! let username = "nori";
12+
//! let password = "secret_fish!!";
13+
//! let authz = BasicAuth::new(username, Some(password));
14+
//!
15+
//! let mut res = Response::new(200);
16+
//! authz.apply(&mut res);
17+
//!
18+
//! let authz = BasicAuth::from_headers(res)?.unwrap();
19+
//!
20+
//! assert_eq!(authz.username(), username);
21+
//! assert_eq!(authz.password(), Some(password));
22+
//! #
23+
//! # Ok(()) }
24+
//! ```
225
326
mod authentication_scheme;
427
mod authorization;
28+
mod basic_auth;
529

630
pub use authentication_scheme::AuthenticationScheme;
731
pub use authorization::Authorization;
32+
pub use basic_auth::BasicAuth;

0 commit comments

Comments
 (0)