1use crate::{GoogleAuthHeaders, RequestTimeouts};
2use futures::{Future, TryFutureExt};
3use hyper::header::{HeaderMap, HeaderName, HeaderValue, USER_AGENT};
4use jiff::Timestamp;
5use std::pin::Pin;
6use std::sync::Arc;
7use std::task::{Context, Poll};
8use tonic::client::GrpcService;
9use tower::Service;
10use tower_layer::Layer;
11use tracing::*;
12
13const X_GOOG_API_CLIENT: HeaderName = HeaderName::from_static("x-goog-api-client");
14const GOOGLE_CLOUD_RESOURCE_PREFIX: HeaderName =
15 HeaderName::from_static("google-cloud-resource-prefix");
16
17fn default_headers(cloud_resource_prefix: Option<String>) -> crate::error::Result<HeaderMap> {
18 let mut headers = HeaderMap::new();
19 let default_agent =
20 HeaderValue::from_static(concat!("gcloud-sdk-rs/", env!("CARGO_PKG_VERSION")));
21 headers.insert(USER_AGENT, default_agent.clone());
22 headers.insert(X_GOOG_API_CLIENT, default_agent);
23 if let Some(prefix) = cloud_resource_prefix {
24 headers.insert(
25 GOOGLE_CLOUD_RESOURCE_PREFIX,
26 HeaderValue::from_str(&prefix)?,
27 );
28 }
29 Ok(headers)
30}
31
32fn append_header_value(
35 headers: &HeaderMap,
36 name: &HeaderName,
37 extra: &str,
38) -> crate::error::Result<HeaderValue> {
39 let combined = match headers.get(name).and_then(|v| v.to_str().ok()) {
40 Some(current) => format!("{current} {extra}"),
41 None => extra.to_string(),
42 };
43 Ok(HeaderValue::from_str(&combined)?)
44}
45
46pub(crate) fn replace_headers(target: &mut HeaderMap, source: &HeaderMap) {
53 for name in source.keys() {
54 target.remove(name);
55 }
56 target.extend(
57 source
58 .iter()
59 .map(|(name, value)| (name.clone(), value.clone())),
60 );
61}
62
63#[derive(Clone)]
64pub struct GoogleAuthMiddlewareService<T> {
65 inner: T,
66 auth_headers: Arc<GoogleAuthHeaders>,
67 headers: Arc<HeaderMap>,
69 request_timeouts: RequestTimeouts,
71}
72
73impl<T> GoogleAuthMiddlewareService<T> {
74 pub fn new(
75 service: T,
76 auth_headers: impl Into<Arc<GoogleAuthHeaders>>,
77 cloud_resource_prefix: Option<String>,
78 ) -> crate::error::Result<GoogleAuthMiddlewareService<T>> {
79 Ok(GoogleAuthMiddlewareService {
80 inner: service,
81 auth_headers: auth_headers.into(),
82 headers: Arc::new(default_headers(cloud_resource_prefix)?),
83 request_timeouts: RequestTimeouts::new(),
84 })
85 }
86
87 pub(crate) fn set_request_timeouts(&mut self, request_timeouts: RequestTimeouts) {
88 self.request_timeouts = request_timeouts;
89 }
90
91 pub fn set_user_agent(&mut self, user_agent: String) -> crate::error::Result<()> {
92 let value = HeaderValue::from_str(&user_agent)?;
93 Arc::make_mut(&mut self.headers).insert(USER_AGENT, value);
94 Ok(())
95 }
96
97 pub fn set_x_goog_api_client(&mut self, x_goog_api_client: String) -> crate::error::Result<()> {
98 let value = HeaderValue::from_str(&x_goog_api_client)?;
99 Arc::make_mut(&mut self.headers).insert(X_GOOG_API_CLIENT, value);
100 Ok(())
101 }
102
103 pub fn set_cloud_resource_prefix(
104 &mut self,
105 cloud_resource_prefix: String,
106 ) -> crate::error::Result<()> {
107 let value = HeaderValue::from_str(&cloud_resource_prefix)?;
108 Arc::make_mut(&mut self.headers).insert(GOOGLE_CLOUD_RESOURCE_PREFIX, value);
109 Ok(())
110 }
111
112 pub fn append_user_agent(&mut self, user_agent: String) -> crate::error::Result<()> {
113 let value = append_header_value(&self.headers, &USER_AGENT, &user_agent)?;
114 Arc::make_mut(&mut self.headers).insert(USER_AGENT, value);
115 Ok(())
116 }
117
118 pub fn append_x_goog_api_client(
119 &mut self,
120 x_goog_api_client: String,
121 ) -> crate::error::Result<()> {
122 let value = append_header_value(&self.headers, &X_GOOG_API_CLIENT, &x_goog_api_client)?;
123 Arc::make_mut(&mut self.headers).insert(X_GOOG_API_CLIENT, value);
124 Ok(())
125 }
126
127 pub fn set_additional_headers(&mut self, additional_headers: HeaderMap) {
128 Arc::make_mut(&mut self.headers).extend(additional_headers);
129 }
130
131 pub(crate) fn with_inner<U>(
136 &self,
137 inner: U,
138 cloud_resource_prefix: Option<String>,
139 ) -> crate::error::Result<GoogleAuthMiddlewareService<U>> {
140 let mut headers = HeaderMap::clone(&self.headers);
141 headers.remove(GOOGLE_CLOUD_RESOURCE_PREFIX);
142 if let Some(prefix) = cloud_resource_prefix {
143 headers.insert(
144 GOOGLE_CLOUD_RESOURCE_PREFIX,
145 HeaderValue::from_str(&prefix)?,
146 );
147 }
148 Ok(GoogleAuthMiddlewareService {
149 inner,
150 auth_headers: Arc::clone(&self.auth_headers),
151 headers: Arc::new(headers),
152 request_timeouts: self.request_timeouts,
153 })
154 }
155}
156
157impl<T, RequestBody> Service<hyper::Request<RequestBody>> for GoogleAuthMiddlewareService<T>
158where
159 T: GrpcService<RequestBody> + Send + Clone + 'static,
160 T::Future: 'static + Send,
161 RequestBody: 'static + Send,
162 T::ResponseBody: 'static + Send,
163 T::Error: 'static + Send,
164{
165 type Response = hyper::Response<T::ResponseBody>;
166 type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
167 type Future =
168 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
169
170 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
171 self.inner.poll_ready(cx).map_err(Into::into)
172 }
173
174 fn call(&mut self, mut req: hyper::Request<RequestBody>) -> Self::Future {
175 let auth_headers = Arc::clone(&self.auth_headers);
176 let headers = Arc::clone(&self.headers);
177 let clock = req
178 .extensions()
179 .get::<RequestTimeouts>()
180 .copied()
181 .unwrap_or(self.request_timeouts)
182 .start();
183
184 let clone = self.inner.clone();
187 let mut inner = std::mem::replace(&mut self.inner, clone);
188
189 let limited = clock.map(|clock| (clock, req.uri().clone()));
190 let send = async move {
191 let begin_time = Timestamp::now();
192 let request_auth_headers = auth_headers.headers().await.map_err(Box::new)?;
193 let token_generated_time = Timestamp::now();
194
195 let req_headers = req.headers_mut();
196 replace_headers(req_headers, &request_auth_headers);
197 replace_headers(req_headers, &headers);
198 if let Some(clock) = clock {
199 clock.insert_grpc_timeout(req_headers)?;
200 }
201
202 let req_uri = req.uri().clone();
203 inner
204 .call(req)
205 .map_ok(|x| {
206 let finished_time = Timestamp::now();
207 debug!(
208 %req_uri,
209 "OK: took {}ms (incl. token gen: {}ms)",
210 finished_time.duration_since(begin_time).as_millis(),
211 token_generated_time.duration_since(begin_time).as_millis()
212 );
213 x
214 })
215 .await
216 .map_err(|e| {
217 let finished_time = Timestamp::now();
218 error!(
219 %req_uri,
220 "Err: took {}ms (incl. token gen: {}ms)",
221 finished_time.duration_since(begin_time).as_millis(),
222 token_generated_time.duration_since(begin_time).as_millis()
223 );
224 e.into()
225 })
226 };
227 match limited {
228 Some((clock, uri)) => Box::pin(clock.limit(uri, send)),
229 None => Box::pin(send),
230 }
231 }
232}
233
234pub struct GoogleAuthMiddlewareLayer {
235 auth_headers: Arc<GoogleAuthHeaders>,
236 headers: Arc<HeaderMap>,
237}
238
239impl GoogleAuthMiddlewareLayer {
240 pub fn new(
241 auth_headers: impl Into<Arc<GoogleAuthHeaders>>,
242 cloud_resource_prefix: Option<String>,
243 ) -> crate::error::Result<Self> {
244 Ok(GoogleAuthMiddlewareLayer {
245 auth_headers: auth_headers.into(),
246 headers: Arc::new(default_headers(cloud_resource_prefix)?),
247 })
248 }
249
250 pub fn amend_user_agent(mut self, user_agent: String) -> crate::error::Result<Self> {
251 let value = append_header_value(&self.headers, &USER_AGENT, &user_agent)?;
252 Arc::make_mut(&mut self.headers).insert(USER_AGENT, value);
253 Ok(self)
254 }
255
256 pub fn amend_x_goog_api_client(
257 mut self,
258 x_goog_api_client: String,
259 ) -> crate::error::Result<Self> {
260 let value = append_header_value(&self.headers, &X_GOOG_API_CLIENT, &x_goog_api_client)?;
261 Arc::make_mut(&mut self.headers).insert(X_GOOG_API_CLIENT, value);
262 Ok(self)
263 }
264
265 pub fn set_additional_headers(&mut self, additional_headers: HeaderMap) {
266 Arc::make_mut(&mut self.headers).extend(additional_headers);
267 }
268}
269
270impl<S> Layer<S> for GoogleAuthMiddlewareLayer {
271 type Service = GoogleAuthMiddlewareService<S>;
272
273 fn layer(&self, service: S) -> GoogleAuthMiddlewareService<S> {
274 GoogleAuthMiddlewareService {
275 inner: service,
276 auth_headers: Arc::clone(&self.auth_headers),
277 headers: Arc::clone(&self.headers),
278 request_timeouts: RequestTimeouts::new(),
279 }
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::test_support::StubCredentials;
287 use google_cloud_auth::credentials::Credentials;
288 use hyper::{Request, Response};
289 use std::convert::Infallible;
290 use std::time::Duration;
291
292 #[derive(Clone)]
293 struct DummyService {
294 tx: Arc<tokio::sync::mpsc::Sender<Request<String>>>,
295 }
296
297 impl Service<Request<String>> for DummyService {
298 type Response = Response<String>;
299 type Error = Infallible;
300 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
301
302 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
303 Poll::Ready(Ok(()))
304 }
305
306 fn call(&mut self, req: Request<String>) -> Self::Future {
307 let tx = self.tx.clone();
308 Box::pin(async move {
309 tx.send(req).await.unwrap();
310 Ok(Response::builder()
311 .status(200)
312 .body("".to_string())
313 .unwrap())
314 })
315 }
316 }
317
318 fn request() -> Request<String> {
319 Request::builder()
320 .uri("http://example.com")
321 .body("".to_string())
322 .unwrap()
323 }
324
325 #[tokio::test]
326 async fn test_headers_presence() {
327 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
328 let dummy_service = DummyService { tx: Arc::new(tx) };
329 let mut service = GoogleAuthMiddlewareService::new(
330 dummy_service,
331 Arc::new(GoogleAuthHeaders::from(Credentials::from(
332 StubCredentials::bearer("dummy-token"),
333 ))),
334 None,
335 )
336 .unwrap();
337
338 tower::Service::call(&mut service, request()).await.unwrap();
339
340 let captured_req = rx.recv().await.unwrap();
341 let expected_default = format!("gcloud-sdk-rs/{}", env!("CARGO_PKG_VERSION"));
342 assert_eq!(
343 captured_req
344 .headers()
345 .get(hyper::header::USER_AGENT)
346 .unwrap(),
347 expected_default.as_str()
348 );
349 assert_eq!(
350 captured_req.headers().get("x-goog-api-client").unwrap(),
351 expected_default.as_str()
352 );
353 assert_eq!(
354 captured_req.headers().get("authorization").unwrap(),
355 "Bearer dummy-token"
356 );
357 }
358
359 #[tokio::test]
360 async fn every_credentials_header_is_forwarded() {
361 let mut credentials_headers = HeaderMap::new();
362 credentials_headers.insert(
363 hyper::header::AUTHORIZATION,
364 HeaderValue::from_static("Bearer billed-token"),
365 );
366 credentials_headers.insert(
367 "x-goog-user-project",
368 HeaderValue::from_static("billing-project"),
369 );
370 let credentials = StubCredentials::new(credentials_headers);
371
372 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
373 let mut service = GoogleAuthMiddlewareService::new(
374 DummyService { tx: Arc::new(tx) },
375 Arc::new(GoogleAuthHeaders::from(Credentials::from(
376 credentials.clone(),
377 ))),
378 None,
379 )
380 .unwrap();
381
382 tower::Service::call(&mut service, request()).await.unwrap();
383
384 let captured_req = rx.recv().await.unwrap();
385 let authorization = captured_req.headers().get("authorization").unwrap();
386 assert_eq!(authorization, "Bearer billed-token");
387 assert!(authorization.is_sensitive());
388 assert_eq!(
389 captured_req.headers().get("x-goog-user-project"),
390 Some(&HeaderValue::from_static("billing-project"))
391 );
392 }
393
394 #[tokio::test]
395 async fn headers_are_refreshed_when_the_entity_tag_changes() {
396 let credentials = StubCredentials::bearer("first-token");
397 let (tx, mut rx) = tokio::sync::mpsc::channel(3);
398 let mut service = GoogleAuthMiddlewareService::new(
399 DummyService { tx: Arc::new(tx) },
400 Arc::new(GoogleAuthHeaders::from(Credentials::from(
401 credentials.clone(),
402 ))),
403 None,
404 )
405 .unwrap();
406
407 tower::Service::call(&mut service, request()).await.unwrap();
408 tower::Service::call(&mut service, request()).await.unwrap();
409 credentials.rotate_bearer("second-token");
410 tower::Service::call(&mut service, request()).await.unwrap();
411
412 let authorizations: Vec<String> = [
413 rx.recv().await.unwrap(),
414 rx.recv().await.unwrap(),
415 rx.recv().await.unwrap(),
416 ]
417 .iter()
418 .map(|req| req.headers()["authorization"].to_str().unwrap().to_string())
419 .collect();
420 assert_eq!(
421 authorizations,
422 [
423 "Bearer first-token",
424 "Bearer first-token",
425 "Bearer second-token"
426 ]
427 );
428 assert_eq!(credentials.not_modified(), 1);
431 assert_eq!(credentials.served(), 2);
432 }
433
434 #[tokio::test]
435 async fn test_headers_amend() {
436 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
437 let dummy_service = DummyService { tx: Arc::new(tx) };
438
439 let layer = GoogleAuthMiddlewareLayer::new(
440 GoogleAuthHeaders::from(Credentials::from(StubCredentials::bearer("dummy-token"))),
441 None,
442 )
443 .unwrap()
444 .amend_user_agent("extra-ua".to_string())
445 .unwrap()
446 .amend_x_goog_api_client("extra-client".to_string())
447 .unwrap();
448
449 let mut service = layer.layer(dummy_service);
450
451 tower::Service::call(&mut service, request()).await.unwrap();
452
453 let captured_req = rx.recv().await.unwrap();
454 let expected_ua = format!("gcloud-sdk-rs/{} extra-ua", env!("CARGO_PKG_VERSION"));
455 let expected_client = format!("gcloud-sdk-rs/{} extra-client", env!("CARGO_PKG_VERSION"));
456
457 assert_eq!(
458 captured_req
459 .headers()
460 .get(hyper::header::USER_AGENT)
461 .unwrap(),
462 expected_ua.as_str()
463 );
464 assert_eq!(
465 captured_req.headers().get("x-goog-api-client").unwrap(),
466 expected_client.as_str()
467 );
468 }
469
470 #[tokio::test]
471 async fn invalid_user_agent_is_rejected_at_setter() {
472 let layer_result = GoogleAuthMiddlewareLayer::new(
473 GoogleAuthHeaders::from(Credentials::from(StubCredentials::bearer("dummy-token"))),
474 None,
475 )
476 .unwrap()
477 .amend_user_agent("bad\nvalue".to_string());
478
479 match layer_result {
480 Err(e) => assert!(matches!(
481 e.into_kind(),
482 crate::error::ErrorKind::HeaderValue(_)
483 )),
484 Ok(_) => panic!("expected an invalid header value to be rejected"),
485 }
486 }
487
488 #[tokio::test]
489 async fn amended_clone_does_not_change_sibling() {
490 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
491 let dummy_service = DummyService { tx: Arc::new(tx) };
492 let base_service = GoogleAuthMiddlewareService::new(
493 dummy_service,
494 Arc::new(GoogleAuthHeaders::from(Credentials::from(
495 StubCredentials::bearer("dummy-token"),
496 ))),
497 None,
498 )
499 .unwrap();
500
501 let mut amended = base_service.clone();
502 amended.append_user_agent("extra".to_string()).unwrap();
503
504 let mut sibling = base_service.clone();
505
506 tower::Service::call(&mut sibling, request()).await.unwrap();
507
508 let captured_req = rx.recv().await.unwrap();
509 let expected_default = format!("gcloud-sdk-rs/{}", env!("CARGO_PKG_VERSION"));
510 assert_eq!(
511 captured_req
512 .headers()
513 .get(hyper::header::USER_AGENT)
514 .unwrap(),
515 expected_default.as_str()
516 );
517 }
518
519 #[tokio::test]
520 async fn test_additional_headers() {
521 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
522 let dummy_service = DummyService { tx: Arc::new(tx) };
523 let mut service = GoogleAuthMiddlewareService::new(
524 dummy_service,
525 Arc::new(GoogleAuthHeaders::from(Credentials::from(
526 StubCredentials::bearer("dummy-token"),
527 ))),
528 None,
529 )
530 .unwrap();
531 let mut test_headers = hyper::HeaderMap::new();
532 test_headers.insert("x-test-header", "test-value".parse().unwrap());
533 service.set_additional_headers(test_headers);
534
535 tower::Service::call(&mut service, request()).await.unwrap();
536
537 let captured_req = rx.recv().await.unwrap();
538 assert_eq!(
539 captured_req.headers().get("x-test-header").unwrap(),
540 "test-value"
541 );
542 }
543
544 #[tokio::test]
545 async fn additional_headers_keep_every_value_of_a_repeated_name() {
546 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
547 let dummy_service = DummyService { tx: Arc::new(tx) };
548 let mut service = GoogleAuthMiddlewareService::new(
549 dummy_service,
550 Arc::new(GoogleAuthHeaders::from(Credentials::from(
551 StubCredentials::bearer("dummy-token"),
552 ))),
553 None,
554 )
555 .unwrap();
556
557 let mut test_headers = hyper::HeaderMap::new();
558 test_headers.append("x-multi", "first".parse().unwrap());
559 test_headers.append("x-multi", "second".parse().unwrap());
560 service.set_additional_headers(test_headers);
561
562 tower::Service::call(&mut service, request()).await.unwrap();
563
564 let captured_req = rx.recv().await.unwrap();
565 let values: Vec<&str> = captured_req
566 .headers()
567 .get_all("x-multi")
568 .iter()
569 .map(|v| v.to_str().unwrap())
570 .collect();
571 assert_eq!(values, vec!["first", "second"]);
572 }
573
574 #[tokio::test]
575 async fn with_inner_shares_the_token_and_replaces_the_resource_prefix() {
576 let credentials = StubCredentials::bearer("counted-token");
577
578 let (tx, mut rx) = tokio::sync::mpsc::channel(2);
579 let mut extra = hyper::HeaderMap::new();
580 extra.insert("x-test-header", "test-value".parse().unwrap());
581 let mut layer = GoogleAuthMiddlewareLayer::new(
582 GoogleAuthHeaders::from(Credentials::from(credentials.clone())),
583 Some("projects/first".to_string()),
584 )
585 .unwrap()
586 .amend_user_agent("extra-ua".to_string())
587 .unwrap();
588 layer.set_additional_headers(extra);
589 let mut first = layer.layer(DummyService {
590 tx: Arc::new(tx.clone()),
591 });
592
593 let (other_tx, mut other_rx) = tokio::sync::mpsc::channel(2);
594 let other = DummyService {
595 tx: Arc::new(other_tx),
596 };
597 let mut with_prefix = first
598 .with_inner(other.clone(), Some("projects/second".to_string()))
599 .unwrap();
600 let mut without_prefix = first.with_inner(other, None).unwrap();
601
602 for service in [&mut first, &mut with_prefix, &mut without_prefix] {
603 tower::Service::call(service, request()).await.unwrap();
604 }
605
606 let first_req = rx.recv().await.unwrap();
607 let with_prefix_req = other_rx.recv().await.unwrap();
608 let without_prefix_req = other_rx.recv().await.unwrap();
609
610 let expected_ua = format!("gcloud-sdk-rs/{} extra-ua", env!("CARGO_PKG_VERSION"));
611 for req in [&first_req, &with_prefix_req, &without_prefix_req] {
612 assert_eq!(
613 req.headers().get(hyper::header::USER_AGENT).unwrap(),
614 expected_ua.as_str()
615 );
616 assert_eq!(req.headers().get("x-test-header").unwrap(), "test-value");
617 assert_eq!(
618 req.headers().get("authorization").unwrap(),
619 "Bearer counted-token"
620 );
621 }
622 assert_eq!(
623 first_req
624 .headers()
625 .get(GOOGLE_CLOUD_RESOURCE_PREFIX)
626 .unwrap(),
627 "projects/first"
628 );
629 assert_eq!(
630 with_prefix_req
631 .headers()
632 .get(GOOGLE_CLOUD_RESOURCE_PREFIX)
633 .unwrap(),
634 "projects/second"
635 );
636 assert!(without_prefix_req
637 .headers()
638 .get(GOOGLE_CLOUD_RESOURCE_PREFIX)
639 .is_none());
640 assert_eq!(credentials.served(), 1);
643 }
644
645 #[tokio::test(start_paused = true)]
648 async fn grpc_timeout_carries_the_total_limit_only() {
649 let (tx, mut rx) = tokio::sync::mpsc::channel(4);
650 let mut service = GoogleAuthMiddlewareService::new(
651 DummyService { tx: Arc::new(tx) },
652 Arc::new(GoogleAuthHeaders::from(Credentials::from(
653 StubCredentials::bearer("dummy-token"),
654 ))),
655 None,
656 )
657 .unwrap();
658 let total = RequestTimeouts::new().with_total(Duration::from_secs(5));
659 let mut idle_only = request();
660 idle_only
661 .extensions_mut()
662 .insert(RequestTimeouts::new().with_idle(Duration::from_secs(5)));
663 let mut total_only = request();
664 total_only.extensions_mut().insert(total);
665 let mut caller_timeout = request();
666 caller_timeout.extensions_mut().insert(total);
667 caller_timeout
668 .headers_mut()
669 .insert("grpc-timeout", HeaderValue::from_static("1S"));
670
671 for req in [request(), idle_only, total_only, caller_timeout] {
672 tower::Service::call(&mut service, req).await.unwrap();
673 }
674
675 let mut sent = Vec::new();
676 for _ in 0..4 {
677 sent.push(
678 rx.recv()
679 .await
680 .unwrap()
681 .headers()
682 .get("grpc-timeout")
683 .cloned(),
684 );
685 }
686 assert_eq!(
687 sent,
688 [
689 None,
690 None,
691 Some(HeaderValue::from_static("5000000u")),
692 Some(HeaderValue::from_static("1S")),
693 ]
694 );
695 }
696}