Skip to main content

bitwarden_vault/cipher/cipher_client/admin/
delete.rs

1use bitwarden_api_api::models::CipherBulkDeleteRequestModel;
2use bitwarden_core::{ApiError, OrganizationId};
3use bitwarden_error::bitwarden_error;
4use thiserror::Error;
5
6use crate::{CipherId, cipher_client::admin::CipherAdminClient};
7
8#[allow(missing_docs)]
9#[bitwarden_error(flat)]
10#[derive(Debug, Error)]
11/// Errors that can occur when deleting ciphers as an admin.
12pub enum DeleteCipherAdminError {
13    // ApiError is incompatible with wasm_bindgen, so we wrap it in this enum
14    // for wasm_bindgen compatibility.
15    #[error(transparent)]
16    Api(#[from] ApiError),
17}
18
19async fn delete_cipher(
20    cipher_id: CipherId,
21    api_client: &bitwarden_api_api::apis::ApiClient,
22) -> Result<(), ApiError> {
23    let api = api_client.ciphers_api();
24    api.delete_admin(cipher_id.into()).await?;
25    Ok(())
26}
27
28async fn delete_ciphers_many(
29    cipher_ids: Vec<CipherId>,
30    organization_id: OrganizationId,
31    api_client: &bitwarden_api_api::apis::ApiClient,
32) -> Result<(), ApiError> {
33    let api = api_client.ciphers_api();
34
35    api.delete_many_admin(Some(CipherBulkDeleteRequestModel {
36        ids: cipher_ids.iter().map(|id| id.to_string()).collect(),
37        organization_id: Some(organization_id.to_string()),
38    }))
39    .await?;
40
41    Ok(())
42}
43
44async fn soft_delete(
45    cipher_id: CipherId,
46    api_client: &bitwarden_api_api::apis::ApiClient,
47) -> Result<(), ApiError> {
48    let api = api_client.ciphers_api();
49    api.put_delete_admin(cipher_id.into()).await?;
50    Ok(())
51}
52
53async fn soft_delete_many(
54    cipher_ids: Vec<CipherId>,
55    organization_id: OrganizationId,
56    api_client: &bitwarden_api_api::apis::ApiClient,
57) -> Result<(), ApiError> {
58    let api = api_client.ciphers_api();
59
60    api.put_delete_many_admin(Some(CipherBulkDeleteRequestModel {
61        ids: cipher_ids.iter().map(|id| id.to_string()).collect(),
62        organization_id: Some(organization_id.to_string()),
63    }))
64    .await?;
65    Ok(())
66}
67
68#[bitwarden_ffi::wasm_export]
69impl CipherAdminClient {
70    /// Deletes the Cipher with the matching CipherId from the server, using the admin endpoint.
71    /// Affects server data only, does not modify local state.
72    pub async fn delete(&self, cipher_id: CipherId) -> Result<(), DeleteCipherAdminError> {
73        Ok(delete_cipher(cipher_id, &self.api_configurations.api_client).await?)
74    }
75
76    /// Soft-deletes the Cipher with the matching CipherId from the server, using the admin
77    /// endpoint. Affects server data only, does not modify local state.
78    pub async fn soft_delete(&self, cipher_id: CipherId) -> Result<(), DeleteCipherAdminError> {
79        Ok(soft_delete(cipher_id, &self.api_configurations.api_client).await?)
80    }
81
82    /// Deletes all Cipher objects with a matching CipherId from the server, using the admin
83    /// endpoint. Affects server data only, does not modify local state.
84    pub async fn delete_many(
85        &self,
86        cipher_ids: Vec<CipherId>,
87        organization_id: OrganizationId,
88    ) -> Result<(), DeleteCipherAdminError> {
89        Ok(delete_ciphers_many(
90            cipher_ids,
91            organization_id,
92            &self.api_configurations.api_client,
93        )
94        .await?)
95    }
96
97    /// Soft-deletes all Cipher objects for the given CipherIds from the server, using the admin
98    /// endpoint. Affects server data only, does not modify local state.
99    pub async fn soft_delete_many(
100        &self,
101        cipher_ids: Vec<CipherId>,
102        organization_id: OrganizationId,
103    ) -> Result<(), DeleteCipherAdminError> {
104        Ok(soft_delete_many(
105            cipher_ids,
106            organization_id,
107            &self.api_configurations.api_client,
108        )
109        .await?)
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    const TEST_CIPHER_ID: &str = "5faa9684-c793-4a2d-8a12-b33900187097";
118    const TEST_CIPHER_ID_2: &str = "6faa9684-c793-4a2d-8a12-b33900187098";
119    const TEST_ORG_ID: &str = "1bc9ac1e-f5aa-45f2-94bf-b181009709b8";
120
121    #[tokio::test]
122    async fn test_delete_as_admin() {
123        delete_cipher(
124            TEST_CIPHER_ID.parse().unwrap(),
125            &bitwarden_api_api::apis::ApiClient::new_mocked(|mock| {
126                mock.ciphers_api.expect_delete_admin().returning(move |id| {
127                    assert_eq!(&id.to_string(), TEST_CIPHER_ID);
128                    Ok(())
129                });
130            }),
131        )
132        .await
133        .unwrap()
134    }
135
136    #[tokio::test]
137    async fn test_soft_delete_as_admin() {
138        soft_delete(
139            TEST_CIPHER_ID.parse().unwrap(),
140            &bitwarden_api_api::apis::ApiClient::new_mocked(|mock| {
141                mock.ciphers_api
142                    .expect_put_delete_admin()
143                    .returning(move |id| {
144                        assert_eq!(&id.to_string(), TEST_CIPHER_ID);
145                        Ok(())
146                    });
147            }),
148        )
149        .await
150        .unwrap()
151    }
152
153    #[tokio::test]
154    async fn test_delete_many_as_admin() {
155        delete_ciphers_many(
156            vec![
157                TEST_CIPHER_ID.parse().unwrap(),
158                TEST_CIPHER_ID_2.parse().unwrap(),
159            ],
160            TEST_ORG_ID.parse().unwrap(),
161            &bitwarden_api_api::apis::ApiClient::new_mocked(|mock| {
162                mock.ciphers_api
163                    .expect_delete_many_admin()
164                    .returning(move |request| {
165                        let CipherBulkDeleteRequestModel {
166                            ids,
167                            organization_id,
168                        } = request.unwrap();
169
170                        assert_eq!(
171                            ids,
172                            vec![TEST_CIPHER_ID.to_string(), TEST_CIPHER_ID_2.to_string(),],
173                        );
174                        assert_eq!(organization_id, Some(TEST_ORG_ID.to_string()));
175                        Ok(())
176                    });
177            }),
178        )
179        .await
180        .unwrap()
181    }
182
183    #[tokio::test]
184    async fn test_soft_delete_many_as_admin() {
185        soft_delete_many(
186            vec![
187                TEST_CIPHER_ID.parse().unwrap(),
188                TEST_CIPHER_ID_2.parse().unwrap(),
189            ],
190            TEST_ORG_ID.parse().unwrap(),
191            &bitwarden_api_api::apis::ApiClient::new_mocked(|mock| {
192                mock.ciphers_api
193                    .expect_put_delete_many_admin()
194                    .returning(move |request| {
195                        let CipherBulkDeleteRequestModel {
196                            ids,
197                            organization_id,
198                        } = request.unwrap();
199
200                        assert_eq!(
201                            ids,
202                            vec![TEST_CIPHER_ID.to_string(), TEST_CIPHER_ID_2.to_string()],
203                        );
204                        assert_eq!(organization_id, Some(TEST_ORG_ID.to_string()));
205                        Ok(())
206                    });
207            }),
208        )
209        .await
210        .unwrap()
211    }
212}