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