1use crate::application::dto::{
2 GdprActionResponse, GdprEraseRequestDto, GdprMarketingPreferenceRequest, GdprRectifyRequest,
3 GdprRestrictProcessingRequest,
4};
5use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
6use crate::infrastructure::web::classification_erreurs;
7use crate::infrastructure::web::{AppState, AuthenticatedUser};
8use actix_web::{delete, get, put, web, HttpRequest, HttpResponse, Responder};
9use chrono::Utc;
10use tokio::spawn;
11
12fn extract_ip_address(req: &HttpRequest) -> Option<String> {
14 req.headers()
16 .get("X-Forwarded-For")
17 .and_then(|h| h.to_str().ok())
18 .map(|s| s.split(',').next().unwrap_or("").trim().to_string())
19 .filter(|s| !s.is_empty())
20 .or_else(|| {
21 req.headers()
23 .get("X-Real-IP")
24 .and_then(|h| h.to_str().ok())
25 .map(|s| s.to_string())
26 })
27 .or_else(|| {
28 req.peer_addr().map(|addr| addr.ip().to_string())
30 })
31}
32
33fn extract_user_agent(req: &HttpRequest) -> Option<String> {
35 req.headers()
36 .get("User-Agent")
37 .and_then(|h| h.to_str().ok())
38 .map(|s| s.to_string())
39}
40
41#[utoipa::path(
51 get,
52 path = "/gdpr/export",
53 tag = "GDPR",
54 summary = "Export user personal data (Article 15 - Right to Access)",
55 responses(
56 (status = 200, description = "User data exported"),
57 (status = 401, description = "Unauthorized"),
58 (status = 403, description = "Forbidden"),
59 (status = 404, description = "User not found"),
60 (status = 410, description = "User already anonymized"),
61 (status = 500, description = "Internal server error"),
62 ),
63 security(("bearer_auth" = []))
64)]
65#[get("/gdpr/export")]
66pub async fn export_user_data(
67 req: HttpRequest,
68 data: web::Data<AppState>,
69 auth: AuthenticatedUser,
70) -> impl Responder {
71 let user_id = auth.user_id;
73
74 let ip_address = extract_ip_address(&req);
76 let user_agent = extract_user_agent(&req);
77
78 let organization_id = if auth.is_superadmin() {
82 None
83 } else {
84 auth.organization_id
85 };
86
87 match data
89 .gdpr_use_cases
90 .export_user_data(user_id, user_id, organization_id)
91 .await
92 {
93 Ok(export_data) => {
94 let user_email = export_data.user.email.clone();
96 let user_name = format!(
97 "{} {}",
98 export_data.user.first_name, export_data.user.last_name
99 );
100
101 let audit_entry = AuditLogEntry::new(
103 AuditEventType::GdprDataExported,
104 Some(user_id),
105 organization_id,
106 )
107 .with_resource("User", user_id)
108 .with_client_info(ip_address, user_agent)
109 .with_metadata(serde_json::json!({
110 "total_items": export_data.total_items,
111 "export_date": export_data.export_date
112 }));
113
114 let audit_logger = data.audit_logger.clone();
115 spawn(async move {
116 audit_logger.log(&audit_entry).await;
117 });
118
119 let email_service = data.email_service.clone();
121 spawn(async move {
122 if let Err(e) = email_service
123 .send_gdpr_export_notification(&user_email, &user_name, user_id)
124 .await
125 {
126 log::error!("Failed to send GDPR export email notification: {}", e);
127 }
128 });
129
130 HttpResponse::Ok().json(export_data)
131 }
132 Err(e) => {
133 let audit_entry = AuditLogEntry::new(
135 AuditEventType::GdprDataExportFailed,
136 Some(user_id),
137 organization_id,
138 )
139 .with_resource("User", user_id)
140 .with_client_info(ip_address, user_agent)
141 .with_error(e.clone());
142
143 let audit_logger = data.audit_logger.clone();
144 spawn(async move {
145 audit_logger.log(&audit_entry).await;
146 });
147
148 if classification_erreurs::est_introuvable(&e) {
149 HttpResponse::NotFound().json(serde_json::json!({
150 "error": e
151 }))
152 } else if classification_erreurs::est_interdit(&e) {
153 HttpResponse::Forbidden().json(serde_json::json!({
154 "error": e
155 }))
156 } else if e.contains("anonymized") {
157 HttpResponse::Gone().json(serde_json::json!({
158 "error": e
159 }))
160 } else {
161 HttpResponse::InternalServerError().json(serde_json::json!({
162 "error": format!("Failed to export user data: {}", e)
163 }))
164 }
165 }
166 }
167}
168
169#[utoipa::path(
184 delete,
185 path = "/gdpr/erase",
186 tag = "GDPR",
187 summary = "Erase user data by anonymization (Article 17 - Right to Erasure)",
188 responses(
189 (status = 200, description = "User data anonymized"),
190 (status = 401, description = "Unauthorized"),
191 (status = 403, description = "Forbidden"),
192 (status = 404, description = "User not found"),
193 (status = 409, description = "Legal holds prevent erasure"),
194 (status = 410, description = "User already anonymized"),
195 (status = 500, description = "Internal server error"),
196 ),
197 security(("bearer_auth" = []))
198)]
199#[delete("/gdpr/erase")]
200pub async fn erase_user_data(
201 req: HttpRequest,
202 data: web::Data<AppState>,
203 auth: AuthenticatedUser,
204 body: web::Json<GdprEraseRequestDto>,
205) -> impl Responder {
206 let user_id = auth.user_id;
208
209 let ip_address = extract_ip_address(&req);
211 let user_agent = extract_user_agent(&req);
212
213 let organization_id = if auth.is_superadmin() {
215 None
216 } else {
217 auth.organization_id
218 };
219
220 match data
222 .gdpr_use_cases
223 .erase_user_data(user_id, user_id, organization_id, Some(&body.password))
224 .await
225 {
226 Ok(erase_response) => {
227 let user_email = erase_response.user_email.clone();
229 let user_name = format!(
230 "{} {}",
231 erase_response.user_first_name, erase_response.user_last_name
232 );
233 let owners_count = erase_response.owners_anonymized;
234
235 let audit_entry = AuditLogEntry::new(
237 AuditEventType::GdprDataErased,
238 Some(user_id),
239 organization_id,
240 )
241 .with_resource("User", user_id)
242 .with_client_info(ip_address, user_agent)
243 .with_metadata(serde_json::json!({
244 "owners_anonymized": erase_response.owners_anonymized,
245 "anonymized_at": erase_response.anonymized_at
246 }));
247
248 let audit_logger = data.audit_logger.clone();
249 spawn(async move {
250 audit_logger.log(&audit_entry).await;
251 });
252
253 let email_service = data.email_service.clone();
255 spawn(async move {
256 if let Err(e) = email_service
257 .send_gdpr_erasure_notification(&user_email, &user_name, owners_count)
258 .await
259 {
260 log::error!("Failed to send GDPR erasure email notification: {}", e);
261 }
262 });
263
264 HttpResponse::Ok().json(erase_response)
265 }
266 Err(e) => {
267 let audit_entry = AuditLogEntry::new(
269 AuditEventType::GdprDataErasureFailed,
270 Some(user_id),
271 organization_id,
272 )
273 .with_resource("User", user_id)
274 .with_client_info(ip_address, user_agent)
275 .with_error(e.clone());
276
277 let audit_logger = data.audit_logger.clone();
278 spawn(async move {
279 audit_logger.log(&audit_entry).await;
280 });
281
282 if classification_erreurs::est_interdit(&e) {
283 HttpResponse::Forbidden().json(serde_json::json!({
284 "error": e
285 }))
286 } else if e.contains("already anonymized") {
287 HttpResponse::Gone().json(serde_json::json!({
288 "error": e
289 }))
290 } else if e.contains("legal holds") {
291 HttpResponse::Conflict().json(serde_json::json!({
292 "error": e,
293 "message": "Cannot erase data due to legal obligations. Please resolve pending issues before requesting erasure."
294 }))
295 } else if classification_erreurs::est_introuvable(&e) {
296 HttpResponse::NotFound().json(serde_json::json!({
297 "error": e
298 }))
299 } else {
300 HttpResponse::InternalServerError().json(serde_json::json!({
301 "error": format!("Failed to erase user data: {}", e)
302 }))
303 }
304 }
305 }
306}
307
308#[utoipa::path(
316 get,
317 path = "/gdpr/can-erase",
318 tag = "GDPR",
319 summary = "Check if user data can be erased (no legal holds)",
320 responses(
321 (status = 200, description = "Erasure eligibility status returned"),
322 (status = 401, description = "Unauthorized"),
323 (status = 500, description = "Internal server error"),
324 ),
325 security(("bearer_auth" = []))
326)]
327#[get("/gdpr/can-erase")]
328pub async fn can_erase_user(
329 req: HttpRequest,
330 data: web::Data<AppState>,
331 auth: AuthenticatedUser,
332) -> impl Responder {
333 let user_id = auth.user_id;
334
335 let ip_address = extract_ip_address(&req);
337 let user_agent = extract_user_agent(&req);
338
339 match data.gdpr_use_cases.can_erase_user(user_id).await {
340 Ok(can_erase) => {
341 let audit_entry = AuditLogEntry::new(
343 AuditEventType::GdprErasureCheckRequested,
344 Some(user_id),
345 auth.organization_id,
346 )
347 .with_resource("User", user_id)
348 .with_client_info(ip_address, user_agent)
349 .with_metadata(serde_json::json!({
350 "can_erase": can_erase
351 }));
352
353 let audit_logger = data.audit_logger.clone();
354 spawn(async move {
355 audit_logger.log(&audit_entry).await;
356 });
357
358 HttpResponse::Ok().json(serde_json::json!({
359 "can_erase": can_erase,
360 "user_id": user_id.to_string()
361 }))
362 }
363 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
364 "error": format!("Failed to check erasure eligibility: {}", e)
365 })),
366 }
367}
368
369#[utoipa::path(
391 put,
392 path = "/gdpr/rectify",
393 tag = "GDPR",
394 summary = "Rectify user personal data (Article 16 - Right to Rectification)",
395 request_body = GdprRectifyRequest,
396 responses(
397 (status = 200, description = "Data successfully rectified"),
398 (status = 400, description = "Validation error"),
399 (status = 401, description = "Unauthorized"),
400 (status = 403, description = "Forbidden"),
401 (status = 404, description = "User not found"),
402 (status = 500, description = "Internal server error"),
403 ),
404 security(("bearer_auth" = []))
405)]
406#[put("/gdpr/rectify")]
407pub async fn rectify_user_data(
408 req: HttpRequest,
409 data: web::Data<AppState>,
410 auth: AuthenticatedUser,
411 request: web::Json<GdprRectifyRequest>,
412) -> impl Responder {
413 let user_id = auth.user_id;
414
415 let ip_address = extract_ip_address(&req);
417 let user_agent = extract_user_agent(&req);
418
419 match data
421 .gdpr_use_cases
422 .rectify_user_data(
423 user_id,
424 user_id, request.email.clone(),
426 request.first_name.clone(),
427 request.last_name.clone(),
428 )
429 .await
430 {
431 Ok(_) => {
432 let audit_entry = AuditLogEntry::new(
434 AuditEventType::GdprDataRectified,
435 Some(user_id),
436 auth.organization_id,
437 )
438 .with_resource("User", user_id)
439 .with_client_info(ip_address, user_agent)
440 .with_metadata(serde_json::json!({
441 "fields_updated": {
442 "email": request.email.is_some(),
443 "first_name": request.first_name.is_some(),
444 "last_name": request.last_name.is_some()
445 }
446 }));
447
448 let audit_logger = data.audit_logger.clone();
449 spawn(async move {
450 audit_logger.log(&audit_entry).await;
451 });
452
453 let response = GdprActionResponse {
454 success: true,
455 message: "Personal data successfully rectified".to_string(),
456 updated_at: Utc::now().to_rfc3339(),
457 };
458
459 HttpResponse::Ok().json(response)
460 }
461 Err(e) => {
462 let audit_entry = AuditLogEntry::new(
464 AuditEventType::GdprDataRectificationFailed,
465 Some(user_id),
466 auth.organization_id,
467 )
468 .with_resource("User", user_id)
469 .with_client_info(ip_address, user_agent)
470 .with_error(e.clone());
471
472 let audit_logger = data.audit_logger.clone();
473 spawn(async move {
474 audit_logger.log(&audit_entry).await;
475 });
476
477 if classification_erreurs::est_interdit(&e) {
478 HttpResponse::Forbidden().json(serde_json::json!({
479 "error": e
480 }))
481 } else if classification_erreurs::est_introuvable(&e) {
482 HttpResponse::NotFound().json(serde_json::json!({
483 "error": e
484 }))
485 } else if e.contains("Validation error")
486 || e.contains("Invalid email")
487 || e.contains("cannot be empty")
488 || e.contains("No fields provided")
489 {
490 HttpResponse::BadRequest().json(serde_json::json!({
491 "error": e
492 }))
493 } else {
494 HttpResponse::InternalServerError().json(serde_json::json!({
495 "error": format!("Failed to rectify user data: {}", e)
496 }))
497 }
498 }
499 }
500}
501
502#[utoipa::path(
519 put,
520 path = "/gdpr/restrict-processing",
521 tag = "GDPR",
522 summary = "Restrict data processing (Article 18 - Right to Restriction)",
523 request_body = GdprRestrictProcessingRequest,
524 responses(
525 (status = 200, description = "Processing restriction applied"),
526 (status = 400, description = "Processing already restricted"),
527 (status = 401, description = "Unauthorized"),
528 (status = 403, description = "Forbidden"),
529 (status = 404, description = "User not found"),
530 (status = 500, description = "Internal server error"),
531 ),
532 security(("bearer_auth" = []))
533)]
534#[put("/gdpr/restrict-processing")]
535pub async fn restrict_user_processing(
536 req: HttpRequest,
537 data: web::Data<AppState>,
538 auth: AuthenticatedUser,
539 _request: web::Json<GdprRestrictProcessingRequest>,
540) -> impl Responder {
541 let user_id = auth.user_id;
542
543 let ip_address = extract_ip_address(&req);
545 let user_agent = extract_user_agent(&req);
546
547 match data
549 .gdpr_use_cases
550 .restrict_user_processing(user_id, user_id)
551 .await
552 {
553 Ok(_) => {
554 let audit_entry = AuditLogEntry::new(
556 AuditEventType::GdprProcessingRestricted,
557 Some(user_id),
558 auth.organization_id,
559 )
560 .with_resource("User", user_id)
561 .with_client_info(ip_address, user_agent);
562
563 let audit_logger = data.audit_logger.clone();
564 spawn(async move {
565 audit_logger.log(&audit_entry).await;
566 });
567
568 let response = GdprActionResponse {
569 success: true,
570 message: "Data processing successfully restricted. Your data will be stored but not processed for certain operations.".to_string(),
571 updated_at: Utc::now().to_rfc3339(),
572 };
573
574 HttpResponse::Ok().json(response)
575 }
576 Err(e) => {
577 let audit_entry = AuditLogEntry::new(
579 AuditEventType::GdprProcessingRestrictionFailed,
580 Some(user_id),
581 auth.organization_id,
582 )
583 .with_resource("User", user_id)
584 .with_client_info(ip_address, user_agent)
585 .with_error(e.clone());
586
587 let audit_logger = data.audit_logger.clone();
588 spawn(async move {
589 audit_logger.log(&audit_entry).await;
590 });
591
592 if classification_erreurs::est_interdit(&e) {
593 HttpResponse::Forbidden().json(serde_json::json!({
594 "error": e
595 }))
596 } else if classification_erreurs::est_introuvable(&e) {
597 HttpResponse::NotFound().json(serde_json::json!({
598 "error": e
599 }))
600 } else if e.contains("already restricted") {
601 HttpResponse::BadRequest().json(serde_json::json!({
602 "error": e
603 }))
604 } else {
605 HttpResponse::InternalServerError().json(serde_json::json!({
606 "error": format!("Failed to restrict processing: {}", e)
607 }))
608 }
609 }
610 }
611}
612
613#[utoipa::path(
632 put,
633 path = "/gdpr/marketing-preference",
634 tag = "GDPR",
635 summary = "Set marketing opt-out preference (Article 21 - Right to Object)",
636 request_body = GdprMarketingPreferenceRequest,
637 responses(
638 (status = 200, description = "Marketing preference updated"),
639 (status = 401, description = "Unauthorized"),
640 (status = 403, description = "Forbidden"),
641 (status = 404, description = "User not found"),
642 (status = 500, description = "Internal server error"),
643 ),
644 security(("bearer_auth" = []))
645)]
646#[put("/gdpr/marketing-preference")]
647pub async fn set_marketing_preference(
648 req: HttpRequest,
649 data: web::Data<AppState>,
650 auth: AuthenticatedUser,
651 request: web::Json<GdprMarketingPreferenceRequest>,
652) -> impl Responder {
653 let user_id = auth.user_id;
654
655 let ip_address = extract_ip_address(&req);
657 let user_agent = extract_user_agent(&req);
658
659 let opt_out = request.opt_out;
660
661 match data
663 .gdpr_use_cases
664 .set_marketing_preference(user_id, user_id, opt_out)
665 .await
666 {
667 Ok(_) => {
668 let event_type = if opt_out {
670 AuditEventType::GdprMarketingOptOut
671 } else {
672 AuditEventType::GdprMarketingOptIn
673 };
674
675 let audit_entry = AuditLogEntry::new(event_type, Some(user_id), auth.organization_id)
676 .with_resource("User", user_id)
677 .with_client_info(ip_address, user_agent)
678 .with_metadata(serde_json::json!({
679 "opt_out": opt_out
680 }));
681
682 let audit_logger = data.audit_logger.clone();
683 spawn(async move {
684 audit_logger.log(&audit_entry).await;
685 });
686
687 let message = if opt_out {
688 "You have successfully opted out of marketing communications. You will no longer receive promotional emails or offers."
689 } else {
690 "You have successfully opted back in to marketing communications. You will receive promotional emails and offers."
691 };
692
693 let response = GdprActionResponse {
694 success: true,
695 message: message.to_string(),
696 updated_at: Utc::now().to_rfc3339(),
697 };
698
699 HttpResponse::Ok().json(response)
700 }
701 Err(e) => {
702 let audit_entry = AuditLogEntry::new(
704 AuditEventType::GdprMarketingPreferenceChangeFailed,
705 Some(user_id),
706 auth.organization_id,
707 )
708 .with_resource("User", user_id)
709 .with_client_info(ip_address, user_agent)
710 .with_error(e.clone());
711
712 let audit_logger = data.audit_logger.clone();
713 spawn(async move {
714 audit_logger.log(&audit_entry).await;
715 });
716
717 if classification_erreurs::est_interdit(&e) {
718 HttpResponse::Forbidden().json(serde_json::json!({
719 "error": e
720 }))
721 } else if classification_erreurs::est_introuvable(&e) {
722 HttpResponse::NotFound().json(serde_json::json!({
723 "error": e
724 }))
725 } else {
726 HttpResponse::InternalServerError().json(serde_json::json!({
727 "error": format!("Failed to set marketing preference: {}", e)
728 }))
729 }
730 }
731 }
732}
733
734#[cfg(test)]
735mod tests {
736 #[test]
740 fn test_handler_structure_export() {
741 }
744
745 #[test]
746 fn test_handler_structure_erase() {
747 }
749
750 #[test]
751 fn test_handler_structure_can_erase() {
752 }
754}