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 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 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 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 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 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 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 let result = list_sends(&store, &repository).await.unwrap();
232
233 assert_eq!(result.len(), 2);
234
235 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 let result = list_sends(&store, &repository).await.unwrap();
257
258 assert_eq!(result.len(), 0);
259 }
260}