Skip to main content

bitwarden_organization_crypto_management/
key_id_backfill.rs

1//! Record organization key ids to the server for organizations where they were never captured.
2//!
3//! Key ids were introduced after organization keys, so organizations created before the server
4//! started tracking them hold a key whose id the server does not know about. Every member derives
5//! the same id from the organization key, but only Owners and Admins can record it.
6
7use 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/// Errors returned by the organization key id backfill.
19#[derive(Debug, Error)]
20#[bitwarden_error(flat)]
21pub enum OrganizationKeyIdBackfillError {
22    /// The organization repository is not registered or could not be read.
23    #[error("Organization state is not available")]
24    OrganizationStateNotAvailable(#[from] RepositoryError),
25    /// One or more API calls failed. The other organizations were still attempted.
26    #[error("API call failed during organization key id backfill")]
27    Api,
28}
29
30#[bitwarden_ffi::wasm_export]
31impl OrganizationCryptoManagementClient {
32    /// Returns the organizations whose key id the server does not have, and that this account can
33    /// record the key id for.
34    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    /// Records the key id of each organization key that the server does not have.
46    ///
47    /// A failure for one organization does not stop the others. Returns
48    /// [`OrganizationKeyIdBackfillError::Api`] when any organization failed. Makes no API calls
49    /// when no organization needs a backfill.
50    ///
51    /// Requires the client to be unlocked so the organization keys are available in memory.
52    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                // A 409 means the server holds a different key id for this organization.
70                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    /// Returns the organizations that need a backfill, each with the id of its organization key in
85    /// the key store.
86    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            // Only backfill if the org key id is not set on the server
95            .filter(|o| o.details.key_id.is_none())
96            // The server only authorizes confirmed Owners and Admins. It refuses custom users even
97            // with every permission, and provider users who are not members.
98            .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            // Without the organization key there is no key id to record.
107            .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    /// A client holding an organization key for each of `organization_ids`, with
146    /// `organizations` in its state and its API calls served by the configured mock.
147    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    /// An API configuration that fails the test if the key id is posted.
183    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        // The posted id is checked against the key store's below, so the mock only records it.
205        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    /// Asserts that no organization needs a backfill, and that the backfill posts nothing.
250    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        // The server rejects custom members, even with every permission.
278        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}