koprogo_api/application/use_cases/
gdpr_use_cases.rs1use crate::application::dto::{GdprEraseResponseDto, GdprExportResponseDto};
2use crate::application::ports::{GdprRepository, UserRepository};
3use chrono::Utc;
4use std::sync::Arc;
5use uuid::Uuid;
6
7pub struct GdprUseCases {
10 gdpr_repository: Arc<dyn GdprRepository>,
11 user_repository: Arc<dyn UserRepository>,
12}
13
14impl GdprUseCases {
15 pub fn new(
16 gdpr_repository: Arc<dyn GdprRepository>,
17 user_repository: Arc<dyn UserRepository>,
18 ) -> Self {
19 Self {
20 gdpr_repository,
21 user_repository,
22 }
23 }
24
25 pub async fn export_user_data(
40 &self,
41 user_id: Uuid,
42 requesting_user_id: Uuid,
43 organization_id: Option<Uuid>,
44 ) -> Result<GdprExportResponseDto, String> {
45 if user_id != requesting_user_id && organization_id.is_some() {
48 return Err("Unauthorized: You can only export your own data".to_string());
49 }
50
51 let is_anonymized = self.gdpr_repository.is_user_anonymized(user_id).await?;
53 if is_anonymized {
54 return Err("User data has been anonymized and cannot be exported".to_string());
55 }
56
57 let export = self
59 .gdpr_repository
60 .aggregate_user_data(user_id, organization_id)
61 .await?;
62
63 Ok(GdprExportResponseDto::from(export))
65 }
66
67 pub async fn erase_user_data(
97 &self,
98 user_id: Uuid,
99 requesting_user_id: Uuid,
100 organization_id: Option<Uuid>,
101 password: Option<&str>,
102 ) -> Result<GdprEraseResponseDto, String> {
103 if user_id != requesting_user_id && organization_id.is_some() {
105 return Err("Unauthorized: You can only erase your own data".to_string());
106 }
107
108 if user_id == requesting_user_id {
110 let password = password.ok_or_else(|| "Password required".to_string())?;
111 let user = self
112 .user_repository
113 .find_by_id(user_id)
114 .await?
115 .ok_or_else(|| "User not found".to_string())?;
116 let valid = bcrypt::verify(password, &user.password_hash)
117 .map_err(|e| format!("Password verification failed: {e}"))?;
118 if !valid {
119 return Err("Invalid password".to_string());
120 }
121 }
122
123 let is_anonymized = self.gdpr_repository.is_user_anonymized(user_id).await?;
125 if is_anonymized {
126 return Err("User data is already anonymized".to_string());
127 }
128
129 let holds = self.gdpr_repository.check_legal_holds(user_id).await?;
131 if !holds.is_empty() {
132 return Err(format!(
133 "Cannot erase data due to legal holds: {}",
134 holds.join(", ")
135 ));
136 }
137
138 let user_data = self
140 .gdpr_repository
141 .aggregate_user_data(user_id, organization_id)
142 .await?;
143 let user_email = user_data.user_data.email.clone();
144 let user_first_name = user_data.user_data.first_name.clone();
145 let user_last_name = user_data.user_data.last_name.clone();
146
147 let owner_ids = self
149 .gdpr_repository
150 .find_owner_ids_by_user(user_id, organization_id)
151 .await?;
152
153 self.gdpr_repository.anonymize_user(user_id).await?;
155
156 let mut owners_anonymized = 0;
158 for owner_id in &owner_ids {
159 match self.gdpr_repository.anonymize_owner(*owner_id).await {
160 Ok(_) => owners_anonymized += 1,
161 Err(e) => {
162 eprintln!("Warning: Failed to anonymize owner {}: {}", owner_id, e);
164 }
165 }
166 }
167
168 Ok(GdprEraseResponseDto {
169 success: true,
170 message: "Personal data has been successfully anonymized".to_string(),
171 anonymized_at: Utc::now().to_rfc3339(),
172 user_id: user_id.to_string(),
173 user_email,
174 user_first_name,
175 user_last_name,
176 owners_anonymized,
177 })
178 }
179
180 pub async fn can_erase_user(&self, user_id: Uuid) -> Result<bool, String> {
190 let holds = self.gdpr_repository.check_legal_holds(user_id).await?;
191 Ok(holds.is_empty())
192 }
193
194 pub async fn rectify_user_data(
213 &self,
214 user_id: Uuid,
215 requesting_user_id: Uuid,
216 email: Option<String>,
217 first_name: Option<String>,
218 last_name: Option<String>,
219 ) -> Result<(), String> {
220 if user_id != requesting_user_id {
222 return Err("Unauthorized: You can only rectify your own data".to_string());
224 }
225
226 let mut user = self
228 .user_repository
229 .find_by_id(user_id)
230 .await?
231 .ok_or_else(|| format!("User not found: {}", user_id))?;
232
233 user.rectify_data(email, first_name, last_name)?;
235
236 self.user_repository.update(&user).await?;
238
239 Ok(())
240 }
241
242 pub async fn restrict_user_processing(
261 &self,
262 user_id: Uuid,
263 requesting_user_id: Uuid,
264 ) -> Result<(), String> {
265 if user_id != requesting_user_id {
267 return Err("Unauthorized: You can only restrict your own data processing".to_string());
268 }
269
270 let mut user = self
272 .user_repository
273 .find_by_id(user_id)
274 .await?
275 .ok_or_else(|| format!("User not found: {}", user_id))?;
276
277 user.restrict_processing()?;
279
280 self.user_repository.update(&user).await?;
282
283 Ok(())
284 }
285
286 pub async fn unrestrict_user_processing(&self, user_id: Uuid) -> Result<(), String> {
299 let mut user = self
301 .user_repository
302 .find_by_id(user_id)
303 .await?
304 .ok_or_else(|| format!("User not found: {}", user_id))?;
305
306 user.unrestrict_processing();
308
309 self.user_repository.update(&user).await?;
311
312 Ok(())
313 }
314
315 pub async fn set_marketing_preference(
331 &self,
332 user_id: Uuid,
333 requesting_user_id: Uuid,
334 opt_out: bool,
335 ) -> Result<(), String> {
336 if user_id != requesting_user_id {
338 return Err(
339 "Unauthorized: You can only change your own marketing preferences".to_string(),
340 );
341 }
342
343 let mut user = self
345 .user_repository
346 .find_by_id(user_id)
347 .await?
348 .ok_or_else(|| format!("User not found: {}", user_id))?;
349
350 user.set_marketing_opt_out(opt_out);
352
353 self.user_repository.update(&user).await?;
355
356 Ok(())
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use crate::application::ports::gdpr_repository::MockGdprRepo;
364 use crate::application::ports::user_repository::MockUserRepo;
365
366 fn mock_user_repo_avec_mot_de_passe(user_id: Uuid, mot_de_passe: &str) -> MockUserRepo {
380 let hash = bcrypt::hash(mot_de_passe, bcrypt::DEFAULT_COST).expect("hachage de test");
381 let mut mock = MockUserRepo::new();
382 mock.expect_find_by_id().times(1).returning(move |id| {
383 assert_eq!(id, user_id, "l'effacement doit relire l'utilisateur ciblé");
384 let mut user = crate::domain::entities::User::new(
385 "test@example.com".to_string(),
386 hash.clone(),
387 "Test".to_string(),
388 "User".to_string(),
389 crate::domain::entities::UserRole::Owner,
390 None,
391 )
392 .expect("utilisateur de test valide");
393 user.id = id;
394 Ok(Some(user))
395 });
396 mock
397 }
398 use crate::domain::entities::gdpr_export::{GdprExport, UserData};
399 use chrono::Utc;
400
401 fn create_test_user_data(user_id: Uuid) -> UserData {
402 UserData {
403 id: user_id,
404 email: "test@example.com".to_string(),
405 first_name: "John".to_string(),
406 last_name: "Doe".to_string(),
407 organization_id: Some(Uuid::new_v4()),
408 is_active: true,
409 is_anonymized: false,
410 created_at: Utc::now(),
411 updated_at: Utc::now(),
412 }
413 }
414
415 #[tokio::test]
416 async fn test_export_user_data_success() {
417 let user_id = Uuid::new_v4();
418 let org_id = Uuid::new_v4();
419
420 let mut mock_repo = MockGdprRepo::new();
421 mock_repo
422 .expect_is_user_anonymized()
423 .times(1)
424 .returning(|_| Ok(false));
425 mock_repo
426 .expect_aggregate_user_data()
427 .times(1)
428 .returning(move |_, _| {
429 let user_data = create_test_user_data(user_id);
430 Ok(GdprExport::new(user_data))
431 });
432
433 let mock_user_repo = MockUserRepo::new();
434
435 let use_cases = GdprUseCases::new(Arc::new(mock_repo), Arc::new(mock_user_repo));
436 let result = use_cases
437 .export_user_data(user_id, user_id, Some(org_id))
438 .await;
439
440 assert!(result.is_ok());
441 let dto = result.unwrap();
442 assert_eq!(dto.user.email, "test@example.com");
443 }
444
445 #[tokio::test]
446 async fn test_export_user_data_unauthorized() {
447 let user_id = Uuid::new_v4();
448 let other_user_id = Uuid::new_v4();
449 let org_id = Uuid::new_v4();
450
451 let mock_repo = MockGdprRepo::new();
452 let mock_user_repo = MockUserRepo::new();
453
454 let use_cases = GdprUseCases::new(Arc::new(mock_repo), Arc::new(mock_user_repo));
455
456 let result = use_cases
457 .export_user_data(user_id, other_user_id, Some(org_id))
458 .await;
459
460 assert!(result.is_err());
461 assert!(result
462 .unwrap_err()
463 .contains("Unauthorized: You can only export your own data"));
464 }
465
466 #[tokio::test]
467 async fn test_export_anonymized_user_fails() {
468 let user_id = Uuid::new_v4();
469
470 let mut mock_repo = MockGdprRepo::new();
471 mock_repo
472 .expect_is_user_anonymized()
473 .times(1)
474 .returning(|_| Ok(true));
475
476 let mock_user_repo = MockUserRepo::new();
477
478 let use_cases = GdprUseCases::new(Arc::new(mock_repo), Arc::new(mock_user_repo));
479 let result = use_cases
480 .export_user_data(user_id, user_id, Some(Uuid::new_v4()))
481 .await;
482
483 assert!(result.is_err());
484 assert!(result
485 .unwrap_err()
486 .contains("User data has been anonymized"));
487 }
488
489 #[tokio::test]
490 async fn test_erase_user_data_success() {
491 let user_id = Uuid::new_v4();
492 let owner_id1 = Uuid::new_v4();
493 let owner_id2 = Uuid::new_v4();
494 let org_id = Uuid::new_v4();
495
496 let user_data = crate::domain::entities::gdpr_export::UserData {
498 id: user_id,
499 email: "test@example.com".to_string(),
500 first_name: "Test".to_string(),
501 last_name: "User".to_string(),
502 organization_id: Some(org_id),
503 is_active: true,
504 is_anonymized: false,
505 created_at: Utc::now(),
506 updated_at: Utc::now(),
507 };
508 let gdpr_export = crate::domain::entities::gdpr_export::GdprExport::new(user_data);
509
510 let mut mock_repo = MockGdprRepo::new();
511 mock_repo
512 .expect_is_user_anonymized()
513 .times(1)
514 .returning(|_| Ok(false));
515 mock_repo
516 .expect_check_legal_holds()
517 .times(1)
518 .returning(|_| Ok(vec![]));
519 mock_repo
520 .expect_aggregate_user_data()
521 .times(1)
522 .returning(move |_, _| Ok(gdpr_export.clone()));
523 mock_repo
524 .expect_find_owner_ids_by_user()
525 .times(1)
526 .returning(move |_, _| Ok(vec![owner_id1, owner_id2]));
527 mock_repo
528 .expect_anonymize_user()
529 .times(1)
530 .returning(|_| Ok(()));
531 mock_repo
532 .expect_anonymize_owner()
533 .times(2)
534 .returning(|_| Ok(()));
535
536 let mock_user_repo = mock_user_repo_avec_mot_de_passe(user_id, "password");
537
538 let use_cases = GdprUseCases::new(Arc::new(mock_repo), Arc::new(mock_user_repo));
539 let result = use_cases
540 .erase_user_data(user_id, user_id, Some(org_id), Some("password"))
541 .await;
542
543 assert!(result.is_ok());
544 let dto = result.unwrap();
545 assert!(dto.success);
546 assert_eq!(dto.owners_anonymized, 2);
547 assert_eq!(dto.user_email, "test@example.com");
548 assert_eq!(dto.user_first_name, "Test");
549 assert_eq!(dto.user_last_name, "User");
550 }
551
552 #[tokio::test]
553 async fn test_erase_user_data_unauthorized() {
554 let user_id = Uuid::new_v4();
555 let other_user_id = Uuid::new_v4();
556 let org_id = Uuid::new_v4();
557
558 let mock_repo = MockGdprRepo::new();
559 let mock_user_repo = MockUserRepo::new();
560
561 let use_cases = GdprUseCases::new(Arc::new(mock_repo), Arc::new(mock_user_repo));
562
563 let result = use_cases
564 .erase_user_data(user_id, other_user_id, Some(org_id), None)
565 .await;
566
567 assert!(result.is_err());
568 assert!(result
569 .unwrap_err()
570 .contains("Unauthorized: You can only erase your own data"));
571 }
572
573 #[tokio::test]
574 async fn test_erase_already_anonymized_user_fails() {
575 let user_id = Uuid::new_v4();
576
577 let mut mock_repo = MockGdprRepo::new();
578 mock_repo
579 .expect_is_user_anonymized()
580 .times(1)
581 .returning(|_| Ok(true));
582
583 let mock_user_repo = mock_user_repo_avec_mot_de_passe(user_id, "password");
584
585 let use_cases = GdprUseCases::new(Arc::new(mock_repo), Arc::new(mock_user_repo));
586 let result = use_cases
587 .erase_user_data(user_id, user_id, Some(Uuid::new_v4()), Some("password"))
588 .await;
589
590 assert!(result.is_err());
591 assert!(result.unwrap_err().contains("already anonymized"));
592 }
593
594 #[tokio::test]
595 async fn test_erase_with_legal_holds_fails() {
596 let user_id = Uuid::new_v4();
597
598 let mut mock_repo = MockGdprRepo::new();
599 mock_repo
600 .expect_is_user_anonymized()
601 .times(1)
602 .returning(|_| Ok(false));
603 mock_repo
604 .expect_check_legal_holds()
605 .times(1)
606 .returning(|_| Ok(vec!["Unpaid expenses".to_string()]));
607
608 let mock_user_repo = mock_user_repo_avec_mot_de_passe(user_id, "password");
609
610 let use_cases = GdprUseCases::new(Arc::new(mock_repo), Arc::new(mock_user_repo));
611 let result = use_cases
612 .erase_user_data(user_id, user_id, Some(Uuid::new_v4()), Some("password"))
613 .await;
614
615 assert!(result.is_err());
616 assert!(result.unwrap_err().contains("legal holds"));
617 }
618
619 #[tokio::test]
620 async fn test_can_erase_user_no_holds() {
621 let user_id = Uuid::new_v4();
622
623 let mut mock_repo = MockGdprRepo::new();
624 mock_repo
625 .expect_check_legal_holds()
626 .times(1)
627 .returning(|_| Ok(vec![]));
628
629 let mock_user_repo = MockUserRepo::new();
630
631 let use_cases = GdprUseCases::new(Arc::new(mock_repo), Arc::new(mock_user_repo));
632 let result = use_cases.can_erase_user(user_id).await;
633
634 assert!(result.is_ok());
635 assert!(result.unwrap());
636 }
637
638 #[tokio::test]
639 async fn test_can_erase_user_with_holds() {
640 let user_id = Uuid::new_v4();
641
642 let mut mock_repo = MockGdprRepo::new();
643 mock_repo
644 .expect_check_legal_holds()
645 .times(1)
646 .returning(|_| Ok(vec!["Unpaid expenses".to_string()]));
647
648 let mock_user_repo = MockUserRepo::new();
649
650 let use_cases = GdprUseCases::new(Arc::new(mock_repo), Arc::new(mock_user_repo));
651 let result = use_cases.can_erase_user(user_id).await;
652
653 assert!(result.is_ok());
654 assert!(!result.unwrap());
655 }
656}