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