use std::ops::ControlFlow;
use std::sync::Arc;
use super::AllowRule;
use super::FieldClassification;
use super::FieldMatchKind;
use super::FieldNameMatching;
use super::RedactionFloor;
use super::SensitiveFieldRule;
use super::Sensitivity;
use super::UnknownFieldPolicy;
use crate::policy::ResolvedField;
use crate::policy::internal::RedactionPolicyInner;
use crate::policy::internal::visit_canonical_field_candidates;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RedactionRules {
application: Arc<RedactionPolicyInner>,
floor: Option<RedactionFloor>,
}
impl RedactionRules {
#[must_use]
pub(crate) fn new(application: RedactionPolicyInner, floor: Option<RedactionFloor>) -> Self {
Self {
application: Arc::new(application),
floor,
}
}
#[must_use]
#[inline]
pub fn floor(&self) -> Option<&RedactionFloor> {
self.floor.as_ref()
}
#[must_use]
#[inline]
pub fn with_floor(mut self, floor: RedactionFloor) -> Self {
self.floor = Some(floor);
self
}
#[must_use]
pub fn disable_floor(mut self) -> Self {
self.floor = None;
self
}
#[must_use]
pub fn classify_field<'a>(&'a self, field: &str) -> FieldClassification<'a> {
classify_inner(&self.application, field, self.application.matching, true)
}
#[must_use]
#[inline]
pub fn sensitivity_for(&self, field: &str) -> Option<Sensitivity> {
match self.resolve_field(field) {
ResolvedField::Sensitive { sensitivity } => Some(sensitivity),
ResolvedField::PassThrough => None,
}
}
#[must_use]
#[inline]
pub(crate) fn sensitivity_for_exact(&self, field: &str) -> Option<Sensitivity> {
match self.resolve_field_exact(field) {
ResolvedField::Sensitive { sensitivity } => Some(sensitivity),
ResolvedField::PassThrough => None,
}
}
pub(crate) fn resolve_field_exact(&self, field: &str) -> ResolvedField {
let application = sensitivity_inner(&self.application, field, FieldNameMatching::Exact, true);
let floor = self
.floor
.as_ref()
.and_then(|floor| sensitivity_inner(&floor.inner, field, FieldNameMatching::Exact, false));
match self.floor.as_ref().zip(floor) {
Some((_floor, floor_level)) => ResolvedField::Sensitive {
sensitivity: application.map_or(floor_level, |level| level.max(floor_level)),
},
None => match application {
Some(sensitivity) => ResolvedField::Sensitive { sensitivity },
None => ResolvedField::PassThrough,
},
}
}
#[inline]
pub(crate) fn resolve_field(&self, field: &str) -> ResolvedField {
self.resolve_field_with_matching(field, self.application.matching)
}
fn resolve_field_with_matching(&self, field: &str, matching: FieldNameMatching) -> ResolvedField {
let application = sensitivity_inner(&self.application, field, matching, true);
let floor = self
.floor
.as_ref()
.and_then(|floor| sensitivity_inner(&floor.inner, field, floor.inner.matching, false));
match floor {
Some(floor_level) => ResolvedField::Sensitive {
sensitivity: application.map_or(floor_level, |level| level.max(floor_level)),
},
None => match application {
Some(sensitivity) => ResolvedField::Sensitive { sensitivity },
None => ResolvedField::PassThrough,
},
}
}
#[must_use]
#[inline]
pub fn matching(&self) -> FieldNameMatching {
self.application.matching
}
#[must_use]
#[inline]
pub fn unknown_field_policy(&self) -> UnknownFieldPolicy {
self.application.unknown_field_policy
}
pub fn application_sensitive_rules(&self) -> impl Iterator<Item = SensitiveFieldRule<'_>> {
self.application
.sensitive
.iter()
.map(|(field, level)| SensitiveFieldRule::new(field, *level))
}
pub fn application_allow_rules(&self) -> impl Iterator<Item = AllowRule<'_>> {
self.application
.allow_exact
.iter()
.map(|field| AllowRule::new(field, FieldNameMatching::Exact))
.chain(
self.application
.allow_suffix
.iter()
.map(|field| AllowRule::new(field, FieldNameMatching::ExactOrTokenSuffix)),
)
}
pub(crate) fn clone_application(&self) -> RedactionPolicyInner {
(*self.application).clone()
}
}
#[must_use]
fn classify_inner<'a>(
inner: &'a RedactionPolicyInner,
field: &str,
matching: FieldNameMatching,
allow: bool,
) -> FieldClassification<'a> {
match visit_canonical_field_candidates(field, matching, |is_exact, candidate| {
let match_kind = if is_exact {
FieldMatchKind::Exact
} else {
FieldMatchKind::TokenSuffix
};
if allow
&& is_exact
&& let Some(field) = inner.allow_exact.get(candidate)
{
return ControlFlow::Break(FieldClassification::Allowed {
rule: AllowRule::new(field, FieldNameMatching::Exact),
match_kind,
});
}
if allow && let Some(field) = inner.allow_suffix.get(candidate) {
return ControlFlow::Break(FieldClassification::Allowed {
rule: AllowRule::new(field, FieldNameMatching::ExactOrTokenSuffix),
match_kind,
});
}
if let Some((field, sensitivity)) = inner.sensitive.get_key_value(candidate) {
return ControlFlow::Break(FieldClassification::Sensitive {
rule: SensitiveFieldRule::new(field, *sensitivity),
match_kind,
});
}
ControlFlow::Continue(())
}) {
ControlFlow::Break(classification) => classification,
ControlFlow::Continue(()) => FieldClassification::Unknown,
}
}
#[must_use]
fn sensitivity_inner(
inner: &RedactionPolicyInner,
field: &str,
matching: FieldNameMatching,
allow: bool,
) -> Option<Sensitivity> {
match classify_inner(inner, field, matching, allow) {
FieldClassification::Allowed { .. } => None,
FieldClassification::Sensitive { .. } | FieldClassification::Unknown => {
strongest_sensitive_match(inner, field, matching).or_else(|| inner.unknown_field_policy.sensitivity())
}
}
}
#[must_use]
fn strongest_sensitive_match(
inner: &RedactionPolicyInner,
field: &str,
matching: FieldNameMatching,
) -> Option<Sensitivity> {
let mut strongest: Option<Sensitivity> = None;
let _ = visit_canonical_field_candidates(field, matching, |_is_exact, candidate| {
if let Some(level) = inner.sensitive.get(candidate) {
strongest = Some(strongest.map_or(*level, |current| current.max(*level)));
}
ControlFlow::<()>::Continue(())
});
strongest
}