Skip to main content

bitwarden_send/
get_list.rs

1use bitwarden_core::{MissingFieldError, key_management::KeyIds};
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::ItemNotFoundError, send_client::SendClient};
10
11#[allow(missing_docs)]
12#[bitwarden_error(flat)]
13#[derive(Debug, Error)]
14pub enum GetSendError {
15    #[error(transparent)]
16    ItemNotFound(#[from] ItemNotFoundError),
17    #[error(transparent)]
18    Crypto(#[from] CryptoError),
19    #[error(transparent)]
20    MissingField(#[from] MissingFieldError),
21    #[error(transparent)]
22    Repository(#[from] RepositoryError),
23}
24
25async fn get_send(
26    store: &KeyStore<KeyIds>,
27    repository: &dyn Repository<Send>,
28    id: SendId,
29) -> Result<SendView, GetSendError> {
30    let send = repository.get(id).await?.ok_or(ItemNotFoundError)?;
31
32    Ok(store.decrypt(&send)?)
33}
34
35async fn list_sends(
36    store: &KeyStore<KeyIds>,
37    repository: &dyn Repository<Send>,
38) -> Result<Vec<SendView>, GetSendError> {
39    let sends = repository.list().await?;
40    let views = store.decrypt_list(&sends)?;
41    Ok(views)
42}
43
44#[cfg_attr(feature = "wasm", wasm_bindgen)]
45impl SendClient {
46    /// Get all sends from state and decrypt them to a list of [SendView].
47    pub async fn list(&self) -> Result<Vec<SendView>, GetSendError> {
48        let key_store = self.client.internal.get_key_store();
49        let repository = self.get_repository()?;
50
51        list_sends(key_store, repository.as_ref()).await
52    }
53
54    /// Get a specific [Send] by its ID from state and decrypt it to a [SendView].
55    pub async fn get(&self, send_id: SendId) -> Result<SendView, GetSendError> {
56        let key_store = self.client.internal.get_key_store();
57        let repository = self.get_repository()?;
58
59        get_send(key_store, repository.as_ref(), send_id).await
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use bitwarden_core::key_management::SymmetricKeyId;
66    use bitwarden_crypto::SymmetricKeyAlgorithm;
67    use bitwarden_test::MemoryRepository;
68    use uuid::uuid;
69
70    use super::*;
71    use crate::{AuthType, SendTextView, SendType, SendView};
72
73    #[tokio::test]
74    async fn test_get_send() {
75        let store: KeyStore<KeyIds> = KeyStore::default();
76        {
77            let mut ctx = store.context_mut();
78            let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
79            ctx.persist_symmetric_key(local_key_id, SymmetricKeyId::User)
80                .unwrap();
81        }
82
83        let send_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
84
85        // Create and store a send
86        let repository = MemoryRepository::<Send>::default();
87        let send_view = SendView {
88            id: None,
89            access_id: None,
90            name: "Test Send".to_string(),
91            notes: Some("Test notes".to_string()),
92            key: None,
93            new_password: None,
94            has_password: false,
95            r#type: SendType::Text,
96            file: None,
97            text: Some(SendTextView {
98                text: Some("Secret text".to_string()),
99                hidden: false,
100            }),
101            max_access_count: None,
102            access_count: 0,
103            disabled: false,
104            hide_email: false,
105            revision_date: "2025-01-01T00:00:00Z".parse().unwrap(),
106            deletion_date: "2025-01-10T00:00:00Z".parse().unwrap(),
107            expiration_date: None,
108            emails: Vec::new(),
109            auth_type: AuthType::None,
110        };
111        let mut send = store.encrypt(send_view).unwrap();
112        send.id = Some(crate::send::SendId::new(send_id));
113        repository.set(SendId::new(send_id), send).await.unwrap();
114
115        // Test getting the send
116        let result = get_send(&store, &repository, SendId::new(send_id))
117            .await
118            .unwrap();
119
120        assert_eq!(result.id, Some(crate::send::SendId::new(send_id)));
121        assert_eq!(result.name, "Test Send");
122        assert_eq!(result.notes, Some("Test notes".to_string()));
123        assert_eq!(
124            result.text,
125            Some(SendTextView {
126                text: Some("Secret text".to_string()),
127                hidden: false,
128            })
129        );
130    }
131
132    #[tokio::test]
133    async fn test_get_send_not_found() {
134        let store: KeyStore<KeyIds> = KeyStore::default();
135        {
136            let mut ctx = store.context_mut();
137            let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
138            ctx.persist_symmetric_key(local_key_id, SymmetricKeyId::User)
139                .unwrap();
140        }
141
142        let send_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
143        let repository = MemoryRepository::<Send>::default();
144
145        // Try to get a send that doesn't exist
146        let result = get_send(&store, &repository, SendId::new(send_id)).await;
147
148        assert!(result.is_err());
149        assert!(matches!(result.unwrap_err(), GetSendError::ItemNotFound(_)));
150    }
151
152    #[tokio::test]
153    async fn test_list_sends() {
154        let store: KeyStore<KeyIds> = KeyStore::default();
155        {
156            let mut ctx = store.context_mut();
157            let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
158            ctx.persist_symmetric_key(local_key_id, SymmetricKeyId::User)
159                .unwrap();
160        }
161
162        let repository = MemoryRepository::<Send>::default();
163
164        // Create and store multiple sends
165        let send_id_1 = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
166        let send_view_1 = SendView {
167            id: None,
168            access_id: None,
169            name: "Send 1".to_string(),
170            notes: None,
171            key: None,
172            new_password: None,
173            has_password: false,
174            r#type: SendType::Text,
175            file: None,
176            text: Some(SendTextView {
177                text: Some("Text 1".to_string()),
178                hidden: false,
179            }),
180            max_access_count: None,
181            access_count: 0,
182            disabled: false,
183            hide_email: false,
184            revision_date: "2025-01-01T00:00:00Z".parse().unwrap(),
185            deletion_date: "2025-01-10T00:00:00Z".parse().unwrap(),
186            expiration_date: None,
187            emails: Vec::new(),
188            auth_type: AuthType::None,
189        };
190        let mut send_1 = store.encrypt(send_view_1).unwrap();
191        send_1.id = Some(crate::send::SendId::new(send_id_1));
192        repository
193            .set(SendId::new(send_id_1), send_1)
194            .await
195            .unwrap();
196
197        let send_id_2 = uuid!("36afb22c-9c95-4db5-8bac-c21cb204a3f2");
198        let send_view_2 = SendView {
199            id: None,
200            access_id: None,
201            name: "Send 2".to_string(),
202            notes: None,
203            key: None,
204            new_password: None,
205            has_password: false,
206            r#type: SendType::Text,
207            file: None,
208            text: Some(SendTextView {
209                text: Some("Text 2".to_string()),
210                hidden: false,
211            }),
212            max_access_count: None,
213            access_count: 0,
214            disabled: false,
215            hide_email: false,
216            revision_date: "2025-01-02T00:00:00Z".parse().unwrap(),
217            deletion_date: "2025-01-11T00:00:00Z".parse().unwrap(),
218            expiration_date: None,
219            emails: Vec::new(),
220            auth_type: AuthType::None,
221        };
222        let mut send_2 = store.encrypt(send_view_2).unwrap();
223        send_2.id = Some(crate::send::SendId::new(send_id_2));
224        repository
225            .set(SendId::new(send_id_2), send_2)
226            .await
227            .unwrap();
228
229        // Test listing all sends
230        let result = list_sends(&store, &repository).await.unwrap();
231
232        assert_eq!(result.len(), 2);
233
234        // Find sends by name (order may vary)
235        let send1 = result.iter().find(|s| s.name == "Send 1").unwrap();
236        let send2 = result.iter().find(|s| s.name == "Send 2").unwrap();
237
238        assert_eq!(send1.id, Some(crate::send::SendId::new(send_id_1)));
239        assert_eq!(send2.id, Some(crate::send::SendId::new(send_id_2)));
240    }
241
242    #[tokio::test]
243    async fn test_list_sends_empty() {
244        let store: KeyStore<KeyIds> = KeyStore::default();
245        {
246            let mut ctx = store.context_mut();
247            let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
248            ctx.persist_symmetric_key(local_key_id, SymmetricKeyId::User)
249                .unwrap();
250        }
251
252        let repository = MemoryRepository::<Send>::default();
253
254        // Test listing when repository is empty
255        let result = list_sends(&store, &repository).await.unwrap();
256
257        assert_eq!(result.len(), 0);
258    }
259}