actix_web_httpauth/headers/www_authenticate/challenge/bearer/
challenge.rs

1use std::{borrow::Cow, fmt, str};
2
3use actix_web::{
4    http::header::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue},
5    web::{BufMut, Bytes, BytesMut},
6};
7
8use super::{super::Challenge, BearerBuilder, Error};
9use crate::utils;
10
11/// Challenge for [`WWW-Authenticate`] header with HTTP Bearer auth scheme, described in [RFC 6750].
12///
13/// # Examples
14/// ```
15/// # use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
16/// use actix_web_httpauth::headers::www_authenticate::bearer::{
17///     Bearer, Error,
18/// };
19/// use actix_web_httpauth::headers::www_authenticate::WwwAuthenticate;
20///
21/// fn index(_req: HttpRequest) -> HttpResponse {
22///     let challenge = Bearer::build()
23///         .realm("example")
24///         .scope("openid profile email")
25///         .error(Error::InvalidToken)
26///         .error_description("The access token expired")
27///         .error_uri("http://example.org")
28///         .finish();
29///
30///     HttpResponse::Unauthorized()
31///         .insert_header(WwwAuthenticate(challenge))
32///         .finish()
33/// }
34/// ```
35///
36/// [`WWW-Authenticate`]: crate::headers::www_authenticate::WwwAuthenticate
37/// [RFC 6750]: https://tools.ietf.org/html/rfc6750#section-3
38#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub struct Bearer {
40    pub(crate) scope: Option<Cow<'static, str>>,
41    pub(crate) realm: Option<Cow<'static, str>>,
42    pub(crate) error: Option<Error>,
43    pub(crate) error_description: Option<Cow<'static, str>>,
44    pub(crate) error_uri: Option<Cow<'static, str>>,
45}
46
47impl Bearer {
48    /// Creates the builder for `Bearer` challenge.
49    ///
50    /// # Examples
51    /// ```
52    /// # use actix_web_httpauth::headers::www_authenticate::bearer::{Bearer};
53    /// let challenge = Bearer::build()
54    ///     .realm("Restricted area")
55    ///     .scope("openid profile email")
56    ///     .finish();
57    /// ```
58    pub fn build() -> BearerBuilder {
59        BearerBuilder::default()
60    }
61}
62
63#[doc(hidden)]
64impl Challenge for Bearer {
65    fn to_bytes(&self) -> Bytes {
66        let desc_uri_required = self
67            .error_description
68            .as_ref()
69            .map_or(0, |desc| desc.len() + 20)
70            + self.error_uri.as_ref().map_or(0, |url| url.len() + 12);
71
72        let capacity = 6
73            + self.realm.as_ref().map_or(0, |realm| realm.len() + 9)
74            + self.scope.as_ref().map_or(0, |scope| scope.len() + 9)
75            + desc_uri_required;
76
77        let mut buffer = BytesMut::with_capacity(capacity);
78        buffer.put(&b"Bearer"[..]);
79
80        if let Some(ref realm) = self.realm {
81            buffer.put(&b" realm=\""[..]);
82            utils::put_quoted(&mut buffer, realm);
83            buffer.put_u8(b'"');
84        }
85
86        if let Some(ref scope) = self.scope {
87            buffer.put(&b" scope=\""[..]);
88            utils::put_quoted(&mut buffer, scope);
89            buffer.put_u8(b'"');
90        }
91
92        if let Some(ref error) = self.error {
93            let error_repr = error.as_str();
94            let remaining = buffer.remaining_mut();
95            let required = desc_uri_required + error_repr.len() + 9; // 9 is for `" error=\"\""`
96
97            if remaining < required {
98                buffer.reserve(required);
99            }
100
101            buffer.put(&b" error=\""[..]);
102            utils::put_quoted(&mut buffer, error_repr);
103            buffer.put_u8(b'"')
104        }
105
106        if let Some(ref error_description) = self.error_description {
107            buffer.put(&b" error_description=\""[..]);
108            utils::put_quoted(&mut buffer, error_description);
109            buffer.put_u8(b'"');
110        }
111
112        if let Some(ref error_uri) = self.error_uri {
113            buffer.put(&b" error_uri=\""[..]);
114            utils::put_quoted(&mut buffer, error_uri);
115            buffer.put_u8(b'"');
116        }
117
118        buffer.freeze()
119    }
120}
121
122impl fmt::Display for Bearer {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
124        let bytes = self.to_bytes();
125
126        let repr = str::from_utf8(&bytes)
127            // Should not happen since challenges are crafted manually
128            // from `&'static str`'s and Strings
129            .map_err(|_| fmt::Error)?;
130
131        f.write_str(repr)
132    }
133}
134
135impl TryIntoHeaderValue for Bearer {
136    type Error = InvalidHeaderValue;
137
138    fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
139        HeaderValue::from_maybe_shared(self.to_bytes())
140    }
141}