bitwarden_core/client/
builder.rs1#[cfg(feature = "internal")]
2use std::sync::RwLock;
3use std::sync::{Arc, OnceLock};
4
5use bitwarden_api_base::new_http_client_builder;
6use bitwarden_crypto::{CipherSuite, KeyStore};
7use bitwarden_state::registry::StateRegistry;
8use reqwest::header::{self, HeaderValue};
9
10#[cfg(feature = "internal")]
11use crate::key_management::state_bridge::StateBridge;
12use crate::{
13 auth::auth_tokens::{NoopTokenHandler, TokenHandler},
14 client::{
15 client::Client,
16 client_settings::{ClientName, ClientSettings, HostPlatformInfo},
17 internal::{ApiConfigurations, InternalClient},
18 },
19};
20
21pub struct ClientBuilder {
23 settings: Option<ClientSettings>,
24 token_handler: Arc<dyn TokenHandler>,
25 state_registry: Option<StateRegistry>,
26 middleware: Vec<Arc<dyn reqwest_middleware::Middleware>>,
27 #[cfg(feature = "test-fixtures")]
28 api_configurations: Option<Arc<ApiConfigurations>>,
29}
30
31impl ClientBuilder {
32 pub fn new() -> Self {
34 Self {
35 settings: None,
36 token_handler: Arc::new(NoopTokenHandler),
37 state_registry: None,
38 middleware: Vec::new(),
39 #[cfg(feature = "test-fixtures")]
40 api_configurations: None,
41 }
42 }
43
44 #[cfg(feature = "test-fixtures")]
48 pub fn with_api_configurations(mut self, api_configurations: Arc<ApiConfigurations>) -> Self {
49 self.api_configurations = Some(api_configurations);
50 self
51 }
52
53 pub fn with_settings(mut self, settings: ClientSettings) -> Self {
55 self.settings = Some(settings);
56 self
57 }
58
59 pub fn with_token_handler(mut self, token_handler: Arc<dyn TokenHandler>) -> Self {
61 self.token_handler = token_handler;
62 self
63 }
64
65 pub fn with_middleware(
67 mut self,
68 middleware: Vec<Arc<dyn reqwest_middleware::Middleware>>,
69 ) -> Self {
70 self.middleware = middleware;
71 self
72 }
73
74 pub fn with_state(mut self, state_registry: StateRegistry) -> Self {
77 self.state_registry = Some(state_registry);
78 self
79 }
80
81 pub fn build(self) -> Client {
83 let settings = self.settings.unwrap_or_default();
84
85 let external_http_client = new_http_client_builder()
86 .build()
87 .expect("External HTTP Client build should not fail");
88
89 let headers = build_default_headers(&HostPlatformInfo::from(&settings));
90
91 let key_store = KeyStore::default();
92 let state_registry = self
93 .state_registry
94 .unwrap_or_else(StateRegistry::new_with_memory_db);
95
96 let identity_http_client = new_http_client_builder()
98 .default_headers(headers.clone())
99 .build()
100 .expect("Bw HTTP Client build should not fail");
101 let identity = bitwarden_api_identity::Configuration {
102 base_path: settings.identity_url,
103 client: identity_http_client.into(),
104 };
105
106 let auth_middleware = self.token_handler.initialize_middleware(
108 &state_registry,
109 identity.clone(),
110 key_store.clone(),
111 );
112
113 #[cfg(not(target_arch = "wasm32"))]
118 let api_http_client = if self.middleware.is_empty() {
119 new_http_client_builder()
120 .default_headers(headers)
121 .build()
122 .expect("Bw HTTP Client build should not fail")
123 } else {
124 new_http_client_builder()
125 .default_headers(headers)
126 .redirect(reqwest::redirect::Policy::none())
127 .build()
128 .expect("Bw HTTP Client (no redirect) build should not fail")
129 };
130
131 #[cfg(target_arch = "wasm32")]
132 let api_http_client = new_http_client_builder()
133 .default_headers(headers)
134 .build()
135 .expect("Bw HTTP Client build should not fail");
136
137 let mut middleware_builder = reqwest_middleware::ClientBuilder::new(api_http_client);
139 for mw in self.middleware {
140 middleware_builder = middleware_builder.with_arc(mw);
141 }
142 let bw_http_client = middleware_builder.with_arc(auth_middleware).build();
143 let api = bitwarden_api_api::Configuration {
144 base_path: settings.api_url,
145 client: bw_http_client,
146 };
147
148 #[cfg(feature = "test-fixtures")]
149 let api_configurations = self
150 .api_configurations
151 .unwrap_or_else(|| ApiConfigurations::new(identity, api, settings.device_type));
152 #[cfg(not(feature = "test-fixtures"))]
153 let api_configurations = ApiConfigurations::new(identity, api, settings.device_type);
154
155 let client = Client {
156 internal: Arc::new(InternalClient {
157 user_id: OnceLock::new(),
158 token_handler: self.token_handler,
159 api_configurations,
160 external_http_client,
161 key_store,
162 #[cfg(feature = "internal")]
163 security_state: RwLock::new(None),
164 #[cfg(feature = "internal")]
165 state_bridge: StateBridge::new(),
166 state_registry,
167 }),
168 };
169
170 client
173 .internal
174 .get_key_store()
175 .set_cipher_suite(CipherSuite::from_gov_mode(client.gov_mode()));
176
177 client
178 }
179}
180
181impl Default for ClientBuilder {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187pub(crate) fn build_default_headers(info: &HostPlatformInfo) -> header::HeaderMap {
189 let mut headers = header::HeaderMap::new();
190
191 if let Some(device_identifier) = &info.device_identifier {
194 headers.append(
195 "Device-Identifier",
196 HeaderValue::from_str(device_identifier)
197 .expect("Device identifier should be a valid header value"),
198 );
199 }
200
201 if let Some(client_type) = Into::<Option<ClientName>>::into(info.device_type) {
202 headers.append(
203 "Bitwarden-Client-Name",
204 HeaderValue::from_str(&client_type.to_string())
205 .expect("All ASCII strings are valid header values"),
206 );
207 }
208
209 if let Some(version) = &info.bitwarden_client_version {
210 headers.append(
211 "Bitwarden-Client-Version",
212 HeaderValue::from_str(version).expect("Version should be a valid header value"),
213 );
214 }
215
216 if let Some(package_type) = &info.bitwarden_package_type {
217 headers.append(
218 "Bitwarden-Package-Type",
219 HeaderValue::from_str(package_type)
220 .expect("Package type should be a valid header value"),
221 );
222 }
223
224 headers.append(
227 "Device-Type",
228 HeaderValue::from_str(&(info.device_type as u8).to_string())
229 .expect("All numbers are valid ASCII"),
230 );
231
232 headers.append(
233 reqwest::header::USER_AGENT,
234 HeaderValue::from_str(&info.user_agent).expect("User agent should be a valid header value"),
235 );
236
237 headers
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn test_client_builder_default_builds() {
246 let _client = ClientBuilder::new().build();
247 }
248
249 #[test]
250 fn test_client_builder_with_settings_builds() {
251 let settings = ClientSettings::default();
252 let _client = ClientBuilder::new().with_settings(settings).build();
253 }
254
255 #[test]
256 fn test_client_builder_with_token_handler_builds() {
257 let handler: Arc<dyn TokenHandler> = Arc::new(NoopTokenHandler);
258 let _client = ClientBuilder::new().with_token_handler(handler).build();
259 }
260
261 #[test]
262 fn test_client_builder_chain_order_independence() {
263 let _a = ClientBuilder::new()
264 .with_settings(ClientSettings::default())
265 .with_token_handler(Arc::new(NoopTokenHandler) as Arc<dyn TokenHandler>)
266 .build();
267 let _b = ClientBuilder::new()
268 .with_token_handler(Arc::new(NoopTokenHandler) as Arc<dyn TokenHandler>)
269 .with_settings(ClientSettings::default())
270 .build();
271 }
272
273 #[test]
274 fn test_client_builder_with_state_builds() {
275 use bitwarden_state::registry::StateRegistry;
276 let registry = StateRegistry::new_with_memory_db();
277 let _client = ClientBuilder::new().with_state(registry).build();
278 }
279
280 #[test]
281 fn test_client_builder_with_state_in_chain() {
282 use bitwarden_state::registry::StateRegistry;
283 let registry = StateRegistry::new_with_memory_db();
284 let _client = ClientBuilder::new()
285 .with_settings(ClientSettings::default())
286 .with_state(registry)
287 .build();
288 }
289
290 #[test]
291 fn test_client_builder_with_middleware_compiles() {
292 struct StubMiddleware;
293
294 #[async_trait::async_trait]
295 impl reqwest_middleware::Middleware for StubMiddleware {
296 async fn handle(
297 &self,
298 req: reqwest::Request,
299 extensions: &mut http::Extensions,
300 next: reqwest_middleware::Next<'_>,
301 ) -> reqwest_middleware::Result<reqwest::Response> {
302 next.run(req, extensions).await
303 }
304 }
305
306 let arc_middleware: Arc<dyn reqwest_middleware::Middleware> = Arc::new(StubMiddleware);
307 let _client = ClientBuilder::new()
308 .with_middleware(vec![arc_middleware])
309 .build();
310 }
311}