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