cloud_sdk_reqwest/shared/
credentials.rs1use core::fmt;
2use std::sync::{Arc, RwLock};
3
4use cloud_sdk::authentication::{
5 CredentialGeneration, CredentialGenerationError, CredentialLifetime, CredentialLifetimeState,
6 CredentialTimestamp, RefreshHandoff,
7};
8use cloud_sdk_sanitization::SecretBuffer;
9
10use super::BearerToken;
11
12mod error;
13
14pub use error::{
15 CredentialStateError, CredentialUpdateError, RefreshHandoffError, TokenRefreshError,
16 TokenRotationError,
17};
18
19struct VersionedToken {
20 generation: CredentialGeneration,
21 token: BearerToken,
22 lifetime: Option<CredentialLifetime>,
23}
24
25struct CredentialLineage;
26
27#[derive(Clone)]
32pub struct BearerRefreshHandoff {
33 lineage: Arc<CredentialLineage>,
34 expected: RefreshHandoff,
35}
36
37impl BearerRefreshHandoff {
38 #[must_use]
40 pub fn expected_generation(&self) -> CredentialGeneration {
41 self.expected.expected_generation()
42 }
43}
44
45impl fmt::Debug for BearerRefreshHandoff {
46 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
47 formatter
48 .debug_struct("BearerRefreshHandoff")
49 .field("generation", &self.expected_generation())
50 .field("lineage", &"[redacted]")
51 .finish()
52 }
53}
54
55pub struct BearerCredentialSnapshot {
60 lineage: Arc<CredentialLineage>,
61 current: Arc<VersionedToken>,
62}
63
64impl BearerCredentialSnapshot {
65 #[must_use]
67 pub fn generation(&self) -> CredentialGeneration {
68 self.current.generation
69 }
70
71 pub fn refresh_handoff(&self) -> Result<BearerRefreshHandoff, RefreshHandoffError> {
73 if self.current.lifetime.is_some() {
74 return Err(RefreshHandoffError::ExplicitTimeRequired);
75 }
76 Ok(self.new_handoff())
77 }
78
79 pub fn refresh_handoff_at(
81 &self,
82 now: CredentialTimestamp,
83 ) -> Result<BearerRefreshHandoff, RefreshHandoffError> {
84 let lifetime = self
85 .current
86 .lifetime
87 .ok_or(RefreshHandoffError::LifetimeNotConfigured)?;
88 match lifetime.state_at(now) {
89 CredentialLifetimeState::ClockRollback => Err(RefreshHandoffError::ClockRollback),
90 CredentialLifetimeState::Fresh => Err(RefreshHandoffError::RefreshNotRequired),
91 CredentialLifetimeState::RefreshRequired => Ok(self.new_handoff()),
92 CredentialLifetimeState::Expired => Err(RefreshHandoffError::CredentialExpired),
93 }
94 }
95
96 #[must_use]
98 pub fn lifetime(&self) -> Option<CredentialLifetime> {
99 self.current.lifetime
100 }
101
102 fn new_handoff(&self) -> BearerRefreshHandoff {
103 BearerRefreshHandoff {
104 lineage: Arc::clone(&self.lineage),
105 expected: self.generation().refresh_handoff(),
106 }
107 }
108
109 pub(crate) fn header_value(&self) -> Result<reqwest::header::HeaderValue, ()> {
110 self.current.token.header_value()
111 }
112
113 #[cfg(test)]
114 pub(crate) fn header_value_with_drop_probe(&self) -> Result<reqwest::header::HeaderValue, ()> {
115 self.current.token.header_value_with_drop_probe()
116 }
117
118 #[cfg(test)]
119 pub(crate) fn owned_bytes(&self) -> &[u8] {
120 self.current.token.owned_bytes()
121 }
122}
123
124impl Clone for BearerCredentialSnapshot {
125 fn clone(&self) -> Self {
126 Self {
127 lineage: Arc::clone(&self.lineage),
128 current: Arc::clone(&self.current),
129 }
130 }
131}
132
133impl fmt::Debug for BearerCredentialSnapshot {
134 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
135 formatter
136 .debug_struct("BearerCredentialSnapshot")
137 .field("generation", &self.generation())
138 .field("credential", &"[redacted]")
139 .finish()
140 }
141}
142
143pub(crate) struct CredentialStore {
144 lineage: Arc<CredentialLineage>,
145 current: RwLock<Arc<VersionedToken>>,
146}
147
148impl CredentialStore {
149 pub(crate) fn new(token: BearerToken, lifetime: Option<CredentialLifetime>) -> Self {
150 Self {
151 lineage: Arc::new(CredentialLineage),
152 current: RwLock::new(Arc::new(VersionedToken {
153 generation: CredentialGeneration::INITIAL,
154 token,
155 lifetime,
156 })),
157 }
158 }
159
160 pub(crate) fn snapshot(&self) -> Result<BearerCredentialSnapshot, CredentialStateError> {
161 let current = match self.current.read() {
162 Ok(current) => current,
163 Err(poisoned) => {
164 self.current.clear_poison();
165 poisoned.into_inner()
166 }
167 };
168 Ok(BearerCredentialSnapshot {
169 lineage: Arc::clone(&self.lineage),
170 current: Arc::clone(¤t),
171 })
172 }
173
174 pub(crate) fn rotate(
175 &self,
176 token: BearerToken,
177 ) -> Result<CredentialGeneration, CredentialUpdateError> {
178 let retired = {
179 let mut current = self.write_current();
180 replace_current(&mut current, token, None).map_err(map_update_failure)?
181 };
182 let (retired, generation) = retired;
183 drop(retired);
184 Ok(generation)
185 }
186
187 pub(crate) fn rotate_with_lifetime(
188 &self,
189 token: BearerToken,
190 lifetime: CredentialLifetime,
191 ) -> Result<CredentialGeneration, CredentialUpdateError> {
192 let retired = {
193 let mut current = self.write_current();
194 replace_current(&mut current, token, Some(lifetime)).map_err(map_update_failure)?
195 };
196 let (retired, generation) = retired;
197 drop(retired);
198 Ok(generation)
199 }
200
201 pub(crate) fn refresh(
202 &self,
203 handoff: BearerRefreshHandoff,
204 token: BearerToken,
205 ) -> Result<CredentialGeneration, TokenRefreshError> {
206 if !Arc::ptr_eq(&self.lineage, &handoff.lineage) {
207 return Err(TokenRefreshError::CredentialMismatch);
208 }
209 let retired = {
210 let mut current = self.write_current();
211 if handoff.expected_generation() != current.generation {
212 return Err(TokenRefreshError::StaleGeneration);
213 }
214 replace_current(&mut current, token, None).map_err(map_refresh_update)?
215 };
216 let (retired, generation) = retired;
217 drop(retired);
218 Ok(generation)
219 }
220
221 pub(crate) fn refresh_with_lifetime(
222 &self,
223 handoff: BearerRefreshHandoff,
224 token: BearerToken,
225 lifetime: CredentialLifetime,
226 ) -> Result<CredentialGeneration, TokenRefreshError> {
227 if !Arc::ptr_eq(&self.lineage, &handoff.lineage) {
228 return Err(TokenRefreshError::CredentialMismatch);
229 }
230 let retired = {
231 let mut current = self.write_current();
232 if handoff.expected_generation() != current.generation {
233 return Err(TokenRefreshError::StaleGeneration);
234 }
235 replace_current(&mut current, token, Some(lifetime)).map_err(map_refresh_update)?
236 };
237 let (retired, generation) = retired;
238 drop(retired);
239 Ok(generation)
240 }
241
242 fn write_current(&self) -> std::sync::RwLockWriteGuard<'_, Arc<VersionedToken>> {
243 match self.current.write() {
244 Ok(current) => current,
245 Err(poisoned) => {
246 self.current.clear_poison();
247 poisoned.into_inner()
248 }
249 }
250 }
251
252 pub(crate) fn rotate_from_mut_bytes(
253 &self,
254 source: &mut [u8],
255 ) -> Result<CredentialGeneration, TokenRotationError> {
256 let token =
257 BearerToken::from_mut_bytes(source).map_err(TokenRotationError::TokenRejected)?;
258 self.rotate(token).map_err(map_rotation_update)
259 }
260
261 pub(crate) fn rotate_from_secret_buffer(
262 &self,
263 source: SecretBuffer<'_>,
264 ) -> Result<CredentialGeneration, TokenRotationError> {
265 let token =
266 BearerToken::from_secret_buffer(source).map_err(TokenRotationError::TokenRejected)?;
267 self.rotate(token).map_err(map_rotation_update)
268 }
269
270 pub(crate) fn rotate_from_mut_bytes_with_lifetime(
271 &self,
272 source: &mut [u8],
273 lifetime: CredentialLifetime,
274 ) -> Result<CredentialGeneration, TokenRotationError> {
275 let token =
276 BearerToken::from_mut_bytes(source).map_err(TokenRotationError::TokenRejected)?;
277 self.rotate_with_lifetime(token, lifetime)
278 .map_err(map_rotation_update)
279 }
280
281 pub(crate) fn rotate_from_secret_buffer_with_lifetime(
282 &self,
283 source: SecretBuffer<'_>,
284 lifetime: CredentialLifetime,
285 ) -> Result<CredentialGeneration, TokenRotationError> {
286 let token =
287 BearerToken::from_secret_buffer(source).map_err(TokenRotationError::TokenRejected)?;
288 self.rotate_with_lifetime(token, lifetime)
289 .map_err(map_rotation_update)
290 }
291
292 pub(crate) fn refresh_from_mut_bytes(
293 &self,
294 handoff: BearerRefreshHandoff,
295 source: &mut [u8],
296 ) -> Result<CredentialGeneration, TokenRefreshError> {
297 let token =
298 BearerToken::from_mut_bytes(source).map_err(TokenRefreshError::TokenRejected)?;
299 self.refresh(handoff, token)
300 }
301
302 pub(crate) fn refresh_from_secret_buffer(
303 &self,
304 handoff: BearerRefreshHandoff,
305 source: SecretBuffer<'_>,
306 ) -> Result<CredentialGeneration, TokenRefreshError> {
307 let token =
308 BearerToken::from_secret_buffer(source).map_err(TokenRefreshError::TokenRejected)?;
309 self.refresh(handoff, token)
310 }
311
312 pub(crate) fn refresh_from_mut_bytes_with_lifetime(
313 &self,
314 handoff: BearerRefreshHandoff,
315 source: &mut [u8],
316 lifetime: CredentialLifetime,
317 ) -> Result<CredentialGeneration, TokenRefreshError> {
318 let token =
319 BearerToken::from_mut_bytes(source).map_err(TokenRefreshError::TokenRejected)?;
320 self.refresh_with_lifetime(handoff, token, lifetime)
321 }
322
323 pub(crate) fn refresh_from_secret_buffer_with_lifetime(
324 &self,
325 handoff: BearerRefreshHandoff,
326 source: SecretBuffer<'_>,
327 lifetime: CredentialLifetime,
328 ) -> Result<CredentialGeneration, TokenRefreshError> {
329 let token =
330 BearerToken::from_secret_buffer(source).map_err(TokenRefreshError::TokenRejected)?;
331 self.refresh_with_lifetime(handoff, token, lifetime)
332 }
333}
334
335fn map_rotation_update(error: CredentialUpdateError) -> TokenRotationError {
336 match error {
337 CredentialUpdateError::StateUnavailable => TokenRotationError::StateUnavailable,
338 CredentialUpdateError::GenerationExhausted => TokenRotationError::GenerationExhausted,
339 CredentialUpdateError::LifetimeRequired => TokenRotationError::LifetimeRequired,
340 CredentialUpdateError::LifetimeForbidden => TokenRotationError::LifetimeForbidden,
341 }
342}
343
344impl fmt::Debug for CredentialStore {
345 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
346 formatter.write_str("CredentialStore([redacted])")
347 }
348}
349
350#[derive(Clone, Copy, Debug, Eq, PartialEq)]
351enum CredentialUpdateFailure {
352 GenerationExhausted,
353 LifetimeRequired,
354 LifetimeForbidden,
355}
356
357fn map_update_failure(error: CredentialUpdateFailure) -> CredentialUpdateError {
358 match error {
359 CredentialUpdateFailure::GenerationExhausted => CredentialUpdateError::GenerationExhausted,
360 CredentialUpdateFailure::LifetimeRequired => CredentialUpdateError::LifetimeRequired,
361 CredentialUpdateFailure::LifetimeForbidden => CredentialUpdateError::LifetimeForbidden,
362 }
363}
364
365fn map_refresh_update(error: CredentialUpdateFailure) -> TokenRefreshError {
366 match error {
367 CredentialUpdateFailure::GenerationExhausted => TokenRefreshError::GenerationExhausted,
368 CredentialUpdateFailure::LifetimeRequired => TokenRefreshError::LifetimeRequired,
369 CredentialUpdateFailure::LifetimeForbidden => TokenRefreshError::LifetimeForbidden,
370 }
371}
372
373fn replace_current(
374 current: &mut Arc<VersionedToken>,
375 token: BearerToken,
376 lifetime: Option<CredentialLifetime>,
377) -> Result<(Arc<VersionedToken>, CredentialGeneration), CredentialUpdateFailure> {
378 match (current.lifetime, lifetime) {
379 (Some(_), None) => return Err(CredentialUpdateFailure::LifetimeRequired),
380 (None, Some(_)) => return Err(CredentialUpdateFailure::LifetimeForbidden),
381 (Some(_), Some(_)) | (None, None) => {}
382 }
383 let generation =
384 current
385 .generation
386 .checked_next()
387 .map_err(|_error: CredentialGenerationError| {
388 CredentialUpdateFailure::GenerationExhausted
389 })?;
390 let replacement = Arc::new(VersionedToken {
391 generation,
392 token,
393 lifetime,
394 });
395 Ok((core::mem::replace(current, replacement), generation))
396}
397
398#[cfg(test)]
399mod tests;