bitwarden_organization_crypto_management/
key_id_backfill.rs1use bitwarden_api_api::models::SetOrganizationKeyIdRequest;
8use bitwarden_core::{OrganizationId, key_management::SymmetricKeySlotId};
9use bitwarden_crypto::KeyId;
10use bitwarden_error::bitwarden_error;
11use bitwarden_organizations::{OrganizationUserStatusType, OrganizationUserType};
12use bitwarden_state::repository::{RepositoryError, RepositoryOption};
13use thiserror::Error;
14use tracing::{error, info};
15
16use crate::OrganizationCryptoManagementClient;
17
18#[derive(Debug, Error)]
20#[bitwarden_error(flat)]
21pub enum OrganizationKeyIdBackfillError {
22 #[error("Organization state is not available")]
24 OrganizationStateNotAvailable(#[from] RepositoryError),
25 #[error("API call failed during organization key id backfill")]
27 Api,
28}
29
30#[bitwarden_ffi::wasm_export]
31impl OrganizationCryptoManagementClient {
32 pub async fn organization_key_ids_needing_backfill(
35 &self,
36 ) -> Result<Vec<OrganizationId>, OrganizationKeyIdBackfillError> {
37 Ok(self
38 .backfill_targets()
39 .await?
40 .into_iter()
41 .map(|(organization_id, _)| organization_id)
42 .collect())
43 }
44
45 pub async fn organization_key_id_backfill(&self) -> Result<(), OrganizationKeyIdBackfillError> {
53 let mut failed = false;
54
55 for (organization_id, key_id) in self.backfill_targets().await? {
56 info!(%organization_id, "Recording the organization key id with the server");
57 if let Err(e) = self
58 .api_configurations
59 .api_client
60 .organization_keys_api()
61 .set_organization_key_id(
62 organization_id.into(),
63 SetOrganizationKeyIdRequest {
64 organization_key_id: key_id.to_string(),
65 },
66 )
67 .await
68 {
69 error!(%organization_id, "Failed to post the organization key id: {e:?}");
71 failed = true;
72 }
73 }
74
75 if failed {
76 Err(OrganizationKeyIdBackfillError::Api)
77 } else {
78 Ok(())
79 }
80 }
81}
82
83impl OrganizationCryptoManagementClient {
84 async fn backfill_targets(
87 &self,
88 ) -> Result<Vec<(OrganizationId, KeyId)>, OrganizationKeyIdBackfillError> {
89 let organizations = self.repository.require()?.list().await?;
90
91 let ctx = self.key_store.context();
92 Ok(organizations
93 .into_iter()
94 .filter(|o| o.details.key_id.is_none())
96 .filter(|o| {
99 o.membership.is_member
100 && o.membership.status == OrganizationUserStatusType::Confirmed
101 && matches!(
102 o.membership.r#type,
103 OrganizationUserType::Owner | OrganizationUserType::Admin
104 )
105 })
106 .filter_map(|o| {
108 ctx.get_symmetric_key_id(SymmetricKeySlotId::Organization(o.details.id))
109 .map(|key_id| (o.details.id, key_id))
110 })
111 .collect())
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use std::sync::{Arc, Mutex};
118
119 use bitwarden_api_api::apis::{ApiClient, ApiClientMock};
120 use bitwarden_core::client::ApiConfigurations;
121 use bitwarden_crypto::{KeyStore, SymmetricCryptoKey, SymmetricKeyAlgorithm};
122 use bitwarden_organizations::{
123 OrganizationDetails, OrganizationMembership, Permissions, ProfileOrganization,
124 };
125 use bitwarden_state::repository::Repository;
126 use bitwarden_test::MemoryRepository;
127
128 use super::*;
129
130 fn organization(user_type: OrganizationUserType) -> ProfileOrganization {
131 let default = ProfileOrganization::default();
132 ProfileOrganization {
133 details: OrganizationDetails {
134 id: OrganizationId::new_v4(),
135 ..default.details
136 },
137 membership: OrganizationMembership {
138 r#type: user_type,
139 ..default.membership
140 },
141 ..ProfileOrganization::default()
142 }
143 }
144
145 async fn client(
148 organizations: &[ProfileOrganization],
149 organization_ids: &[OrganizationId],
150 configure: impl FnOnce(&mut ApiClientMock),
151 ) -> OrganizationCryptoManagementClient {
152 let repository = Arc::new(MemoryRepository::<ProfileOrganization>::default());
153 for organization in organizations {
154 repository
155 .set(organization.details.id, organization.clone())
156 .await
157 .expect("the repository is in memory");
158 }
159
160 let key_store = KeyStore::default();
161 {
162 let mut ctx = key_store.context_mut();
163 for organization_id in organization_ids {
164 #[allow(deprecated)]
165 ctx.set_symmetric_key(
166 SymmetricKeySlotId::Organization(*organization_id),
167 SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac),
168 )
169 .expect("the context is mutable");
170 }
171 }
172
173 OrganizationCryptoManagementClient {
174 key_store,
175 api_configurations: Arc::new(ApiConfigurations::from_api_client(
176 ApiClient::new_mocked(configure),
177 )),
178 repository: Some(repository),
179 }
180 }
181
182 fn expect_no_posts(mock: &mut ApiClientMock) {
184 mock.organization_keys_api
185 .expect_set_organization_key_id()
186 .never();
187 }
188
189 fn organization_key_id(
190 client: &OrganizationCryptoManagementClient,
191 organization_id: OrganizationId,
192 ) -> String {
193 client
194 .key_store
195 .context()
196 .get_symmetric_key_id(SymmetricKeySlotId::Organization(organization_id))
197 .expect("an organization key has a key id")
198 .to_string()
199 }
200
201 async fn assert_backfill_posts_the_key_id(user_type: OrganizationUserType) {
202 let organization = organization(user_type);
203 let organization_id = organization.details.id;
204 let posted = Arc::new(Mutex::new(Vec::new()));
206 let recorder = posted.clone();
207 let client = client(&[organization], &[organization_id], |mock| {
208 mock.organization_keys_api
209 .expect_set_organization_key_id()
210 .once()
211 .returning(move |organization_id, body| {
212 recorder.lock().unwrap().push((
213 OrganizationId::new(organization_id),
214 body.organization_key_id,
215 ));
216 Ok(())
217 });
218 })
219 .await;
220
221 assert_eq!(
222 client
223 .organization_key_ids_needing_backfill()
224 .await
225 .unwrap(),
226 vec![organization_id]
227 );
228 client.organization_key_id_backfill().await.unwrap();
229
230 assert_eq!(
231 *posted.lock().unwrap(),
232 vec![(
233 organization_id,
234 organization_key_id(&client, organization_id)
235 )]
236 );
237 }
238
239 #[tokio::test]
240 async fn test_backfill_posts_the_key_id_for_an_owner() {
241 assert_backfill_posts_the_key_id(OrganizationUserType::Owner).await;
242 }
243
244 #[tokio::test]
245 async fn test_backfill_posts_the_key_id_for_an_admin() {
246 assert_backfill_posts_the_key_id(OrganizationUserType::Admin).await;
247 }
248
249 async fn assert_nothing_to_backfill(
251 organizations: &[ProfileOrganization],
252 organization_ids: &[OrganizationId],
253 ) {
254 let client = client(organizations, organization_ids, expect_no_posts).await;
255
256 assert!(
257 client
258 .organization_key_ids_needing_backfill()
259 .await
260 .unwrap()
261 .is_empty()
262 );
263 client.organization_key_id_backfill().await.unwrap();
264 }
265
266 #[tokio::test]
267 async fn test_no_backfill_when_the_server_already_knows_the_key_id() {
268 let mut organization = organization(OrganizationUserType::Owner);
269 organization.details.key_id = Some(KeyId::make());
270 let organization_id = organization.details.id;
271
272 assert_nothing_to_backfill(&[organization], &[organization_id]).await;
273 }
274
275 #[tokio::test]
276 async fn test_no_backfill_for_custom_users_and_users() {
277 let mut custom = organization(OrganizationUserType::Custom);
279 custom.membership.permissions = Permissions {
280 access_event_logs: true,
281 access_import_export: true,
282 access_reports: true,
283 create_new_collections: true,
284 edit_any_collection: true,
285 delete_any_collection: true,
286 manage_groups: true,
287 manage_sso: true,
288 manage_policies: true,
289 manage_users: true,
290 manage_reset_password: true,
291 manage_scim: true,
292 manage_access_rules: true,
293 manage_rotation: true,
294 };
295 let user = organization(OrganizationUserType::User);
296 let ids = [custom.details.id, user.details.id];
297
298 assert_nothing_to_backfill(&[custom, user], &ids).await;
299 }
300
301 #[tokio::test]
302 async fn test_no_backfill_for_provider_only_access() {
303 let mut organization = organization(OrganizationUserType::Owner);
304 organization.membership.is_member = false;
305 organization.membership.is_provider_user = true;
306 let organization_id = organization.details.id;
307
308 assert_nothing_to_backfill(&[organization], &[organization_id]).await;
309 }
310
311 #[tokio::test]
312 async fn test_no_backfill_for_a_revoked_owner() {
313 let mut organization = organization(OrganizationUserType::Owner);
314 organization.membership.status = OrganizationUserStatusType::Revoked;
315 let organization_id = organization.details.id;
316
317 assert_nothing_to_backfill(&[organization], &[organization_id]).await;
318 }
319
320 #[tokio::test]
321 async fn test_no_backfill_without_an_organization_key() {
322 let organization = organization(OrganizationUserType::Owner);
323
324 assert_nothing_to_backfill(&[organization], &[]).await;
325 }
326
327 #[tokio::test]
328 async fn test_a_failed_post_does_not_stop_the_other_organizations() {
329 let failing = organization(OrganizationUserType::Owner);
330 let succeeding = organization(OrganizationUserType::Admin);
331 let (failing_id, succeeding_id) = (failing.details.id, succeeding.details.id);
332
333 let client = client(
334 &[failing, succeeding],
335 &[failing_id, succeeding_id],
336 |mock| {
337 mock.organization_keys_api
338 .expect_set_organization_key_id()
339 .withf(move |org, _body| OrganizationId::new(*org) == failing_id)
340 .returning(|_org, _body| {
341 Err(bitwarden_api_api::apis::Error::Io(std::io::Error::other(
342 "connection reset",
343 )))
344 })
345 .once();
346 mock.organization_keys_api
347 .expect_set_organization_key_id()
348 .withf(move |org, _body| OrganizationId::new(*org) == succeeding_id)
349 .returning(|_org, _body| Ok(()))
350 .once();
351 },
352 )
353 .await;
354
355 assert!(matches!(
356 client.organization_key_id_backfill().await,
357 Err(OrganizationKeyIdBackfillError::Api)
358 ));
359 }
360
361 #[tokio::test]
362 async fn test_errors_without_an_organization_repository() {
363 let client = OrganizationCryptoManagementClient {
364 repository: None,
365 ..client(&[], &[], expect_no_posts).await
366 };
367
368 assert!(matches!(
369 client.organization_key_ids_needing_backfill().await,
370 Err(OrganizationKeyIdBackfillError::OrganizationStateNotAvailable(_))
371 ));
372 assert!(matches!(
373 client.organization_key_id_backfill().await,
374 Err(OrganizationKeyIdBackfillError::OrganizationStateNotAvailable(_))
375 ));
376 }
377}