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