Skip to main content

bitwarden_send/
get_list.rs

1use bitwarden_core::{MissingFieldError, key_management::KeySlotIds};
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<KeySlotIds>,
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<KeySlotIds>,
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::SymmetricKeySlotId;
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<KeySlotIds> = 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, SymmetricKeySlotId::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            data: None,
102            max_access_count: None,
103            access_count: 0,
104            disabled: false,
105            hide_email: false,
106            revision_date: "2025-01-01T00:00:00Z".parse().unwrap(),
107            deletion_date: "2025-01-10T00:00:00Z".parse().unwrap(),
108            expiration_date: None,
109            emails: Vec::new(),
110            auth_type: AuthType::None,
111        };
112        let mut send = store.encrypt(send_view).unwrap();
113        send.id = Some(crate::send::SendId::new(send_id));
114        repository.set(SendId::new(send_id), send).await.unwrap();
115
116        // Test getting the send
117        let result = get_send(&store, &repository, SendId::new(send_id))
118            .await
119            .unwrap();
120
121        assert_eq!(result.id, Some(crate::send::SendId::new(send_id)));
122        assert_eq!(result.name, "Test Send");
123        assert_eq!(result.notes, Some("Test notes".to_string()));
124        assert_eq!(
125            result.text,
126            Some(SendTextView {
127                text: Some("Secret text".to_string()),
128                hidden: false,
129            })
130        );
131    }
132
133    #[tokio::test]
134    async fn test_get_send_not_found() {
135        let store: KeyStore<KeySlotIds> = KeyStore::default();
136        {
137            let mut ctx = store.context_mut();
138            let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
139            ctx.persist_symmetric_key(local_key_id, SymmetricKeySlotId::User)
140                .unwrap();
141        }
142
143        let send_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
144        let repository = MemoryRepository::<Send>::default();
145
146        // Try to get a send that doesn't exist
147        let result = get_send(&store, &repository, SendId::new(send_id)).await;
148
149        assert!(result.is_err());
150        assert!(matches!(result.unwrap_err(), GetSendError::ItemNotFound(_)));
151    }
152
153    #[tokio::test]
154    async fn test_list_sends() {
155        let store: KeyStore<KeySlotIds> = KeyStore::default();
156        {
157            let mut ctx = store.context_mut();
158            let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
159            ctx.persist_symmetric_key(local_key_id, SymmetricKeySlotId::User)
160                .unwrap();
161        }
162
163        let repository = MemoryRepository::<Send>::default();
164
165        // Create and store multiple sends
166        let send_id_1 = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
167        let send_view_1 = SendView {
168            id: None,
169            access_id: None,
170            name: "Send 1".to_string(),
171            notes: None,
172            key: None,
173            new_password: None,
174            has_password: false,
175            r#type: SendType::Text,
176            file: None,
177            text: Some(SendTextView {
178                text: Some("Text 1".to_string()),
179                hidden: false,
180            }),
181            data: None,
182            max_access_count: None,
183            access_count: 0,
184            disabled: false,
185            hide_email: false,
186            revision_date: "2025-01-01T00:00:00Z".parse().unwrap(),
187            deletion_date: "2025-01-10T00:00:00Z".parse().unwrap(),
188            expiration_date: None,
189            emails: Vec::new(),
190            auth_type: AuthType::None,
191        };
192        let mut send_1 = store.encrypt(send_view_1).unwrap();
193        send_1.id = Some(crate::send::SendId::new(send_id_1));
194        repository
195            .set(SendId::new(send_id_1), send_1)
196            .await
197            .unwrap();
198
199        let send_id_2 = uuid!("36afb22c-9c95-4db5-8bac-c21cb204a3f2");
200        let send_view_2 = SendView {
201            id: None,
202            access_id: None,
203            name: "Send 2".to_string(),
204            notes: None,
205            key: None,
206            new_password: None,
207            has_password: false,
208            r#type: SendType::Text,
209            file: None,
210            text: Some(SendTextView {
211                text: Some("Text 2".to_string()),
212                hidden: false,
213            }),
214            data: None,
215            max_access_count: None,
216            access_count: 0,
217            disabled: false,
218            hide_email: false,
219            revision_date: "2025-01-02T00:00:00Z".parse().unwrap(),
220            deletion_date: "2025-01-11T00:00:00Z".parse().unwrap(),
221            expiration_date: None,
222            emails: Vec::new(),
223            auth_type: AuthType::None,
224        };
225        let mut send_2 = store.encrypt(send_view_2).unwrap();
226        send_2.id = Some(crate::send::SendId::new(send_id_2));
227        repository
228            .set(SendId::new(send_id_2), send_2)
229            .await
230            .unwrap();
231
232        // Test listing all sends
233        let result = list_sends(&store, &repository).await.unwrap();
234
235        assert_eq!(result.len(), 2);
236
237        // Find sends by name (order may vary)
238        let send1 = result.iter().find(|s| s.name == "Send 1").unwrap();
239        let send2 = result.iter().find(|s| s.name == "Send 2").unwrap();
240
241        assert_eq!(send1.id, Some(crate::send::SendId::new(send_id_1)));
242        assert_eq!(send2.id, Some(crate::send::SendId::new(send_id_2)));
243    }
244
245    #[tokio::test]
246    async fn test_list_sends_empty() {
247        let store: KeyStore<KeySlotIds> = KeyStore::default();
248        {
249            let mut ctx = store.context_mut();
250            let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
251            ctx.persist_symmetric_key(local_key_id, SymmetricKeySlotId::User)
252                .unwrap();
253        }
254
255        let repository = MemoryRepository::<Send>::default();
256
257        // Test listing when repository is empty
258        let result = list_sends(&store, &repository).await.unwrap();
259
260        assert_eq!(result.len(), 0);
261    }
262}