Skip to main content

gcloud_sdk/
reqwest_auth_middleware.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use reqwest_middleware::{Middleware, Next};
5
6use crate::middleware::replace_headers;
7use crate::GoogleAuthHeaders;
8
9/// A `reqwest-middleware` middleware that sets the authentication headers of a
10/// [`GoogleAuthHeaders`] on every request: `authorization` with an access token or
11/// an ID token, and any other header the credentials add, such as `x-goog-user-project`.
12#[derive(Clone)]
13pub struct GoogleAuthReqwestMiddleware {
14    auth_headers: Arc<GoogleAuthHeaders>,
15}
16
17impl GoogleAuthReqwestMiddleware {
18    pub fn new(auth_headers: impl Into<Arc<GoogleAuthHeaders>>) -> Self {
19        Self {
20            auth_headers: auth_headers.into(),
21        }
22    }
23}
24
25#[async_trait]
26impl Middleware for GoogleAuthReqwestMiddleware {
27    async fn handle(
28        &self,
29        mut req: reqwest::Request,
30        extensions: &mut hyper::http::Extensions,
31        next: Next<'_>,
32    ) -> reqwest_middleware::Result<reqwest::Response> {
33        let auth_headers = self
34            .auth_headers
35            .headers()
36            .await
37            .map_err(reqwest_middleware::Error::middleware)?;
38        replace_headers(req.headers_mut(), &auth_headers);
39        next.run(req, extensions).await
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::test_support::StubCredentials;
47    use google_cloud_auth::credentials::Credentials;
48    use reqwest::header::{HeaderMap, HeaderValue};
49    use std::sync::Mutex;
50
51    /// Ends the chain with an empty `200 OK` instead of sending the request, keeping the
52    /// headers it received.
53    #[derive(Clone, Default)]
54    struct CaptureHeaders {
55        received: Arc<Mutex<Vec<HeaderMap>>>,
56    }
57
58    #[async_trait]
59    impl Middleware for CaptureHeaders {
60        async fn handle(
61            &self,
62            req: reqwest::Request,
63            _extensions: &mut hyper::http::Extensions,
64            _next: Next<'_>,
65        ) -> reqwest_middleware::Result<reqwest::Response> {
66            self.received.lock().unwrap().push(req.headers().clone());
67            Ok(reqwest::Response::from(hyper::Response::new(
68                Vec::<u8>::new(),
69            )))
70        }
71    }
72
73    #[tokio::test]
74    async fn every_credentials_header_is_forwarded_and_authorization_is_sensitive() {
75        let mut credentials_headers = HeaderMap::new();
76        credentials_headers.insert(
77            reqwest::header::AUTHORIZATION,
78            HeaderValue::from_static("Bearer billed-token"),
79        );
80        credentials_headers.insert(
81            "x-goog-user-project",
82            HeaderValue::from_static("billing-project"),
83        );
84        let auth_headers =
85            GoogleAuthHeaders::from(Credentials::from(StubCredentials::new(credentials_headers)));
86        let capture = CaptureHeaders::default();
87        let client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new())
88            .with(GoogleAuthReqwestMiddleware::new(auth_headers))
89            .with(capture.clone())
90            .build();
91
92        client.get("http://orders.invalid/").send().await.unwrap();
93
94        let received = capture.received.lock().unwrap();
95        let authorization = &received[0][reqwest::header::AUTHORIZATION];
96        assert_eq!(authorization, "Bearer billed-token");
97        assert!(authorization.is_sensitive());
98        assert_eq!(received[0]["x-goog-user-project"], "billing-project");
99    }
100}