cloud_sdk_reqwest/shared/
auth.rs1use core::fmt;
2
3use cloud_sdk_sanitization::{SecretBuffer, sanitize_bytes};
4use reqwest::header::HeaderValue;
5#[cfg(test)]
6use std::sync::{
7 Arc,
8 atomic::{AtomicUsize, Ordering},
9};
10use std::vec::Vec;
11
12use super::sensitive_header_value;
13#[cfg(test)]
14use super::sensitive_header_value_with_probe;
15pub const MAX_BEARER_TOKEN_BYTES: usize = 4096;
17
18const BEARER_PREFIX: &[u8] = b"Bearer ";
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
22pub enum BearerTokenError {
23 Empty,
25 TooLong,
27 InvalidByte,
30 AllocationFailed,
32}
33
34impl_static_error!(BearerTokenError,
35 Self::Empty => "bearer token is empty",
36 Self::TooLong => "bearer token exceeds the length limit",
37 Self::InvalidByte => "bearer token contains an invalid byte",
38 Self::AllocationFailed => "bearer-token allocation failed",
39);
40
41pub struct BearerToken {
44 authorization: Vec<u8>,
45 #[cfg(test)]
46 drop_probe: Option<Arc<AtomicUsize>>,
47 #[cfg(test)]
48 header_drop_probe: Option<Arc<AtomicUsize>>,
49}
50
51impl BearerToken {
52 pub fn new(token: &str) -> Result<Self, BearerTokenError> {
54 Self::from_bytes(token.as_bytes())
55 }
56
57 pub fn from_mut_bytes(token: &mut [u8]) -> Result<Self, BearerTokenError> {
60 let result = Self::from_bytes(token);
61 sanitize_bytes(token);
62 result
63 }
64
65 pub fn from_secret_buffer(token: SecretBuffer<'_>) -> Result<Self, BearerTokenError> {
68 Self::from_bytes(token.as_slice())
69 }
70
71 fn from_bytes(token: &[u8]) -> Result<Self, BearerTokenError> {
72 if token.is_empty() {
73 return Err(BearerTokenError::Empty);
74 }
75 if token.len() > MAX_BEARER_TOKEN_BYTES {
76 return Err(BearerTokenError::TooLong);
77 }
78 let mut saw_token_byte = false;
79 let mut padding = false;
80 for byte in token.iter().copied() {
81 match byte {
82 b'=' if saw_token_byte => padding = true,
83 b'=' => return Err(BearerTokenError::InvalidByte),
84 _ if padding || !is_bearer_byte(byte) => {
85 return Err(BearerTokenError::InvalidByte);
86 }
87 _ => saw_token_byte = true,
88 }
89 }
90
91 let capacity = BEARER_PREFIX
92 .len()
93 .checked_add(token.len())
94 .ok_or(BearerTokenError::TooLong)?;
95 let mut authorization = Vec::new();
96 authorization
97 .try_reserve_exact(capacity)
98 .map_err(|_| BearerTokenError::AllocationFailed)?;
99 authorization.extend_from_slice(BEARER_PREFIX);
100 authorization.extend_from_slice(token);
101 Ok(Self {
102 authorization,
103 #[cfg(test)]
104 drop_probe: None,
105 #[cfg(test)]
106 header_drop_probe: None,
107 })
108 }
109
110 pub(crate) fn header_value(&self) -> Result<HeaderValue, ()> {
111 sensitive_header_value(&self.authorization)
112 }
113
114 #[cfg(test)]
115 pub(crate) fn header_value_with_drop_probe(&self) -> Result<HeaderValue, ()> {
116 sensitive_header_value_with_probe(&self.authorization, self.header_drop_probe.clone())
117 }
118
119 #[cfg(test)]
120 pub(crate) fn owned_bytes(&self) -> &[u8] {
121 &self.authorization
122 }
123
124 #[cfg(test)]
125 pub(crate) fn with_drop_probe(
126 token: &str,
127 probe: Arc<AtomicUsize>,
128 ) -> Result<Self, BearerTokenError> {
129 let mut value = Self::new(token)?;
130 value.drop_probe = Some(probe);
131 Ok(value)
132 }
133
134 #[cfg(test)]
135 pub(crate) fn with_header_drop_probe(
136 token: &str,
137 probe: Arc<AtomicUsize>,
138 ) -> Result<Self, BearerTokenError> {
139 let mut value = Self::new(token)?;
140 value.header_drop_probe = Some(probe);
141 Ok(value)
142 }
143}
144
145impl Drop for BearerToken {
146 fn drop(&mut self) {
147 sanitize_bytes(&mut self.authorization);
148 #[cfg(test)]
149 if let Some(probe) = &self.drop_probe {
150 probe.fetch_add(1, Ordering::SeqCst);
151 }
152 }
153}
154
155impl fmt::Debug for BearerToken {
156 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
157 formatter.write_str("BearerToken([redacted])")
158 }
159}
160
161const fn is_bearer_byte(byte: u8) -> bool {
162 byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/')
163}