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