use security_core::classification::DataClassification;
use security_core::severity::SecuritySeverity;
use security_events::event::{EventOutcome, EventValue, SecurityEvent};
use security_events::kind::EventKind;
use security_events::sink::SecuritySink;
use serde::Serialize;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
pub struct ConsentPurpose(pub String);
impl ConsentPurpose {
#[must_use]
pub fn new(purpose: &str) -> Self {
Self(purpose.to_string())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[non_exhaustive]
pub enum ConsentState {
Granted,
Denied,
Withdrawn,
NotCollected,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
#[non_exhaustive]
pub enum ConsentDecision {
Allowed,
Denied,
NotCollected,
Withdrawn,
PurposeMismatch,
}
pub struct ConsentPolicy {
purpose: ConsentPurpose,
state: ConsentState,
}
impl ConsentPolicy {
#[must_use]
pub fn new(purpose: ConsentPurpose) -> Self {
Self {
purpose,
state: ConsentState::NotCollected,
}
}
#[must_use]
pub fn state(&self) -> ConsentState {
self.state
}
#[must_use]
pub fn purpose(&self) -> &ConsentPurpose {
&self.purpose
}
pub fn grant(&mut self) {
self.state = ConsentState::Granted;
}
pub fn deny(&mut self) {
self.state = ConsentState::Denied;
}
pub fn withdraw(&mut self) {
self.state = ConsentState::Withdrawn;
}
pub fn check_consent(
&self,
requested_purpose: &ConsentPurpose,
sink: &dyn SecuritySink,
) -> ConsentDecision {
if *requested_purpose != self.purpose {
let mut event = SecurityEvent::new(
EventKind::ConsentViolation,
SecuritySeverity::High,
EventOutcome::Blocked,
);
event.labels.insert(
"consented_purpose".to_string(),
EventValue::Classified {
value: self.purpose.0.clone(),
classification: DataClassification::Internal,
},
);
event.labels.insert(
"requested_purpose".to_string(),
EventValue::Classified {
value: requested_purpose.0.clone(),
classification: DataClassification::Internal,
},
);
event.labels.insert(
"reason".to_string(),
EventValue::Classified {
value: "purpose_mismatch".to_string(),
classification: DataClassification::Internal,
},
);
sink.write_event(&event);
return ConsentDecision::PurposeMismatch;
}
match self.state {
ConsentState::Granted => ConsentDecision::Allowed,
ConsentState::Denied => {
self.emit_consent_event(sink, "consent_denied");
ConsentDecision::Denied
}
ConsentState::NotCollected => {
self.emit_consent_event(sink, "consent_not_collected");
ConsentDecision::NotCollected
}
ConsentState::Withdrawn => {
self.emit_consent_event(sink, "consent_withdrawn");
ConsentDecision::Withdrawn
}
}
}
fn emit_consent_event(&self, sink: &dyn SecuritySink, reason: &str) {
let mut event = SecurityEvent::new(
EventKind::ConsentViolation,
SecuritySeverity::Medium,
EventOutcome::Blocked,
);
event.labels.insert(
"purpose".to_string(),
EventValue::Classified {
value: self.purpose.0.clone(),
classification: DataClassification::Internal,
},
);
event.labels.insert(
"reason".to_string(),
EventValue::Classified {
value: reason.to_string(),
classification: DataClassification::Internal,
},
);
sink.write_event(&event);
}
}