Skip to main content

bitwarden_send/
remove_password.rs

1use bitwarden_core::{ApiError, MissingFieldError, key_management::KeySlotIds, require};
2use bitwarden_crypto::{CryptoError, KeyStore};
3use bitwarden_error::bitwarden_error;
4use bitwarden_state::repository::{Repository, RepositoryError};
5use thiserror::Error;
6
7use crate::{Send, SendId, SendView, error::SendParseError, send_client::SendClient};
8
9#[allow(missing_docs)]
10#[bitwarden_error(flat)]
11#[derive(Debug, Error)]
12pub enum RemoveSendPasswordError {
13    #[error(transparent)]
14    Api(#[from] ApiError),
15    #[error(transparent)]
16    Crypto(#[from] CryptoError),
17    #[error(transparent)]
18    MissingField(#[from] MissingFieldError),
19    #[error(transparent)]
20    Repository(#[from] RepositoryError),
21    #[error(transparent)]
22    SendParse(#[from] SendParseError),
23}
24
25async fn remove_send_password<R: Repository<Send> + ?Sized>(
26    key_store: &KeyStore<KeySlotIds>,
27    api_client: &bitwarden_api_api::apis::ApiClient,
28    repository: &R,
29    send_id: SendId,
30) -> Result<SendView, RemoveSendPasswordError> {
31    let resp = api_client
32        .sends_api()
33        .put_remove_password(&send_id.to_string())
34        .await?;
35
36    let send: Send = resp.try_into()?;
37
38    repository.set(require!(send.id), send.clone()).await?;
39
40    Ok(key_store.decrypt(&send)?)
41}
42
43#[bitwarden_ffi::wasm_export]
44impl SendClient {
45    /// Remove the password from a [Send], saving the updated state to the server and local state.
46    pub async fn remove_password(
47        &self,
48        send_id: SendId,
49    ) -> Result<SendView, RemoveSendPasswordError> {
50        let key_store = self.client.internal.get_key_store();
51        let config = self.client.internal.get_api_configurations();
52        let repository = self.get_repository()?;
53
54        remove_send_password(key_store, &config.api_client, repository.as_ref(), send_id).await
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use bitwarden_api_api::{apis::ApiClient, models::SendResponseModel};
61    use bitwarden_core::key_management::{KeySlotIds, SymmetricKeySlotId};
62    use bitwarden_crypto::{KeyStore, SymmetricKeyAlgorithm};
63    use bitwarden_test::MemoryRepository;
64    use uuid::uuid;
65
66    use super::*;
67    use crate::{AuthType, Send, SendId, SendTextView, SendType, SendView};
68
69    async fn make_store_with_send(
70        send_id: uuid::Uuid,
71    ) -> (KeyStore<KeySlotIds>, MemoryRepository<Send>) {
72        let store: KeyStore<KeySlotIds> = KeyStore::default();
73        {
74            let mut ctx = store.context_mut();
75            let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
76            ctx.persist_symmetric_key(local_key_id, SymmetricKeySlotId::User)
77                .unwrap();
78        }
79
80        let repository = MemoryRepository::<Send>::default();
81        let send_view = SendView {
82            id: None,
83            access_id: None,
84            name: "Test Send".to_string(),
85            notes: None,
86            key: None,
87            new_password: None,
88            has_password: false,
89            r#type: SendType::Text,
90            file: None,
91            text: Some(SendTextView {
92                text: Some("Secret text".to_string()),
93                hidden: false,
94            }),
95            data: None,
96            max_access_count: None,
97            access_count: 0,
98            disabled: false,
99            hide_email: false,
100            revision_date: "2025-01-01T00:00:00Z".parse().unwrap(),
101            deletion_date: "2025-01-10T00:00:00Z".parse().unwrap(),
102            expiration_date: None,
103            emails: Vec::new(),
104            auth_type: AuthType::None,
105        };
106        let mut send = store.encrypt(send_view).unwrap();
107        send.id = Some(SendId::new(send_id));
108        repository.set(SendId::new(send_id), send).await.unwrap();
109
110        (store, repository)
111    }
112
113    #[tokio::test]
114    async fn test_remove_send_password() {
115        let send_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
116        let (store, repository) = make_store_with_send(send_id).await;
117
118        // Get the encrypted fields from the stored send so the mock can return a realistic response
119        let stored = repository.get(SendId::new(send_id)).await.unwrap().unwrap();
120        let stored_name = stored.name.to_string();
121        let stored_key = stored.key.to_string();
122        let stored_deletion_date = stored.deletion_date.to_rfc3339();
123
124        let api_client = ApiClient::new_mocked(move |mock| {
125            let name = stored_name.clone();
126            let key = stored_key.clone();
127            let deletion_date = stored_deletion_date.clone();
128            mock.sends_api
129                .expect_put_remove_password()
130                .returning(move |_id| {
131                    Ok(SendResponseModel {
132                        id: Some(send_id),
133                        name: Some(name.clone()),
134                        revision_date: Some("2025-01-02T00:00:00Z".to_string()),
135                        object: Some("send".to_string()),
136                        access_id: None,
137                        r#type: Some(bitwarden_api_api::models::SendType::Text),
138                        auth_type: Some(bitwarden_api_api::models::AuthType::None),
139                        notes: None,
140                        file: None,
141                        text: None,
142                        data: None,
143                        key: Some(key.clone()),
144                        max_access_count: None,
145                        access_count: Some(0),
146                        password: None,
147                        emails: None,
148                        disabled: Some(false),
149                        expiration_date: None,
150                        deletion_date: Some(deletion_date.clone()),
151                        hide_email: Some(false),
152                    })
153                })
154                .once();
155        });
156
157        let result =
158            remove_send_password(&store, &api_client, &repository, SendId::new(send_id)).await;
159
160        assert!(result.is_ok());
161        let view = result.unwrap();
162        assert_eq!(view.id, Some(SendId::new(send_id)));
163        assert!(!view.has_password);
164        assert_eq!(view.auth_type, AuthType::None);
165    }
166
167    #[tokio::test]
168    async fn test_remove_send_password_http_error() {
169        let send_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
170        let (store, repository) = make_store_with_send(send_id).await;
171
172        let api_client = ApiClient::new_mocked(move |mock| {
173            mock.sends_api
174                .expect_put_remove_password()
175                .returning(move |_id| {
176                    Err(bitwarden_api_api::ApiError::Io(std::io::Error::other(
177                        "Simulated error",
178                    )))
179                })
180                .once();
181        });
182
183        let result =
184            remove_send_password(&store, &api_client, &repository, SendId::new(send_id)).await;
185
186        assert!(result.is_err());
187        assert!(matches!(
188            result.unwrap_err(),
189            RemoveSendPasswordError::Api(_)
190        ));
191    }
192}