Skip to main content

bitwarden_auth/registration/
post_keys_for_key_connector_registration.rs

1//! Initializes a new cryptographic state for a user and posts it to the server; enrolls the
2//! user to key connector unlock.
3use bitwarden_api_api::models::SetKeyConnectorKeyRequestModel;
4use bitwarden_core::key_management::account_cryptographic_state::WrappedAccountCryptographicState;
5use bitwarden_crypto::EncString;
6use bitwarden_encoding::B64;
7use tracing::{error, info};
8#[cfg(feature = "wasm")]
9use wasm_bindgen::prelude::*;
10
11use crate::registration::{RegistrationClient, RegistrationError};
12
13/// Result of Key Connector registration process.
14#[cfg_attr(
15    feature = "wasm",
16    derive(tsify::Tsify),
17    tsify(into_wasm_abi, from_wasm_abi)
18)]
19#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
20#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
21pub struct KeyConnectorRegistrationResult {
22    /// The account cryptographic state of the user.
23    pub account_cryptographic_state: WrappedAccountCryptographicState,
24    /// The key connector key used for unlocking.
25    pub key_connector_key: B64,
26    /// The encrypted user key, wrapped with the key connector key.
27    pub key_connector_key_wrapped_user_key: EncString,
28    /// The decrypted user key. This can be used to get the consuming client to an unlocked state.
29    pub user_key: B64,
30}
31
32#[cfg_attr(feature = "wasm", wasm_bindgen)]
33impl RegistrationClient {
34    /// Initializes a new cryptographic state for a user and posts it to the server; enrolls the
35    /// user to key connector unlock.
36    pub async fn post_keys_for_key_connector_registration(
37        &self,
38        key_connector_url: String,
39        sso_org_identifier: String,
40    ) -> Result<KeyConnectorRegistrationResult, RegistrationError> {
41        let client = &self.client.internal;
42        let configuration = &client.get_api_configurations();
43        let key_connector_client = client.get_key_connector_client(key_connector_url);
44
45        internal_post_keys_for_key_connector_registration(
46            self,
47            &configuration.api_client,
48            &key_connector_client,
49            sso_org_identifier,
50        )
51        .await
52    }
53}
54
55async fn internal_post_keys_for_key_connector_registration(
56    registration_client: &RegistrationClient,
57    api_client: &bitwarden_api_api::apis::ApiClient,
58    key_connector_api_client: &bitwarden_api_key_connector::apis::ApiClient,
59    sso_org_identifier: String,
60) -> Result<KeyConnectorRegistrationResult, RegistrationError> {
61    // First call crypto API to get all keys
62    info!("Initializing account cryptography");
63    let registration_crypto_result = registration_client
64        .client
65        .crypto()
66        .make_user_key_connector_registration()
67        .map_err(|_| RegistrationError::Crypto)?;
68
69    info!("Posting key connector key to key connector server");
70    let key_connector_key: B64 = registration_crypto_result.key_connector_key.into();
71    post_key_to_key_connector(key_connector_api_client, &key_connector_key).await?;
72
73    info!("Posting user account cryptographic state to server");
74    let request = SetKeyConnectorKeyRequestModel {
75        key_connector_key_wrapped_user_key: Some(
76            registration_crypto_result
77                .key_connector_key_wrapped_user_key
78                .to_string(),
79        ),
80        account_keys: Some(Box::new(registration_crypto_result.account_keys_request)),
81        ..SetKeyConnectorKeyRequestModel::new(sso_org_identifier.to_string())
82    };
83    api_client
84        .accounts_key_management_api()
85        .post_set_key_connector_key(Some(request))
86        .await
87        .map_err(|e| {
88            error!("Failed to post account cryptographic state to server: {e:?}");
89            RegistrationError::Api
90        })?;
91
92    info!("User initialized!");
93    // Note: This passing out of state and keys is temporary. Once SDK state management is more
94    // mature, the account cryptographic state and keys should be set directly here.
95    Ok(KeyConnectorRegistrationResult {
96        account_cryptographic_state: registration_crypto_result.account_cryptographic_state,
97        key_connector_key,
98        key_connector_key_wrapped_user_key: registration_crypto_result
99            .key_connector_key_wrapped_user_key,
100        user_key: registration_crypto_result.user_key.to_encoded().into(),
101    })
102}
103
104async fn post_key_to_key_connector(
105    key_connector_api_client: &bitwarden_api_key_connector::apis::ApiClient,
106    key_connector_key: &B64,
107) -> Result<(), RegistrationError> {
108    let request =
109        bitwarden_api_key_connector::models::user_key_request_model::UserKeyKeyRequestModel {
110            key: key_connector_key.to_string(),
111        };
112
113    let result = if key_connector_api_client
114        .user_keys_api()
115        .get_user_key()
116        .await
117        .is_ok()
118    {
119        info!("User's key connector key exists, updating");
120        key_connector_api_client
121            .user_keys_api()
122            .put_user_key(request)
123            .await
124    } else {
125        info!("User's key connector key does not exist, creating");
126        key_connector_api_client
127            .user_keys_api()
128            .post_user_key(request)
129            .await
130    };
131
132    result.map_err(|e| {
133        error!("Failed to post key connector key to key connector server: {e:?}");
134        RegistrationError::KeyConnectorApi
135    })
136}
137
138#[cfg(test)]
139mod tests {
140    use bitwarden_api_api::apis::ApiClient;
141    use bitwarden_core::Client;
142
143    use super::*;
144
145    const TEST_SSO_ORG_IDENTIFIER: &str = "test-org";
146
147    #[tokio::test]
148    async fn test_post_keys_for_key_connector_registration_success() {
149        let client = Client::new(None);
150        let registration_client = RegistrationClient::new(client);
151
152        let api_client = ApiClient::new_mocked(|mock| {
153            mock.accounts_key_management_api
154                .expect_post_set_key_connector_key()
155                .once()
156                .returning(move |_body| Ok(()));
157        });
158
159        let key_connector_api_client =
160            bitwarden_api_key_connector::apis::ApiClient::new_mocked(|mock| {
161                mock.user_keys_api
162                    .expect_get_user_key()
163                    .once()
164                    .returning(move || {
165                        Err(bitwarden_api_key_connector::apis::Error::ResponseError(
166                            bitwarden_api_key_connector::apis::ResponseContent {
167                                status: reqwest::StatusCode::NOT_FOUND,
168                                content: "Not Found".to_string(),
169                            },
170                        ))
171                    });
172                mock.user_keys_api
173                    .expect_post_user_key()
174                    .once()
175                    .returning(move |_body| Ok(()));
176            });
177
178        let result = internal_post_keys_for_key_connector_registration(
179            &registration_client,
180            &api_client,
181            &key_connector_api_client,
182            TEST_SSO_ORG_IDENTIFIER.to_string(),
183        )
184        .await;
185        assert!(result.is_ok());
186
187        // Assert that the mock expectations were met
188        if let ApiClient::Mock(mut mock) = api_client {
189            mock.accounts_key_management_api.checkpoint();
190        }
191        if let bitwarden_api_key_connector::apis::ApiClient::Mock(mut mock) =
192            key_connector_api_client
193        {
194            mock.user_keys_api.checkpoint();
195        }
196    }
197
198    #[tokio::test]
199    async fn test_post_keys_for_key_connector_registration_key_connector_api_failure() {
200        let client = Client::new(None);
201        let registration_client = RegistrationClient::new(client);
202
203        let api_client = ApiClient::new_mocked(|mock| {
204            // Should not be called if Key Connector API fails
205            mock.accounts_key_management_api
206                .expect_post_set_key_connector_key()
207                .never();
208        });
209
210        let key_connector_api_client =
211            bitwarden_api_key_connector::apis::ApiClient::new_mocked(|mock| {
212                mock.user_keys_api
213                    .expect_get_user_key()
214                    .once()
215                    .returning(move || {
216                        Err(bitwarden_api_key_connector::apis::Error::ResponseError(
217                            bitwarden_api_key_connector::apis::ResponseContent {
218                                status: reqwest::StatusCode::NOT_FOUND,
219                                content: "Not Found".to_string(),
220                            },
221                        ))
222                    });
223                mock.user_keys_api
224                    .expect_post_user_key()
225                    .once()
226                    .returning(move |_body| {
227                        Err(bitwarden_api_key_connector::apis::Error::Serde(
228                            serde_json::Error::io(std::io::Error::other("API error")),
229                        ))
230                    });
231            });
232
233        let result = internal_post_keys_for_key_connector_registration(
234            &registration_client,
235            &api_client,
236            &key_connector_api_client,
237            TEST_SSO_ORG_IDENTIFIER.to_string(),
238        )
239        .await;
240
241        assert!(result.is_err());
242        assert!(matches!(
243            result.unwrap_err(),
244            RegistrationError::KeyConnectorApi
245        ));
246
247        // Assert that the mock expectations were met
248        if let ApiClient::Mock(mut mock) = api_client {
249            mock.accounts_key_management_api.checkpoint();
250        }
251        if let bitwarden_api_key_connector::apis::ApiClient::Mock(mut mock) =
252            key_connector_api_client
253        {
254            mock.user_keys_api.checkpoint();
255        }
256    }
257
258    #[tokio::test]
259    async fn test_post_keys_for_key_connector_registration_api_failure() {
260        let client = Client::new(None);
261        let registration_client = RegistrationClient::new(client);
262
263        let api_client = ApiClient::new_mocked(|mock| {
264            mock.accounts_key_management_api
265                .expect_post_set_key_connector_key()
266                .once()
267                .returning(move |_body| {
268                    Err(serde_json::Error::io(std::io::Error::other("API error")).into())
269                });
270        });
271
272        let key_connector_api_client =
273            bitwarden_api_key_connector::apis::ApiClient::new_mocked(|mock| {
274                mock.user_keys_api
275                    .expect_get_user_key()
276                    .once()
277                    .returning(move || {
278                        Err(bitwarden_api_key_connector::apis::Error::ResponseError(
279                            bitwarden_api_key_connector::apis::ResponseContent {
280                                status: reqwest::StatusCode::NOT_FOUND,
281                                content: "Not Found".to_string(),
282                            },
283                        ))
284                    });
285                mock.user_keys_api
286                    .expect_post_user_key()
287                    .once()
288                    .returning(move |_body| Ok(()));
289            });
290
291        let result = internal_post_keys_for_key_connector_registration(
292            &registration_client,
293            &api_client,
294            &key_connector_api_client,
295            TEST_SSO_ORG_IDENTIFIER.to_string(),
296        )
297        .await;
298
299        assert!(result.is_err());
300        assert!(matches!(result.unwrap_err(), RegistrationError::Api));
301
302        // Assert that the mock expectations were met
303        if let ApiClient::Mock(mut mock) = api_client {
304            mock.accounts_key_management_api.checkpoint();
305        }
306        if let bitwarden_api_key_connector::apis::ApiClient::Mock(mut mock) =
307            key_connector_api_client
308        {
309            mock.user_keys_api.checkpoint();
310        }
311    }
312}