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#[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.client.internal.get_api_configurations().api_client,
78        )
79        .await?)
80    }
81
82    /// Soft-deletes the Cipher with the matching CipherId from the server, using the admin
83    /// endpoint. Affects server data only, does not modify local state.
84    pub async fn soft_delete(&self, cipher_id: CipherId) -> Result<(), DeleteCipherAdminError> {
85        Ok(soft_delete(
86            cipher_id,
87            &self.client.internal.get_api_configurations().api_client,
88        )
89        .await?)
90    }
91
92    /// Deletes all Cipher objects with a matching CipherId from the server, using the admin
93    /// endpoint. Affects server data only, does not modify local state.
94    pub async fn delete_many(
95        &self,
96        cipher_ids: Vec<CipherId>,
97        organization_id: OrganizationId,
98    ) -> Result<(), DeleteCipherAdminError> {
99        Ok(delete_ciphers_many(
100            cipher_ids,
101            organization_id,
102            &self.client.internal.get_api_configurations().api_client,
103        )
104        .await?)
105    }
106
107    /// Soft-deletes all Cipher objects for the given CipherIds from the server, using the admin
108    /// endpoint. Affects server data only, does not modify local state.
109    pub async fn soft_delete_many(
110        &self,
111        cipher_ids: Vec<CipherId>,
112        organization_id: OrganizationId,
113    ) -> Result<(), DeleteCipherAdminError> {
114        Ok(soft_delete_many(
115            cipher_ids,
116            organization_id,
117            &self.client.internal.get_api_configurations().api_client,
118        )
119        .await?)
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    const TEST_CIPHER_ID: &str = "5faa9684-c793-4a2d-8a12-b33900187097";
128    const TEST_CIPHER_ID_2: &str = "6faa9684-c793-4a2d-8a12-b33900187098";
129    const TEST_ORG_ID: &str = "1bc9ac1e-f5aa-45f2-94bf-b181009709b8";
130
131    #[tokio::test]
132    async fn test_delete_as_admin() {
133        delete_cipher(
134            TEST_CIPHER_ID.parse().unwrap(),
135            &bitwarden_api_api::apis::ApiClient::new_mocked(|mock| {
136                mock.ciphers_api.expect_delete_admin().returning(move |id| {
137                    assert_eq!(&id.to_string(), TEST_CIPHER_ID);
138                    Ok(())
139                });
140            }),
141        )
142        .await
143        .unwrap()
144    }
145
146    #[tokio::test]
147    async fn test_soft_delete_as_admin() {
148        soft_delete(
149            TEST_CIPHER_ID.parse().unwrap(),
150            &bitwarden_api_api::apis::ApiClient::new_mocked(|mock| {
151                mock.ciphers_api
152                    .expect_put_delete_admin()
153                    .returning(move |id| {
154                        assert_eq!(&id.to_string(), TEST_CIPHER_ID);
155                        Ok(())
156                    });
157            }),
158        )
159        .await
160        .unwrap()
161    }
162
163    #[tokio::test]
164    async fn test_delete_many_as_admin() {
165        delete_ciphers_many(
166            vec![
167                TEST_CIPHER_ID.parse().unwrap(),
168                TEST_CIPHER_ID_2.parse().unwrap(),
169            ],
170            TEST_ORG_ID.parse().unwrap(),
171            &bitwarden_api_api::apis::ApiClient::new_mocked(|mock| {
172                mock.ciphers_api
173                    .expect_delete_many_admin()
174                    .returning(move |request| {
175                        let CipherBulkDeleteRequestModel {
176                            ids,
177                            organization_id,
178                        } = request.unwrap();
179
180                        assert_eq!(
181                            ids,
182                            vec![TEST_CIPHER_ID.to_string(), TEST_CIPHER_ID_2.to_string(),],
183                        );
184                        assert_eq!(organization_id, Some(TEST_ORG_ID.to_string()));
185                        Ok(())
186                    });
187            }),
188        )
189        .await
190        .unwrap()
191    }
192
193    #[tokio::test]
194    async fn test_soft_delete_many_as_admin() {
195        soft_delete_many(
196            vec![
197                TEST_CIPHER_ID.parse().unwrap(),
198                TEST_CIPHER_ID_2.parse().unwrap(),
199            ],
200            TEST_ORG_ID.parse().unwrap(),
201            &bitwarden_api_api::apis::ApiClient::new_mocked(|mock| {
202                mock.ciphers_api
203                    .expect_put_delete_many_admin()
204                    .returning(move |request| {
205                        let CipherBulkDeleteRequestModel {
206                            ids,
207                            organization_id,
208                        } = request.unwrap();
209
210                        assert_eq!(
211                            ids,
212                            vec![TEST_CIPHER_ID.to_string(), TEST_CIPHER_ID_2.to_string()],
213                        );
214                        assert_eq!(organization_id, Some(TEST_ORG_ID.to_string()));
215                        Ok(())
216                    });
217            }),
218        )
219        .await
220        .unwrap()
221    }
222}