Skip to main content

rama_http_headers/common/
authorization.rs

1//! Authorization header and types.
2
3use std::ops::{Deref, DerefMut};
4
5use base64::Engine;
6use base64::engine::general_purpose::STANDARD as ENGINE;
7
8use rama_core::extensions::Extensions;
9use rama_core::telemetry::tracing;
10use rama_core::username::{UsernameLabelParser, parse_username};
11use rama_http_types::{HeaderName, HeaderValue};
12use rama_net::user::{Basic, Bearer, RawToken, UserId};
13
14use crate::{Error, HeaderDecode, HeaderEncode, TypedHeader};
15
16/// `Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.2)
17///
18/// The `Authorization` header field allows a user agent to authenticate
19/// itself with an origin server -- usually, but not necessarily, after
20/// receiving a 401 (Unauthorized) response.  Its value consists of
21/// credentials containing the authentication information of the user
22/// agent for the realm of the resource being requested.
23///
24/// # ABNF
25///
26/// ```text
27/// Authorization = credentials
28/// ```
29///
30/// # Example values
31/// * `Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==`
32/// * `Bearer fpKL54jvWmEGVoRdCNjG`
33///
34/// # Examples
35///
36/// ```
37/// use rama_http_headers::Authorization;
38/// use rama_net::user::credentials::{basic, bearer};
39///
40/// let basic = Authorization::new(basic!("Aladdin", "open sesame"));
41/// let bearer = Authorization::new(bearer!("some-opaque-token"));
42/// ```
43///
44#[derive(Clone, PartialEq, Debug)]
45pub struct Authorization<C>(pub C);
46
47impl<C> Authorization<C> {
48    /// Create a new authorization header.
49    pub fn new(credentials: C) -> Self {
50        Self(credentials)
51    }
52
53    pub fn credentials(&self) -> &C {
54        &self.0
55    }
56
57    pub fn into_inner(self) -> C {
58        self.0
59    }
60}
61
62impl<C> AsRef<C> for Authorization<C> {
63    fn as_ref(&self) -> &C {
64        &self.0
65    }
66}
67
68impl<C> Deref for Authorization<C> {
69    type Target = C;
70
71    fn deref(&self) -> &Self::Target {
72        &self.0
73    }
74}
75
76impl<C> DerefMut for Authorization<C> {
77    fn deref_mut(&mut self) -> &mut Self::Target {
78        &mut self.0
79    }
80}
81
82impl<C: Credentials> TypedHeader for Authorization<C> {
83    fn name() -> &'static HeaderName {
84        &::rama_http_types::header::AUTHORIZATION
85    }
86}
87
88impl<C: Credentials> HeaderDecode for Authorization<C> {
89    fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
90        values
91            .next()
92            .and_then(|val| {
93                // Scheme-less credential types (e.g. `RawToken`) declare an
94                // empty `SCHEME` and treat the whole header value as the
95                // token. Skip the `<scheme> SP …` prefix check entirely so
96                // those types don't have to lie about their shape.
97                if C::SCHEME.is_empty() {
98                    return C::decode(val).map(Authorization);
99                }
100                let slice = val.as_bytes();
101                if slice.len() > C::SCHEME.len()
102                    && slice[C::SCHEME.len()] == b' '
103                    && slice[..C::SCHEME.len()].eq_ignore_ascii_case(C::SCHEME.as_bytes())
104                {
105                    C::decode(val).map(Authorization)
106                } else {
107                    None
108                }
109            })
110            .ok_or_else(Error::invalid)
111    }
112}
113
114impl<C: Credentials> HeaderEncode for Authorization<C> {
115    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
116        values.extend(self.0.encode().map(|mut value| {
117            value.set_sensitive(true);
118            debug_assert!(
119                // Scheme-less credentials (empty `SCHEME`) encode as a bare
120                // token with no prefix; `starts_with` on an empty slice
121                // is trivially true, so the assertion below covers them.
122                value.as_bytes().starts_with(C::SCHEME.as_bytes()),
123                "Credentials::encode should include its scheme: scheme = {:?}, encoded = {:?}",
124                C::SCHEME,
125                value,
126            );
127            value
128        }));
129    }
130}
131
132/// Credentials to be used in the `Authorization` header.
133pub trait Credentials: Sized {
134    /// The scheme identify the format of these credentials.
135    ///
136    /// This is the static string that always prefixes the actual credentials,
137    /// like `"Basic"` in basic authorization.
138    const SCHEME: &'static str;
139
140    /// Try to decode the credentials from the `HeaderValue`.
141    ///
142    /// The `SCHEME` will be the first part of the `value`.
143    fn decode(value: &HeaderValue) -> Option<Self>;
144
145    /// Encode the credentials to a `HeaderValue`.
146    ///
147    /// The `SCHEME` must be the first part of the `value`.
148    fn encode(&self) -> Option<HeaderValue>;
149}
150
151impl Credentials for Basic {
152    const SCHEME: &'static str = "Basic";
153
154    fn decode(value: &HeaderValue) -> Option<Self> {
155        let value = value.as_ref();
156
157        if value.len() <= Self::SCHEME.len() + 1 {
158            tracing::trace!(
159                "Basic credentials failed to decode: invalid scheme length in basic str"
160            );
161            return None;
162        }
163        if !value[..Self::SCHEME.len()].eq_ignore_ascii_case(Self::SCHEME.as_bytes()) {
164            tracing::trace!("Basic credentials failed to decode: invalid scheme in basic str");
165            return None;
166        }
167
168        let bytes = &value[Self::SCHEME.len() + 1..];
169        let Some(non_space_pos) = bytes.iter().position(|b| *b != b' ') else {
170            tracing::trace!(
171                "Basic credentials failed to decode: missing space separator in basic str"
172            );
173            return None;
174        };
175
176        let bytes = &bytes[non_space_pos..];
177
178        let bytes = ENGINE
179            .decode(bytes)
180            .inspect_err(|err| {
181                tracing::trace!("Basic credentials failed to decode: base64 decode: {err:?}");
182            })
183            .ok()?;
184
185        let decoded = String::from_utf8(bytes)
186            .inspect_err(|err| {
187                tracing::trace!("Basic credentials failed to decode: utf8 validation: {err:?}");
188            })
189            .ok()?;
190
191        decoded
192            .parse()
193            .inspect_err(|err| {
194                tracing::trace!("Basic credentials failed to decode: str parse: {err:?}");
195            })
196            .ok()
197    }
198
199    fn encode(&self) -> Option<HeaderValue> {
200        let mut encoded = format!("{} ", Self::SCHEME);
201        ENGINE.encode_string(self.to_string(), &mut encoded);
202        HeaderValue::try_from(encoded)
203            .inspect_err(|err| {
204                tracing::debug!("failed to encode basic value as header value: {err}");
205            })
206            .ok()
207    }
208}
209
210impl Credentials for Bearer {
211    const SCHEME: &'static str = "Bearer";
212
213    fn decode(value: &HeaderValue) -> Option<Self> {
214        let value = value.as_ref();
215
216        if value.len() <= Self::SCHEME.len() + 1 {
217            tracing::trace!("Bearer credentials failed to decode: invalid bearer scheme length");
218            return None;
219        }
220        if !value[..Self::SCHEME.len()].eq_ignore_ascii_case(Self::SCHEME.as_bytes()) {
221            tracing::trace!("Bearer credentials failed to decode: invalid bearer scheme");
222            return None;
223        }
224
225        let bytes = &value[Self::SCHEME.len() + 1..];
226
227        let Some(non_space_pos) = bytes.iter().position(|b| *b != b' ') else {
228            tracing::trace!("Bearer credentials failed to decode: no token found");
229            return None;
230        };
231
232        let bytes = &bytes[non_space_pos..];
233
234        let s = std::str::from_utf8(bytes)
235            .inspect_err(|err| {
236                tracing::trace!("Bearer credentials failed to decode: {err:?}");
237            })
238            .ok()?;
239
240        Self::try_from(s.to_owned())
241            .inspect_err(|err| {
242                tracing::trace!("Bearer credentials failed to decode: {err:?}");
243            })
244            .ok()
245    }
246
247    fn encode(&self) -> Option<HeaderValue> {
248        HeaderValue::try_from(format!("{} {}", Self::SCHEME, self.token()))
249            .inspect_err(|err| {
250                tracing::debug!("failed to encode bearer auth as header value: {err}");
251            })
252            .ok()
253    }
254}
255
256impl Credentials for RawToken {
257    /// Scheme-less: the entire `Authorization` header value is the token,
258    /// with no leading `Bearer ` / `Basic ` prefix.
259    const SCHEME: &'static str = "";
260
261    fn decode(value: &HeaderValue) -> Option<Self> {
262        let s = std::str::from_utf8(value.as_bytes())
263            .inspect_err(|err| {
264                tracing::trace!("RawToken credentials failed to decode: {err:?}");
265            })
266            .ok()?;
267        Self::try_from(s.to_owned())
268            .inspect_err(|err| {
269                tracing::trace!("RawToken credentials failed to decode: {err}");
270            })
271            .ok()
272    }
273
274    fn encode(&self) -> Option<HeaderValue> {
275        HeaderValue::try_from(self.token().to_owned())
276            .inspect_err(|err| {
277                tracing::debug!("failed to encode raw token as header value: {err}");
278            })
279            .ok()
280    }
281}
282
283/// The `Authority` trait is used to determine if a set of [`Credentials`] are authorized.
284pub trait Authority<C, L>: Send + Sync + 'static {
285    /// Returns `true` if the credentials are authorized, otherwise `false`.
286    fn authorized(&self, credentials: C) -> impl Future<Output = Option<Extensions>> + Send + '_;
287}
288
289/// A synchronous version of [`Authority`], to be used for primitive implementations.
290pub trait AuthoritySync<C, L>: Send + Sync + 'static {
291    /// Returns `true` if the credentials are authorized, otherwise `false`.
292    fn authorized(&self, ext: &Extensions, credentials: &C) -> bool;
293}
294
295impl<A, C, L> Authority<C, L> for A
296where
297    A: AuthoritySync<C, L>,
298    C: Credentials + Send + 'static,
299    L: 'static,
300{
301    async fn authorized(&self, credentials: C) -> Option<Extensions> {
302        let ext = Extensions::new();
303        if self.authorized(&ext, &credentials) {
304            Some(ext)
305        } else {
306            None
307        }
308    }
309}
310
311impl<T: UsernameLabelParser> AuthoritySync<Self, T> for Basic {
312    fn authorized(&self, ext: &Extensions, credentials: &Self) -> bool {
313        let username = credentials.username();
314        let password = credentials.password();
315
316        if password != self.password() {
317            return false;
318        }
319
320        let parser_ext = Extensions::new();
321        let username = match parse_username(&parser_ext, T::default(), username) {
322            Ok(t) => t,
323            Err(err) => {
324                tracing::trace!("failed to parse username: {:?}", err);
325                return if self == credentials {
326                    ext.insert(UserId::Username(username.to_owned()));
327                    true
328                } else {
329                    false
330                };
331            }
332        };
333
334        if username != self.username() {
335            return false;
336        }
337
338        ext.extend(&parser_ext);
339        ext.insert(UserId::Username(username));
340        true
341    }
342}
343
344impl<C, L, T, const N: usize> AuthoritySync<C, L> for [T; N]
345where
346    C: Credentials + Send + 'static,
347    T: AuthoritySync<C, L>,
348{
349    fn authorized(&self, ext: &Extensions, credentials: &C) -> bool {
350        self.iter().any(|t| t.authorized(ext, credentials))
351    }
352}
353
354impl<C, L, T> AuthoritySync<C, L> for Vec<T>
355where
356    C: Credentials + Send + 'static,
357    T: AuthoritySync<C, L>,
358{
359    fn authorized(&self, ext: &Extensions, credentials: &C) -> bool {
360        self.iter().any(|t| t.authorized(ext, credentials))
361    }
362}
363
364#[cfg(test)]
365mod tests {
366    use rama_http_types::header::HeaderMap;
367    use rama_net::user::credentials::bearer;
368    use rama_utils::str::non_empty_str;
369
370    use super::super::{test_decode, test_encode};
371    use super::{Authorization, Basic, Bearer};
372    use crate::HeaderMapExt;
373
374    #[test]
375    fn basic_encode() {
376        let auth = Authorization::new(Basic::new(
377            non_empty_str!("Aladdin"),
378            non_empty_str!("open sesame"),
379        ));
380        let headers = test_encode(auth);
381
382        assert_eq!(
383            headers["authorization"],
384            "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
385        );
386    }
387
388    #[test]
389    fn basic_username_encode() {
390        let auth = Authorization::new(Basic::new_insecure(non_empty_str!("Aladdin")));
391        let headers = test_encode(auth);
392
393        assert_eq!(headers["authorization"], "Basic QWxhZGRpbjo=",);
394    }
395
396    #[test]
397    fn basic_roundtrip() {
398        let auth = Authorization::new(Basic::new(
399            non_empty_str!("Aladdin"),
400            non_empty_str!("open sesame"),
401        ));
402        let mut h = HeaderMap::new();
403        h.typed_insert(&auth);
404        assert_eq!(h.typed_get(), Some(auth));
405    }
406
407    #[test]
408    fn basic_decode() {
409        let auth: Authorization<Basic> =
410            test_decode(&["Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap();
411        assert_eq!(auth.0.username(), "Aladdin");
412        assert_eq!(auth.0.password(), Some("open sesame"));
413    }
414
415    #[test]
416    fn basic_decode_case_insensitive() {
417        let auth: Authorization<Basic> =
418            test_decode(&["basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap();
419        assert_eq!(auth.0.username(), "Aladdin");
420        assert_eq!(auth.0.password(), Some("open sesame"));
421    }
422
423    #[test]
424    fn basic_decode_extra_whitespaces() {
425        let auth: Authorization<Basic> =
426            test_decode(&["Basic  QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap();
427        assert_eq!(auth.0.username(), "Aladdin");
428        assert_eq!(auth.0.password(), Some("open sesame"));
429    }
430
431    #[test]
432    fn basic_decode_no_password() {
433        let auth: Authorization<Basic> = test_decode(&["Basic QWxhZGRpbjo="]).unwrap();
434        assert_eq!(auth.0.username(), "Aladdin");
435        assert_eq!(auth.0.password(), None);
436    }
437
438    #[test]
439    fn bearer_encode() {
440        let auth = Authorization::new(bearer!("fpKL54jvWmEGVoRdCNjG"));
441
442        let headers = test_encode(auth);
443
444        assert_eq!(headers["authorization"], "Bearer fpKL54jvWmEGVoRdCNjG",);
445    }
446
447    #[test]
448    fn bearer_decode() {
449        let auth: Authorization<Bearer> = test_decode(&["Bearer fpKL54jvWmEGVoRdCNjG"]).unwrap();
450        assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG");
451    }
452
453    #[test]
454    fn bearer_decode_case_insensitive() {
455        let auth: Authorization<Bearer> = test_decode(&["bearer fpKL54jvWmEGVoRdCNjG"]).unwrap();
456        assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG");
457    }
458
459    #[test]
460    fn bearer_decode_extra_whitespaces() {
461        let auth: Authorization<Bearer> = test_decode(&["Bearer   fpKL54jvWmEGVoRdCNjG"]).unwrap();
462        assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG");
463    }
464
465    /// Regression: `RawToken` is a scheme-less credential — the header
466    /// value is the token, no `Bearer ` / `Basic ` prefix. This pins both
467    /// the encoded form (bare token) and the empty-scheme escape hatch in
468    /// `Authorization::decode`.
469    #[test]
470    fn regression_authorization_raw_token_roundtrip() {
471        use rama_net::user::RawToken;
472
473        let token = RawToken::try_from("fpKL54jvWmEGVoRdCNjG").unwrap();
474        let auth = Authorization::new(token.clone());
475
476        // Encode: header value is the bare token, no scheme prefix.
477        let headers = test_encode(auth);
478        assert_eq!(headers["authorization"], "fpKL54jvWmEGVoRdCNjG");
479
480        // Decode: the same bare value round-trips back.
481        let decoded: Authorization<RawToken> = test_decode(&["fpKL54jvWmEGVoRdCNjG"]).unwrap();
482        assert_eq!(decoded.0, token);
483    }
484
485    /// Regression: a `RawToken` header may contain characters that
486    /// `Bearer` rejects (`,`, `:`, `=`, SP) because real-world API keys
487    /// do.
488    #[test]
489    fn regression_authorization_raw_token_accepts_loose_alphabet() {
490        use rama_net::user::RawToken;
491
492        let decoded: Authorization<RawToken> =
493            test_decode(&["sk-live_abc=xyz,scope:read"]).unwrap();
494        assert_eq!(decoded.0.token(), "sk-live_abc=xyz,scope:read");
495    }
496}
497
498//bench_header!(raw, Authorization<String>, { vec![b"foo bar baz".to_vec()] });
499//bench_header!(basic, Authorization<Basic>, { vec![b"Basic QWxhZGRpbjpuIHNlc2FtZQ==".to_vec()] });
500//bench_header!(bearer, Authorization<Bearer>, { vec![b"Bearer fpKL54jvWmEGVoRdCNjG".to_vec()] });
501
502#[cfg(test)]
503mod test_auth {
504    use super::*;
505    use rama_core::username::{UsernameLabels, UsernameOpaqueLabelParser};
506    use rama_net::user::credentials::basic;
507
508    #[tokio::test]
509    async fn basic_authorization() {
510        let auth = basic!("Aladdin", "open sesame");
511        let auths = vec![basic!("foo", "bar"), auth.clone()];
512        let ext = Authority::<_, ()>::authorized(&auths, auth).await.unwrap();
513        let user: &UserId = ext.get_ref().unwrap();
514        assert_eq!(user, "Aladdin");
515    }
516
517    #[tokio::test]
518    async fn basic_authorization_with_labels_found() {
519        let auths = vec![basic!("foo", "bar"), basic!("john", "secret")];
520
521        let ext = Authority::<_, UsernameOpaqueLabelParser>::authorized(
522            &auths,
523            basic!("john-green-red", "secret"),
524        )
525        .await
526        .unwrap();
527
528        let c: &UserId = ext.get_ref().unwrap();
529        assert_eq!(c, "john");
530
531        let labels: &UsernameLabels = ext.get_ref().unwrap();
532        assert_eq!(&labels.0, &vec!["green".to_owned(), "red".to_owned()]);
533    }
534
535    #[tokio::test]
536    async fn basic_authorization_with_labels_not_found() {
537        let auth = basic!("john", "secret");
538        let auths = vec![basic!("foo", "bar"), auth.clone()];
539
540        let ext = Authority::<_, UsernameOpaqueLabelParser>::authorized(&auths, auth)
541            .await
542            .unwrap();
543
544        let c: &UserId = ext.get_ref().unwrap();
545        assert_eq!(c, "john");
546
547        assert!(ext.get_ref::<UsernameLabels>().is_none());
548    }
549}