bitwarden_access_connector/auth/
token_handler.rs1use std::sync::{Arc, RwLock};
10
11use bitwarden_core::{auth::TokenHandler, key_management::KeySlotIds};
12use bitwarden_crypto::KeyStore;
13use bitwarden_state::registry::StateRegistry;
14
15use crate::{api::AccessConnectorAuthMiddleware, auth::session::SessionManager};
16
17#[derive(Clone, Default)]
19pub(crate) struct AccessConnectorTokenHandler {
20 inner: Arc<RwLock<Inner>>,
21}
22
23#[derive(Default)]
24struct Inner {
25 key_store: Option<KeyStore<KeySlotIds>>,
27 session: Option<Arc<SessionManager>>,
29}
30
31impl AccessConnectorTokenHandler {
32 pub(crate) fn key_store(&self) -> Option<KeyStore<KeySlotIds>> {
35 self.inner
36 .read()
37 .expect("RwLock is not poisoned")
38 .key_store
39 .clone()
40 }
41
42 pub(crate) fn set_session(&self, session: Arc<SessionManager>) {
44 self.inner
45 .write()
46 .expect("RwLock is not poisoned")
47 .session
48 .replace(session);
49 }
50
51 pub(crate) fn session(&self) -> Option<Arc<SessionManager>> {
52 self.inner
53 .read()
54 .expect("RwLock is not poisoned")
55 .session
56 .clone()
57 }
58}
59
60#[async_trait::async_trait]
61impl TokenHandler for AccessConnectorTokenHandler {
62 fn initialize_middleware(
63 &self,
64 _state_registry: &StateRegistry,
65 _identity_config: bitwarden_api_base::Configuration,
66 key_store: KeyStore<KeySlotIds>,
67 ) -> Arc<dyn reqwest_middleware::Middleware> {
68 self.inner
69 .write()
70 .expect("RwLock is not poisoned")
71 .key_store
72 .replace(key_store);
73
74 Arc::new(AccessConnectorAuthMiddleware::new(self.clone()))
75 }
76
77 async fn set_tokens(&self, _token: String, _refresh_token: Option<String>, _expires_in: u64) {}
79}
80
81#[cfg(test)]
82mod tests {
83 use bitwarden_api_api::apis::AuthRequired;
84 use wiremock::{
85 Mock, MockServer, ResponseTemplate,
86 matchers::{method, path},
87 };
88
89 use super::*;
90
91 #[test]
93 fn initialize_middleware_back_populates_the_key_store() {
94 let handler = AccessConnectorTokenHandler::default();
95 assert!(handler.key_store().is_none());
96
97 let _client = bitwarden_core::ClientBuilder::new()
98 .with_token_handler(Arc::new(handler.clone()))
99 .build();
100
101 assert!(
102 handler.key_store().is_some(),
103 "ClientBuilder::build must leave the key store available"
104 );
105 }
106
107 #[test]
109 fn the_key_store_is_shared_with_the_client() {
110 use bitwarden_core::key_management::SymmetricKeySlotId;
111 use bitwarden_crypto::{SymmetricCryptoKey, SymmetricKeyAlgorithm};
112
113 let handler = AccessConnectorTokenHandler::default();
114 let client = bitwarden_core::ClientBuilder::new()
115 .with_token_handler(Arc::new(handler.clone()))
116 .build();
117
118 let slot = SymmetricKeySlotId::Organization(
119 "3f1a5c4e-7b2d-4f8a-9c6e-1d2b3a4c5d6e"
120 .parse()
121 .expect("uuid"),
122 );
123 let store = handler.key_store().expect("key store");
124 #[allow(deprecated)]
125 store
126 .context_mut()
127 .set_symmetric_key(
128 slot,
129 SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac),
130 )
131 .expect("set key");
132
133 assert!(
134 client
135 .internal
136 .get_key_store()
137 .context()
138 .has_symmetric_key(slot)
139 );
140
141 store.clear();
142
143 assert!(
144 !client
145 .internal
146 .get_key_store()
147 .context()
148 .has_symmetric_key(slot),
149 "clearing the session's store must empty the client's"
150 );
151 }
152
153 #[tokio::test]
155 async fn an_authenticated_request_without_a_session_fails_and_is_not_sent() {
156 let server = MockServer::start().await;
157 Mock::given(method("GET"))
158 .and(path("/test"))
159 .respond_with(ResponseTemplate::new(200))
160 .mount(&server)
161 .await;
162
163 let handler = AccessConnectorTokenHandler::default();
164 let client = reqwest_middleware::ClientBuilder::new(
165 bitwarden_api_base::new_http_client_builder()
166 .build()
167 .expect("http client"),
168 )
169 .with(AccessConnectorAuthMiddleware::new(handler))
170 .build();
171
172 client
173 .get(format!("{}/test", server.uri()))
174 .with_extension(AuthRequired::Bearer)
175 .send()
176 .await
177 .expect_err("no session means no bearer");
178
179 assert!(
180 server.received_requests().await.unwrap().is_empty(),
181 "the request must not reach the server unauthenticated"
182 );
183 }
184}