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