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