pulseengine_mcp_auth/
consent.rs

1//! Consent management system for privacy compliance
2//!
3//! This module provides comprehensive consent tracking and management
4//! for GDPR, CCPA, and other privacy regulations. It tracks user consent
5//! for data processing activities and provides audit trails.
6
7pub mod manager;
8
9use chrono::{DateTime, Utc};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use thiserror::Error;
13use uuid::Uuid;
14
15/// Consent management errors
16#[derive(Debug, Error)]
17pub enum ConsentError {
18    #[error("Consent record not found: {0}")]
19    ConsentNotFound(String),
20
21    #[error("Invalid consent data: {0}")]
22    InvalidData(String),
23
24    #[error("Consent already exists: {0}")]
25    ConsentExists(String),
26
27    #[error("Storage error: {0}")]
28    StorageError(String),
29
30    #[error("Serialization error: {0}")]
31    SerializationError(#[from] serde_json::Error),
32}
33
34/// Types of consent that can be requested
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
36pub enum ConsentType {
37    /// Consent for data processing (GDPR Article 6)
38    DataProcessing,
39
40    /// Consent for marketing communications
41    Marketing,
42
43    /// Consent for analytics and performance monitoring
44    Analytics,
45
46    /// Consent for sharing data with third parties
47    DataSharing,
48
49    /// Consent for automated decision making
50    AutomatedDecisionMaking,
51
52    /// Consent for storing authentication sessions
53    SessionStorage,
54
55    /// Consent for audit logging
56    AuditLogging,
57
58    /// Custom consent type with description
59    Custom(String),
60}
61
62impl std::fmt::Display for ConsentType {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        match self {
65            ConsentType::DataProcessing => write!(f, "Data Processing"),
66            ConsentType::Marketing => write!(f, "Marketing Communications"),
67            ConsentType::Analytics => write!(f, "Analytics & Performance"),
68            ConsentType::DataSharing => write!(f, "Third-party Data Sharing"),
69            ConsentType::AutomatedDecisionMaking => write!(f, "Automated Decision Making"),
70            ConsentType::SessionStorage => write!(f, "Session Storage"),
71            ConsentType::AuditLogging => write!(f, "Audit Logging"),
72            ConsentType::Custom(desc) => write!(f, "Custom: {desc}"),
73        }
74    }
75}
76
77/// Legal basis for data processing under GDPR
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
79pub enum LegalBasis {
80    /// Consent of the data subject (Article 6(1)(a))
81    Consent,
82
83    /// Performance of a contract (Article 6(1)(b))
84    Contract,
85
86    /// Compliance with legal obligation (Article 6(1)(c))
87    LegalObligation,
88
89    /// Protection of vital interests (Article 6(1)(d))
90    VitalInterests,
91
92    /// Performance of public task (Article 6(1)(e))
93    PublicTask,
94
95    /// Legitimate interests (Article 6(1)(f))
96    LegitimateInterests,
97}
98
99impl std::fmt::Display for LegalBasis {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        match self {
102            LegalBasis::Consent => write!(f, "Consent (GDPR 6.1.a)"),
103            LegalBasis::Contract => write!(f, "Contract (GDPR 6.1.b)"),
104            LegalBasis::LegalObligation => write!(f, "Legal Obligation (GDPR 6.1.c)"),
105            LegalBasis::VitalInterests => write!(f, "Vital Interests (GDPR 6.1.d)"),
106            LegalBasis::PublicTask => write!(f, "Public Task (GDPR 6.1.e)"),
107            LegalBasis::LegitimateInterests => write!(f, "Legitimate Interests (GDPR 6.1.f)"),
108        }
109    }
110}
111
112/// Consent status
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
114pub enum ConsentStatus {
115    /// Consent has been given
116    Granted,
117
118    /// Consent has been withdrawn
119    Withdrawn,
120
121    /// Consent is pending (requested but not yet responded to)
122    Pending,
123
124    /// Consent has expired
125    Expired,
126
127    /// Consent was denied
128    Denied,
129}
130
131impl std::fmt::Display for ConsentStatus {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        match self {
134            ConsentStatus::Granted => write!(f, "Granted"),
135            ConsentStatus::Withdrawn => write!(f, "Withdrawn"),
136            ConsentStatus::Pending => write!(f, "Pending"),
137            ConsentStatus::Expired => write!(f, "Expired"),
138            ConsentStatus::Denied => write!(f, "Denied"),
139        }
140    }
141}
142
143/// Individual consent record
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct ConsentRecord {
146    /// Unique consent ID
147    pub id: String,
148
149    /// Subject identifier (user ID, API key ID, etc.)
150    pub subject_id: String,
151
152    /// Type of consent
153    pub consent_type: ConsentType,
154
155    /// Current consent status
156    pub status: ConsentStatus,
157
158    /// Legal basis for processing
159    pub legal_basis: LegalBasis,
160
161    /// Purpose of data processing
162    pub purpose: String,
163
164    /// Data categories being processed
165    pub data_categories: Vec<String>,
166
167    /// When consent was granted
168    pub granted_at: Option<DateTime<Utc>>,
169
170    /// When consent was withdrawn
171    pub withdrawn_at: Option<DateTime<Utc>>,
172
173    /// When consent expires (if applicable)
174    pub expires_at: Option<DateTime<Utc>>,
175
176    /// Source of consent (web form, API, CLI, etc.)
177    pub consent_source: String,
178
179    /// IP address when consent was given
180    pub source_ip: Option<String>,
181
182    /// Additional metadata
183    pub metadata: HashMap<String, String>,
184
185    /// Record creation timestamp
186    pub created_at: DateTime<Utc>,
187
188    /// Last update timestamp
189    pub updated_at: DateTime<Utc>,
190}
191
192impl ConsentRecord {
193    /// Create a new consent record
194    pub fn new(
195        subject_id: String,
196        consent_type: ConsentType,
197        legal_basis: LegalBasis,
198        purpose: String,
199        consent_source: String,
200    ) -> Self {
201        let now = Utc::now();
202        Self {
203            id: Uuid::new_v4().to_string(),
204            subject_id,
205            consent_type,
206            status: ConsentStatus::Pending,
207            legal_basis,
208            purpose,
209            data_categories: Vec::new(),
210            granted_at: None,
211            withdrawn_at: None,
212            expires_at: None,
213            consent_source,
214            source_ip: None,
215            metadata: HashMap::new(),
216            created_at: now,
217            updated_at: now,
218        }
219    }
220
221    /// Grant consent
222    pub fn grant(&mut self, source_ip: Option<String>) {
223        self.status = ConsentStatus::Granted;
224        self.granted_at = Some(Utc::now());
225        self.withdrawn_at = None;
226        self.source_ip = source_ip;
227        self.updated_at = Utc::now();
228    }
229
230    /// Withdraw consent
231    pub fn withdraw(&mut self, source_ip: Option<String>) {
232        self.status = ConsentStatus::Withdrawn;
233        self.withdrawn_at = Some(Utc::now());
234        self.source_ip = source_ip;
235        self.updated_at = Utc::now();
236    }
237
238    /// Deny consent
239    pub fn deny(&mut self, source_ip: Option<String>) {
240        self.status = ConsentStatus::Denied;
241        self.source_ip = source_ip;
242        self.updated_at = Utc::now();
243    }
244
245    /// Check if consent is currently valid
246    pub fn is_valid(&self) -> bool {
247        match self.status {
248            ConsentStatus::Granted => {
249                // Check if expired
250                if let Some(expires_at) = self.expires_at {
251                    Utc::now() < expires_at
252                } else {
253                    true
254                }
255            }
256            _ => false,
257        }
258    }
259
260    /// Check if consent has expired
261    pub fn is_expired(&self) -> bool {
262        if let Some(expires_at) = self.expires_at {
263            Utc::now() >= expires_at
264        } else {
265            false
266        }
267    }
268
269    /// Set expiration date
270    pub fn set_expiration(&mut self, expires_at: DateTime<Utc>) {
271        self.expires_at = Some(expires_at);
272        self.updated_at = Utc::now();
273    }
274
275    /// Add data category
276    pub fn add_data_category(&mut self, category: String) {
277        if !self.data_categories.contains(&category) {
278            self.data_categories.push(category);
279            self.updated_at = Utc::now();
280        }
281    }
282
283    /// Add metadata
284    pub fn add_metadata(&mut self, key: String, value: String) {
285        self.metadata.insert(key, value);
286        self.updated_at = Utc::now();
287    }
288}
289
290/// Consent audit entry
291#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct ConsentAuditEntry {
293    /// Audit entry ID
294    pub id: String,
295
296    /// Related consent record ID
297    pub consent_id: String,
298
299    /// Subject identifier
300    pub subject_id: String,
301
302    /// Action performed
303    pub action: String,
304
305    /// Previous status
306    pub previous_status: Option<ConsentStatus>,
307
308    /// New status
309    pub new_status: ConsentStatus,
310
311    /// Source of the action
312    pub action_source: String,
313
314    /// IP address of the actor
315    pub source_ip: Option<String>,
316
317    /// Additional details
318    pub details: HashMap<String, String>,
319
320    /// Timestamp
321    pub timestamp: DateTime<Utc>,
322}
323
324/// Summary of consent status for a subject
325#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct ConsentSummary {
327    /// Subject identifier
328    pub subject_id: String,
329
330    /// Consent status by type
331    pub consents: HashMap<ConsentType, ConsentStatus>,
332
333    /// Overall consent validity
334    pub is_valid: bool,
335
336    /// Last update timestamp
337    pub last_updated: DateTime<Utc>,
338
339    /// Pending consent requests
340    pub pending_requests: usize,
341
342    /// Expired consents
343    pub expired_consents: usize,
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    #[test]
351    fn test_consent_record_creation() {
352        let record = ConsentRecord::new(
353            "user123".to_string(),
354            ConsentType::DataProcessing,
355            LegalBasis::Consent,
356            "Process user authentication data".to_string(),
357            "web_form".to_string(),
358        );
359
360        assert_eq!(record.subject_id, "user123");
361        assert_eq!(record.consent_type, ConsentType::DataProcessing);
362        assert_eq!(record.status, ConsentStatus::Pending);
363        assert_eq!(record.legal_basis, LegalBasis::Consent);
364        assert!(record.granted_at.is_none());
365        assert!(!record.is_valid());
366    }
367
368    #[test]
369    fn test_consent_grant_and_withdraw() {
370        let mut record = ConsentRecord::new(
371            "user123".to_string(),
372            ConsentType::Analytics,
373            LegalBasis::Consent,
374            "Analytics tracking".to_string(),
375            "api".to_string(),
376        );
377
378        // Grant consent
379        record.grant(Some("192.168.1.100".to_string()));
380        assert_eq!(record.status, ConsentStatus::Granted);
381        assert!(record.granted_at.is_some());
382        assert!(record.is_valid());
383
384        // Withdraw consent
385        record.withdraw(Some("192.168.1.100".to_string()));
386        assert_eq!(record.status, ConsentStatus::Withdrawn);
387        assert!(record.withdrawn_at.is_some());
388        assert!(!record.is_valid());
389    }
390
391    #[test]
392    fn test_consent_expiration() {
393        let mut record = ConsentRecord::new(
394            "user123".to_string(),
395            ConsentType::Marketing,
396            LegalBasis::Consent,
397            "Marketing emails".to_string(),
398            "web_form".to_string(),
399        );
400
401        // Grant consent
402        record.grant(None);
403        assert!(record.is_valid());
404
405        // Set expiration in the past
406        record.set_expiration(Utc::now() - chrono::Duration::hours(1));
407        assert!(!record.is_valid());
408        assert!(record.is_expired());
409    }
410
411    #[test]
412    fn test_consent_type_display() {
413        assert_eq!(ConsentType::DataProcessing.to_string(), "Data Processing");
414        assert_eq!(
415            ConsentType::Custom("Special Processing".to_string()).to_string(),
416            "Custom: Special Processing"
417        );
418    }
419
420    #[test]
421    fn test_legal_basis_display() {
422        assert_eq!(LegalBasis::Consent.to_string(), "Consent (GDPR 6.1.a)");
423        assert_eq!(
424            LegalBasis::LegitimateInterests.to_string(),
425            "Legitimate Interests (GDPR 6.1.f)"
426        );
427    }
428
429    #[test]
430    fn test_data_categories() {
431        let mut record = ConsentRecord::new(
432            "user123".to_string(),
433            ConsentType::DataProcessing,
434            LegalBasis::Consent,
435            "User data processing".to_string(),
436            "api".to_string(),
437        );
438
439        record.add_data_category("personal_identifiers".to_string());
440        record.add_data_category("authentication_data".to_string());
441        record.add_data_category("personal_identifiers".to_string()); // Duplicate
442
443        assert_eq!(record.data_categories.len(), 2);
444        assert!(
445            record
446                .data_categories
447                .contains(&"personal_identifiers".to_string())
448        );
449        assert!(
450            record
451                .data_categories
452                .contains(&"authentication_data".to_string())
453        );
454    }
455}