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