Skip to main content

cloud_sdk_reqwest/blocking/
client.rs

1use core::fmt;
2use std::sync::Arc;
3
4use cloud_sdk::authentication::{
5    AuthenticatedRequest, BlockingAuthenticatedTransport, CredentialGeneration,
6};
7use cloud_sdk::transport::{
8    BoundTransport, EndpointIdentity, EndpointIdentityError, ResponseStorageSanitizer,
9    ResponseWriter, TransportFailure,
10};
11use cloud_sdk_sanitization::{SecretBuffer, sanitize_bytes};
12
13use super::RawBlockingClient;
14use crate::shared::{
15    AuthenticatedTransportFailure, BearerCredential, BearerCredentialScope,
16    BearerCredentialSnapshot, BearerRefreshHandoff, BearerToken, CredentialStateError,
17    CredentialStore, CredentialUpdateError, HttpsEndpoint, TokenRefreshError, TokenRotationError,
18    TransportError, map_authentication_error, validate_bearer_authentication,
19};
20
21/// Hardened provider-neutral reqwest blocking bearer transport.
22#[derive(Clone)]
23pub struct BlockingClient {
24    client: RawBlockingClient,
25    endpoint: HttpsEndpoint,
26    scope: Arc<BearerCredentialScope>,
27    credentials: Arc<CredentialStore>,
28    allow_insecure_loopback: bool,
29}
30
31impl BlockingClient {
32    pub(super) fn new(
33        client: RawBlockingClient,
34        endpoint: HttpsEndpoint,
35        credential: BearerCredential,
36        allow_insecure_loopback: bool,
37    ) -> Self {
38        Self {
39            client,
40            endpoint,
41            scope: Arc::new(credential.scope),
42            credentials: Arc::new(CredentialStore::new(credential.token)),
43            allow_insecure_loopback,
44        }
45    }
46
47    /// Captures the current generation without exposing token bytes.
48    pub fn credential_snapshot(&self) -> Result<BearerCredentialSnapshot, CredentialStateError> {
49        self.credentials.snapshot()
50    }
51
52    /// Atomically replaces the bearer token used by newly started requests.
53    ///
54    /// In-flight requests retain their previous snapshot. The immutable scope
55    /// cannot change during rotation.
56    pub fn rotate_bearer_token(
57        &self,
58        replacement: BearerToken,
59    ) -> Result<CredentialGeneration, CredentialUpdateError> {
60        self.credentials.rotate(replacement)
61    }
62
63    /// Validates and rotates mutable bytes, clearing the complete source.
64    pub fn rotate_bearer_token_from_mut_bytes(
65        &self,
66        source: &mut [u8],
67    ) -> Result<CredentialGeneration, TokenRotationError> {
68        self.credentials.rotate_from_mut_bytes(source)
69    }
70
71    /// Validates and rotates guarded storage, which clears on return.
72    pub fn rotate_bearer_token_from_secret_buffer(
73        &self,
74        source: SecretBuffer<'_>,
75    ) -> Result<CredentialGeneration, TokenRotationError> {
76        self.credentials.rotate_from_secret_buffer(source)
77    }
78
79    /// Installs a refresh only if its captured generation is still current.
80    pub fn refresh_bearer_token(
81        &self,
82        handoff: BearerRefreshHandoff,
83        replacement: BearerToken,
84    ) -> Result<CredentialGeneration, TokenRefreshError> {
85        self.credentials.refresh(handoff, replacement)
86    }
87
88    /// Validates refreshed mutable bytes, clears them, and rejects stale work.
89    pub fn refresh_bearer_token_from_mut_bytes(
90        &self,
91        handoff: BearerRefreshHandoff,
92        source: &mut [u8],
93    ) -> Result<CredentialGeneration, TokenRefreshError> {
94        self.credentials.refresh_from_mut_bytes(handoff, source)
95    }
96
97    /// Consumes guarded refreshed storage and rejects stale work.
98    pub fn refresh_bearer_token_from_secret_buffer(
99        &self,
100        handoff: BearerRefreshHandoff,
101        source: SecretBuffer<'_>,
102    ) -> Result<CredentialGeneration, TokenRefreshError> {
103        self.credentials.refresh_from_secret_buffer(handoff, source)
104    }
105
106    fn send_inner(
107        &self,
108        authenticated: AuthenticatedRequest<'_, '_>,
109        response_writer: &mut ResponseWriter<'_>,
110    ) -> Result<(), AuthenticatedTransportFailure> {
111        let endpoint_identity = self.endpoint.identity().map_err(|_| {
112            TransportFailure::not_sent(TransportError::AuthenticationEndpointMismatch)
113        })?;
114        validate_bearer_authentication(
115            endpoint_identity,
116            &self.scope,
117            authenticated.policy(),
118            self.allow_insecure_loopback,
119        )
120        .map_err(|error| TransportFailure::not_sent(map_authentication_error(error)))?;
121        let token_snapshot = self
122            .credentials
123            .snapshot()
124            .map_err(|_| TransportFailure::not_sent(TransportError::CredentialStateUnavailable))?;
125        let authorization = token_snapshot
126            .header_value()
127            .map_err(|_| TransportFailure::not_sent(TransportError::HeaderRejected))?;
128        drop(token_snapshot);
129        self.client
130            .execute_authenticated(
131                authenticated.transport_request(),
132                authenticated.response_policy(),
133                authorization,
134                response_writer,
135            )
136            .map_err(|failure| failure.map(TransportError::RawHttp))
137    }
138}
139
140impl BlockingAuthenticatedTransport for BlockingClient {
141    type Error = AuthenticatedTransportFailure;
142
143    fn send_authenticated(
144        &self,
145        request: AuthenticatedRequest<'_, '_>,
146        response: &mut ResponseWriter<'_>,
147    ) -> Result<(), Self::Error> {
148        self.send_inner(request, response)
149    }
150}
151
152impl ResponseStorageSanitizer for BlockingClient {
153    fn sanitize_response_storage(&self, response_storage: &mut [u8]) {
154        sanitize_bytes(response_storage);
155    }
156}
157
158impl BoundTransport for BlockingClient {
159    fn endpoint_identity(&self) -> Result<EndpointIdentity<'_>, EndpointIdentityError> {
160        self.endpoint.identity()
161    }
162}
163
164impl fmt::Debug for BlockingClient {
165    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
166        formatter
167            .debug_struct("BlockingClient")
168            .field("endpoint", &"[redacted]")
169            .field("scope", &"[redacted]")
170            .field("credentials", &"[redacted]")
171            .finish_non_exhaustive()
172    }
173}