actix_web_httpauth/headers/www_authenticate/challenge/
basic.rs

1//! Challenge for the "Basic" HTTP Authentication Scheme.
2
3use std::{borrow::Cow, fmt, str};
4
5use actix_web::{
6    http::header::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue},
7    web::{BufMut, Bytes, BytesMut},
8};
9
10use super::Challenge;
11use crate::utils;
12
13/// Challenge for [`WWW-Authenticate`] header with HTTP Basic auth scheme,
14/// described in [RFC 7617](https://tools.ietf.org/html/rfc7617)
15///
16/// # Examples
17/// ```
18/// # use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
19/// use actix_web_httpauth::headers::www_authenticate::basic::Basic;
20/// use actix_web_httpauth::headers::www_authenticate::WwwAuthenticate;
21///
22/// fn index(_req: HttpRequest) -> HttpResponse {
23///     let challenge = Basic::with_realm("Restricted area");
24///
25///     HttpResponse::Unauthorized()
26///         .insert_header(WwwAuthenticate(challenge))
27///         .finish()
28/// }
29/// ```
30///
31/// [`WWW-Authenticate`]: ../struct.WwwAuthenticate.html
32#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Clone)]
33pub struct Basic {
34    // "realm" parameter is optional now: https://tools.ietf.org/html/rfc7235#appendix-A
35    pub(crate) realm: Option<Cow<'static, str>>,
36}
37
38impl Basic {
39    /// Creates new `Basic` challenge with an empty `realm` field.
40    ///
41    /// # Examples
42    /// ```
43    /// # use actix_web_httpauth::headers::www_authenticate::basic::Basic;
44    /// let challenge = Basic::new();
45    /// ```
46    pub fn new() -> Basic {
47        Default::default()
48    }
49
50    /// Creates new `Basic` challenge from the provided `realm` field value.
51    ///
52    /// # Examples
53    ///
54    /// ```
55    /// # use actix_web_httpauth::headers::www_authenticate::basic::Basic;
56    /// let challenge = Basic::with_realm("Restricted area");
57    /// ```
58    ///
59    /// ```
60    /// # use actix_web_httpauth::headers::www_authenticate::basic::Basic;
61    /// let my_realm = "Earth realm".to_string();
62    /// let challenge = Basic::with_realm(my_realm);
63    /// ```
64    pub fn with_realm<T>(value: T) -> Basic
65    where
66        T: Into<Cow<'static, str>>,
67    {
68        Basic {
69            realm: Some(value.into()),
70        }
71    }
72}
73
74#[doc(hidden)]
75impl Challenge for Basic {
76    fn to_bytes(&self) -> Bytes {
77        // 5 is for `"Basic"`, 9 is for `"realm=\"\""`
78        let length = 5 + self.realm.as_ref().map_or(0, |realm| realm.len() + 9);
79        let mut buffer = BytesMut::with_capacity(length);
80        buffer.put(&b"Basic"[..]);
81        if let Some(ref realm) = self.realm {
82            buffer.put(&b" realm=\""[..]);
83            utils::put_quoted(&mut buffer, realm);
84            buffer.put_u8(b'"');
85        }
86
87        buffer.freeze()
88    }
89}
90
91impl fmt::Display for Basic {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
93        let bytes = self.to_bytes();
94        let repr = str::from_utf8(&bytes)
95            // Should not happen since challenges are crafted manually
96            // from a `&'static str` or `String`
97            .map_err(|_| fmt::Error)?;
98
99        f.write_str(repr)
100    }
101}
102
103impl TryIntoHeaderValue for Basic {
104    type Error = InvalidHeaderValue;
105
106    fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
107        HeaderValue::from_maybe_shared(self.to_bytes())
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_plain_into_header_value() {
117        let challenge = Basic { realm: None };
118
119        let value = challenge.try_into_value();
120        assert!(value.is_ok());
121        let value = value.unwrap();
122        assert_eq!(value, "Basic");
123    }
124
125    #[test]
126    fn test_with_realm_into_header_value() {
127        let challenge = Basic {
128            realm: Some("Restricted area".into()),
129        };
130
131        let value = challenge.try_into_value();
132        assert!(value.is_ok());
133        let value = value.unwrap();
134        assert_eq!(value, "Basic realm=\"Restricted area\"");
135    }
136}