rama_http_headers/common/
authorization.rs1use 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#[derive(Clone, PartialEq, Debug)]
45pub struct Authorization<C>(pub C);
46
47impl<C> Authorization<C> {
48 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 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 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
132pub trait Credentials: Sized {
134 const SCHEME: &'static str;
139
140 fn decode(value: &HeaderValue) -> Option<Self>;
144
145 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 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
283pub trait Authority<C, L>: Send + Sync + 'static {
285 fn authorized(&self, credentials: C) -> impl Future<Output = Option<Extensions>> + Send + '_;
287}
288
289pub trait AuthoritySync<C, L>: Send + Sync + 'static {
291 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 #[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 let headers = test_encode(auth);
478 assert_eq!(headers["authorization"], "fpKL54jvWmEGVoRdCNjG");
479
480 let decoded: Authorization<RawToken> = test_decode(&["fpKL54jvWmEGVoRdCNjG"]).unwrap();
482 assert_eq!(decoded.0, token);
483 }
484
485 #[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#[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}