1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
// Copyright (C) 2021 Scott Lamb <slamb@slamb.org>
// SPDX-License-Identifier: MIT OR Apache-2.0
//! `Basic` authentication scheme as in
//! [RFC 7617](https://datatracker.ietf.org/doc/html/rfc7617).
use std::convert::TryFrom;
use crate::ChallengeRef;
/// Encodes the given credentials.
///
/// This can be used to preemptively send `Basic` authentication, without
/// sending an unauthenticated request and waiting for a `401 Unauthorized`
/// response.
///
/// The caller should use the returned string as an `Authorization` or
/// `Proxy-Authorization` header value.
///
/// The caller is responsible for `username` and `password` being in the
/// correct format. Servers may expect arguments to be in Unicode
/// Normalization Form C as noted in [RFC 7617 section
/// 2.1](https://datatracker.ietf.org/doc/html/rfc7617#section-2.1).
///
/// ```rust
/// assert_eq!(
/// http_auth::basic::encode_credentials("Aladdin", "open sesame"),
/// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
/// );
pub fn encode_credentials(username: &str, password: &str) -> String {
use base64::Engine as _;
let user_pass = format!("{}:{}", username, password);
const PREFIX: &str = "Basic ";
let mut value = String::with_capacity(PREFIX.len() + base64_encoded_len(user_pass.len()));
value.push_str(PREFIX);
base64::engine::general_purpose::STANDARD.encode_string(&user_pass[..], &mut value);
value
}
/// Returns the base64-encoded length for the given input length, including padding.
fn base64_encoded_len(input_len: usize) -> usize {
(input_len + 2) / 3 * 4
}
/// Client for a `Basic` challenge, as in
/// [RFC 7617](https://datatracker.ietf.org/doc/html/rfc7617).
///
/// This implementation always uses `UTF-8`. Thus it doesn't use or store the
/// `charset` parameter, which the RFC only allows to be set to `UTF-8` anyway.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BasicClient {
realm: Box<str>,
}
impl BasicClient {
pub fn realm(&self) -> &str {
&self.realm
}
/// Responds to the challenge with the supplied parameters.
///
/// This is functionally identical to [`encode_credentials`]; no parameters
/// of the `BasicClient` are needed to produce the credentials.
#[inline]
pub fn respond(&self, username: &str, password: &str) -> String {
encode_credentials(username, password)
}
}
impl TryFrom<&ChallengeRef<'_>> for BasicClient {
type Error = String;
fn try_from(value: &ChallengeRef<'_>) -> Result<Self, Self::Error> {
if !value.scheme.eq_ignore_ascii_case("Basic") {
return Err(format!(
"BasicClient doesn't support challenge scheme {:?}",
value.scheme
));
}
let mut realm = None;
for (k, v) in &value.params {
if k.eq_ignore_ascii_case("realm") {
realm = Some(v.to_unescaped());
}
}
let realm = realm.ok_or("missing required parameter realm")?;
Ok(BasicClient {
realm: realm.into_boxed_str(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic() {
// Example from https://datatracker.ietf.org/doc/html/rfc7617#section-2
let ctx = BasicClient {
realm: "WallyWorld".into(),
};
assert_eq!(
ctx.respond("Aladdin", "open sesame"),
"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
);
// Example from https://datatracker.ietf.org/doc/html/rfc7617#section-2.1
// Note that this crate *always* uses UTF-8, not just when the server requests it.
let ctx = BasicClient {
realm: "foo".into(),
};
assert_eq!(ctx.respond("test", "123\u{A3}"), "Basic dGVzdDoxMjPCow==");
}
}
|