kube_client/client/middleware/
mod.rs

1//! Middleware types returned from `ConfigExt` methods.
2use tower::{filter::AsyncFilterLayer, util::Either, Layer};
3pub(crate) use tower_http::auth::AddAuthorizationLayer;
4
5mod base_uri;
6mod extra_headers;
7
8pub use base_uri::{BaseUri, BaseUriLayer};
9pub use extra_headers::{ExtraHeaders, ExtraHeadersLayer};
10
11use super::auth::RefreshableToken;
12/// Layer to set up `Authorization` header depending on the config.
13pub struct AuthLayer(pub(crate) Either<AddAuthorizationLayer, AsyncFilterLayer<RefreshableToken>>);
14
15impl<S> Layer<S> for AuthLayer {
16    type Service = Either<
17        <AddAuthorizationLayer as Layer<S>>::Service,
18        <AsyncFilterLayer<RefreshableToken> as Layer<S>>::Service,
19    >;
20
21    fn layer(&self, inner: S) -> Self::Service {
22        self.0.layer(inner)
23    }
24}
25
26#[cfg(test)]
27mod tests {
28    use super::*;
29
30    use std::{matches, pin::pin, sync::Arc};
31
32    use chrono::{Duration, Utc};
33    use http::{header::AUTHORIZATION, HeaderValue, Request, Response};
34    use secrecy::SecretString;
35    use tokio::sync::Mutex;
36    use tokio_test::assert_ready_ok;
37    use tower::filter::AsyncFilterLayer;
38    use tower_test::{mock, mock::Handle};
39
40    use crate::{
41        client::{AuthError, Body},
42        config::AuthInfo,
43    };
44
45    #[tokio::test(flavor = "current_thread")]
46    async fn valid_token() {
47        const TOKEN: &str = "test";
48        let auth = test_token(TOKEN.into());
49        let (mut service, handle): (_, Handle<Request<Body>, Response<Body>>) =
50            mock::spawn_layer(AsyncFilterLayer::new(auth));
51
52        let spawned = tokio::spawn(async move {
53            // Receive the requests and respond
54            let mut handle = pin!(handle);
55            let (request, send) = handle.next_request().await.expect("service not called");
56            assert_eq!(
57                request.headers().get(AUTHORIZATION).unwrap(),
58                HeaderValue::try_from(format!("Bearer {TOKEN}")).unwrap()
59            );
60            send.send_response(Response::builder().body(Body::empty()).unwrap());
61        });
62
63        assert_ready_ok!(service.poll_ready());
64        service
65            .call(Request::builder().uri("/").body(Body::empty()).unwrap())
66            .await
67            .unwrap();
68        spawned.await.unwrap();
69    }
70
71    #[tokio::test(flavor = "current_thread")]
72    async fn invalid_token() {
73        const TOKEN: &str = "\n";
74        let auth = test_token(TOKEN.into());
75        let (mut service, _handle) =
76            mock::spawn_layer::<Request<Body>, Response<Body>, _>(AsyncFilterLayer::new(auth));
77        let err = service
78            .call(Request::builder().uri("/").body(Body::empty()).unwrap())
79            .await
80            .unwrap_err();
81
82        assert!(err.is::<AuthError>());
83        assert!(matches!(
84            *err.downcast::<AuthError>().unwrap(),
85            AuthError::InvalidBearerToken(_)
86        ));
87    }
88
89    fn test_token(token: String) -> RefreshableToken {
90        let expiry = Utc::now() + Duration::try_seconds(60 * 60).unwrap();
91        let secret_token = SecretString::from(token);
92        let info = AuthInfo {
93            token: Some(secret_token.clone()),
94            ..Default::default()
95        };
96        RefreshableToken::Exec(Arc::new(Mutex::new((secret_token, expiry, info))))
97    }
98}