bitwarden_core/client/
internal.rs

1use std::sync::{Arc, OnceLock, RwLock};
2
3use bitwarden_crypto::KeyStore;
4#[cfg(any(feature = "internal", feature = "secrets"))]
5use bitwarden_crypto::SymmetricCryptoKey;
6#[cfg(feature = "internal")]
7use bitwarden_crypto::{EncString, Kdf, MasterKey, PinKey, UnsignedSharedKey};
8#[cfg(feature = "internal")]
9use bitwarden_state::registry::StateRegistry;
10use chrono::Utc;
11use uuid::Uuid;
12
13#[cfg(any(feature = "internal", feature = "secrets"))]
14use crate::client::encryption_settings::EncryptionSettings;
15#[cfg(feature = "secrets")]
16use crate::client::login_method::ServiceAccountLoginMethod;
17use crate::{
18    auth::renew::renew_token, client::login_method::LoginMethod, error::UserIdAlreadySetError,
19    key_management::KeyIds, DeviceType,
20};
21#[cfg(feature = "internal")]
22use crate::{
23    client::encryption_settings::EncryptionSettingsError,
24    client::{flags::Flags, login_method::UserLoginMethod},
25    error::NotAuthenticatedError,
26};
27
28#[allow(missing_docs)]
29#[derive(Debug, Clone)]
30pub struct ApiConfigurations {
31    pub identity: bitwarden_api_identity::apis::configuration::Configuration,
32    pub api: bitwarden_api_api::apis::configuration::Configuration,
33    pub device_type: DeviceType,
34}
35
36#[derive(Debug, Default, Clone)]
37pub(crate) struct Tokens {
38    // These two fields are always written to, but they are not read
39    // from the secrets manager SDK.
40    #[cfg_attr(not(feature = "internal"), allow(dead_code))]
41    access_token: Option<String>,
42    pub(crate) expires_on: Option<i64>,
43
44    #[cfg_attr(not(feature = "internal"), allow(dead_code))]
45    pub(crate) refresh_token: Option<String>,
46}
47
48#[allow(missing_docs)]
49#[derive(Debug)]
50pub struct InternalClient {
51    pub(crate) user_id: OnceLock<Uuid>,
52    pub(crate) tokens: RwLock<Tokens>,
53    pub(crate) login_method: RwLock<Option<Arc<LoginMethod>>>,
54
55    #[cfg(feature = "internal")]
56    pub(super) flags: RwLock<Flags>,
57
58    /// Use Client::get_api_configurations().await to access this.
59    /// It should only be used directly in renew_token
60    #[doc(hidden)]
61    pub(crate) __api_configurations: RwLock<Arc<ApiConfigurations>>,
62
63    /// Reqwest client useable for external integrations like email forwarders, HIBP.
64    #[allow(unused)]
65    pub(crate) external_client: reqwest::Client,
66
67    pub(super) key_store: KeyStore<KeyIds>,
68
69    #[cfg(feature = "internal")]
70    pub(crate) repository_map: StateRegistry,
71}
72
73impl InternalClient {
74    #[allow(missing_docs)]
75    #[cfg(feature = "internal")]
76    pub fn load_flags(&self, flags: std::collections::HashMap<String, bool>) {
77        *self.flags.write().expect("RwLock is not poisoned") = Flags::load_from_map(flags);
78    }
79
80    #[allow(missing_docs)]
81    #[cfg(feature = "internal")]
82    pub fn get_flags(&self) -> Flags {
83        self.flags.read().expect("RwLock is not poisoned").clone()
84    }
85
86    #[cfg(feature = "internal")]
87    pub(crate) fn get_login_method(&self) -> Option<Arc<LoginMethod>> {
88        self.login_method
89            .read()
90            .expect("RwLock is not poisoned")
91            .clone()
92    }
93
94    #[allow(missing_docs)]
95    pub fn get_access_token_organization(&self) -> Option<Uuid> {
96        match self
97            .login_method
98            .read()
99            .expect("RwLock is not poisoned")
100            .as_deref()
101        {
102            #[cfg(feature = "secrets")]
103            Some(LoginMethod::ServiceAccount(ServiceAccountLoginMethod::AccessToken {
104                organization_id,
105                ..
106            })) => Some(*organization_id),
107            _ => None,
108        }
109    }
110
111    #[cfg(any(feature = "internal", feature = "secrets"))]
112    pub(crate) fn set_login_method(&self, login_method: LoginMethod) {
113        use log::debug;
114
115        debug! {"setting login method: {:#?}", login_method}
116        *self.login_method.write().expect("RwLock is not poisoned") = Some(Arc::new(login_method));
117    }
118
119    pub(crate) fn set_tokens(&self, token: String, refresh_token: Option<String>, expires_in: u64) {
120        *self.tokens.write().expect("RwLock is not poisoned") = Tokens {
121            access_token: Some(token.clone()),
122            expires_on: Some(Utc::now().timestamp() + expires_in as i64),
123            refresh_token,
124        };
125        let mut guard = self
126            .__api_configurations
127            .write()
128            .expect("RwLock is not poisoned");
129
130        let inner = Arc::make_mut(&mut guard);
131        inner.identity.oauth_access_token = Some(token.clone());
132        inner.api.oauth_access_token = Some(token);
133    }
134
135    #[allow(missing_docs)]
136    #[cfg(feature = "internal")]
137    pub fn is_authed(&self) -> bool {
138        let is_token_set = self
139            .tokens
140            .read()
141            .expect("RwLock is not poisoned")
142            .access_token
143            .is_some();
144        let is_login_method_set = self
145            .login_method
146            .read()
147            .expect("RwLock is not poisoned")
148            .is_some();
149
150        is_token_set || is_login_method_set
151    }
152
153    #[allow(missing_docs)]
154    #[cfg(feature = "internal")]
155    pub fn get_kdf(&self) -> Result<Kdf, NotAuthenticatedError> {
156        match self
157            .login_method
158            .read()
159            .expect("RwLock is not poisoned")
160            .as_deref()
161        {
162            Some(LoginMethod::User(
163                UserLoginMethod::Username { kdf, .. } | UserLoginMethod::ApiKey { kdf, .. },
164            )) => Ok(kdf.clone()),
165            _ => Err(NotAuthenticatedError),
166        }
167    }
168
169    #[allow(missing_docs)]
170    pub async fn get_api_configurations(&self) -> Arc<ApiConfigurations> {
171        // At the moment we ignore the error result from the token renewal, if it fails,
172        // the token will end up expiring and the next operation is going to fail anyway.
173        renew_token(self).await.ok();
174        self.__api_configurations
175            .read()
176            .expect("RwLock is not poisoned")
177            .clone()
178    }
179
180    #[allow(missing_docs)]
181    #[cfg(feature = "internal")]
182    pub fn get_http_client(&self) -> &reqwest::Client {
183        &self.external_client
184    }
185
186    #[allow(missing_docs)]
187    pub fn get_key_store(&self) -> &KeyStore<KeyIds> {
188        &self.key_store
189    }
190
191    #[allow(missing_docs)]
192    pub fn init_user_id(&self, user_id: Uuid) -> Result<(), UserIdAlreadySetError> {
193        let set_uuid = self.user_id.get_or_init(|| user_id);
194
195        // Only return an error if the user_id is already set to a different value,
196        // as we want an SDK client to be tied to a single user_id.
197        // If it's the same value, we can just do nothing.
198        if *set_uuid != user_id {
199            Err(UserIdAlreadySetError)
200        } else {
201            Ok(())
202        }
203    }
204
205    #[allow(missing_docs)]
206    pub fn get_user_id(&self) -> Option<Uuid> {
207        self.user_id.get().copied()
208    }
209
210    #[cfg(feature = "internal")]
211    pub(crate) fn initialize_user_crypto_master_key(
212        &self,
213        master_key: MasterKey,
214        user_key: EncString,
215        private_key: EncString,
216        signing_key: Option<EncString>,
217    ) -> Result<(), EncryptionSettingsError> {
218        let user_key = master_key.decrypt_user_key(user_key)?;
219        EncryptionSettings::new_decrypted_key(user_key, private_key, signing_key, &self.key_store)?;
220
221        Ok(())
222    }
223
224    #[cfg(feature = "internal")]
225    pub(crate) fn initialize_user_crypto_decrypted_key(
226        &self,
227        user_key: SymmetricCryptoKey,
228        private_key: EncString,
229        signing_key: Option<EncString>,
230    ) -> Result<(), EncryptionSettingsError> {
231        EncryptionSettings::new_decrypted_key(user_key, private_key, signing_key, &self.key_store)?;
232
233        Ok(())
234    }
235
236    #[cfg(feature = "internal")]
237    pub(crate) fn initialize_user_crypto_pin(
238        &self,
239        pin_key: PinKey,
240        pin_protected_user_key: EncString,
241        private_key: EncString,
242        signing_key: Option<EncString>,
243    ) -> Result<(), EncryptionSettingsError> {
244        let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?;
245        self.initialize_user_crypto_decrypted_key(decrypted_user_key, private_key, signing_key)
246    }
247
248    #[cfg(feature = "secrets")]
249    pub(crate) fn initialize_crypto_single_org_key(
250        &self,
251        organization_id: Uuid,
252        key: SymmetricCryptoKey,
253    ) {
254        EncryptionSettings::new_single_org_key(organization_id, key, &self.key_store);
255    }
256
257    #[allow(missing_docs)]
258    #[cfg(feature = "internal")]
259    pub fn initialize_org_crypto(
260        &self,
261        org_keys: Vec<(Uuid, UnsignedSharedKey)>,
262    ) -> Result<(), EncryptionSettingsError> {
263        EncryptionSettings::set_org_keys(org_keys, &self.key_store)
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use crate::Client;
270
271    #[test]
272    fn initializing_user_multiple_times() {
273        use super::*;
274
275        let client = Client::new(None);
276        let user_id = Uuid::new_v4();
277
278        // Setting the user ID for the first time should work.
279        assert!(client.internal.init_user_id(user_id).is_ok());
280        assert_eq!(client.internal.get_user_id(), Some(user_id));
281
282        // Trying to set the same user_id again should not return an error.
283        assert!(client.internal.init_user_id(user_id).is_ok());
284
285        // Trying to set a different user_id should return an error.
286        let different_user_id = Uuid::new_v4();
287        assert!(client.internal.init_user_id(different_user_id).is_err());
288    }
289}