diff options
Diffstat (limited to 'third_party/rust/headers/src/common/authorization.rs')
-rw-r--r-- | third_party/rust/headers/src/common/authorization.rs | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/third_party/rust/headers/src/common/authorization.rs b/third_party/rust/headers/src/common/authorization.rs new file mode 100644 index 0000000000..dbef15301a --- /dev/null +++ b/third_party/rust/headers/src/common/authorization.rs @@ -0,0 +1,276 @@ +//! Authorization header and types. + +use base64; +use bytes::Bytes; + +use util::HeaderValueString; +use HeaderValue; + +/// `Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.2) +/// +/// The `Authorization` header field allows a user agent to authenticate +/// itself with an origin server -- usually, but not necessarily, after +/// receiving a 401 (Unauthorized) response. Its value consists of +/// credentials containing the authentication information of the user +/// agent for the realm of the resource being requested. +/// +/// # ABNF +/// +/// ```text +/// Authorization = credentials +/// ``` +/// +/// # Example values +/// * `Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==` +/// * `Bearer fpKL54jvWmEGVoRdCNjG` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::Authorization; +/// +/// let basic = Authorization::basic("Aladdin", "open sesame"); +/// let bearer = Authorization::bearer("some-opaque-token").unwrap(); +/// ``` +/// +#[derive(Clone, PartialEq, Debug)] +pub struct Authorization<C: Credentials>(pub C); + +impl Authorization<Basic> { + /// Create a `Basic` authorization header. + pub fn basic(username: &str, password: &str) -> Self { + let colon_pos = username.len(); + let decoded = format!("{}:{}", username, password); + + Authorization(Basic { decoded, colon_pos }) + } + + /// View the decoded username. + pub fn username(&self) -> &str { + self.0.username() + } + + /// View the decoded password. + pub fn password(&self) -> &str { + self.0.password() + } +} + +impl Authorization<Bearer> { + /// Try to create a `Bearer` authorization header. + pub fn bearer(token: &str) -> Result<Self, InvalidBearerToken> { + HeaderValueString::from_string(format!("Bearer {}", token)) + .map(|val| Authorization(Bearer(val))) + .ok_or_else(|| InvalidBearerToken { _inner: () }) + } + + /// View the token part as a `&str`. + pub fn token(&self) -> &str { + self.0.token() + } +} + +impl<C: Credentials> ::Header for Authorization<C> { + fn name() -> &'static ::HeaderName { + &::http::header::AUTHORIZATION + } + + fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + values + .next() + .and_then(|val| { + let slice = val.as_bytes(); + if slice.starts_with(C::SCHEME.as_bytes()) + && slice.len() > C::SCHEME.len() + && slice[C::SCHEME.len()] == b' ' + { + C::decode(val).map(Authorization) + } else { + None + } + }) + .ok_or_else(::Error::invalid) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + let value = self.0.encode(); + debug_assert!( + value.as_bytes().starts_with(C::SCHEME.as_bytes()), + "Credentials::encode should include its scheme: scheme = {:?}, encoded = {:?}", + C::SCHEME, + value, + ); + + values.extend(::std::iter::once(value)); + } +} + +/// Credentials to be used in the `Authorization` header. +pub trait Credentials: Sized { + /// The scheme identify the format of these credentials. + /// + /// This is the static string that always prefixes the actual credentials, + /// like `"Basic"` in basic authorization. + const SCHEME: &'static str; + + /// Try to decode the credentials from the `HeaderValue`. + /// + /// The `SCHEME` will be the first part of the `value`. + fn decode(value: &HeaderValue) -> Option<Self>; + + /// Encode the credentials to a `HeaderValue`. + /// + /// The `SCHEME` must be the first part of the `value`. + fn encode(&self) -> HeaderValue; +} + +/// Credential holder for Basic Authentication +#[derive(Clone, PartialEq, Debug)] +pub struct Basic { + decoded: String, + colon_pos: usize, +} + +impl Basic { + /// View the decoded username. + pub fn username(&self) -> &str { + &self.decoded[..self.colon_pos] + } + + /// View the decoded password. + pub fn password(&self) -> &str { + &self.decoded[self.colon_pos + 1..] + } +} + +impl Credentials for Basic { + const SCHEME: &'static str = "Basic"; + + fn decode(value: &HeaderValue) -> Option<Self> { + debug_assert!( + value.as_bytes().starts_with(b"Basic "), + "HeaderValue to decode should start with \"Basic ..\", received = {:?}", + value, + ); + + let bytes = &value.as_bytes()["Basic ".len()..]; + let non_space_pos = bytes.iter().position(|b| *b != b' ')?; + let bytes = &bytes[non_space_pos..]; + let bytes = base64::decode(bytes).ok()?; + + let decoded = String::from_utf8(bytes).ok()?; + + let colon_pos = decoded.find(':')?; + + Some(Basic { decoded, colon_pos }) + } + + fn encode(&self) -> HeaderValue { + let mut encoded = String::from("Basic "); + base64::encode_config_buf(&self.decoded, base64::STANDARD, &mut encoded); + + let bytes = Bytes::from(encoded); + HeaderValue::from_maybe_shared(bytes).expect("base64 encoding is always a valid HeaderValue") + } +} + +#[derive(Clone, PartialEq, Debug)] +/// Token holder for Bearer Authentication, most often seen with oauth +pub struct Bearer(HeaderValueString); + +impl Bearer { + /// View the token part as a `&str`. + pub fn token(&self) -> &str { + &self.0.as_str()["Bearer ".len()..] + } +} + +impl Credentials for Bearer { + const SCHEME: &'static str = "Bearer"; + + fn decode(value: &HeaderValue) -> Option<Self> { + debug_assert!( + value.as_bytes().starts_with(b"Bearer "), + "HeaderValue to decode should start with \"Bearer ..\", received = {:?}", + value, + ); + + HeaderValueString::from_val(value).ok().map(Bearer) + } + + fn encode(&self) -> HeaderValue { + (&self.0).into() + } +} + +error_type!(InvalidBearerToken); + +#[cfg(test)] +mod tests { + use super::super::{test_decode, test_encode}; + use super::{Authorization, Basic, Bearer}; + use http::header::HeaderMap; + use HeaderMapExt; + + #[test] + fn basic_encode() { + let auth = Authorization::basic("Aladdin", "open sesame"); + let headers = test_encode(auth); + + assert_eq!( + headers["authorization"], + "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", + ); + } + + #[test] + fn basic_roundtrip() { + let auth = Authorization::basic("Aladdin", "open sesame"); + let mut h = HeaderMap::new(); + h.typed_insert(auth.clone()); + assert_eq!(h.typed_get(), Some(auth)); + } + + #[test] + fn basic_encode_no_password() { + let auth = Authorization::basic("Aladdin", ""); + let headers = test_encode(auth); + + assert_eq!(headers["authorization"], "Basic QWxhZGRpbjo=",); + } + + #[test] + fn basic_decode() { + let auth: Authorization<Basic> = + test_decode(&["Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap(); + assert_eq!(auth.0.username(), "Aladdin"); + assert_eq!(auth.0.password(), "open sesame"); + } + + #[test] + fn basic_decode_no_password() { + let auth: Authorization<Basic> = test_decode(&["Basic QWxhZGRpbjo="]).unwrap(); + assert_eq!(auth.0.username(), "Aladdin"); + assert_eq!(auth.0.password(), ""); + } + + #[test] + fn bearer_encode() { + let auth = Authorization::bearer("fpKL54jvWmEGVoRdCNjG").unwrap(); + + let headers = test_encode(auth); + + assert_eq!(headers["authorization"], "Bearer fpKL54jvWmEGVoRdCNjG",); + } + + #[test] + fn bearer_decode() { + let auth: Authorization<Bearer> = test_decode(&["Bearer fpKL54jvWmEGVoRdCNjG"]).unwrap(); + assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG"); + } +} + +//bench_header!(raw, Authorization<String>, { vec![b"foo bar baz".to_vec()] }); +//bench_header!(basic, Authorization<Basic>, { vec![b"Basic QWxhZGRpbjpuIHNlc2FtZQ==".to_vec()] }); +//bench_header!(bearer, Authorization<Bearer>, { vec![b"Bearer fpKL54jvWmEGVoRdCNjG".to_vec()] }); |