use std::sync::{Mutex, OnceLock};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum DataClass {
#[default]
Public,
Internal,
Pii,
Phi,
Pci,
Secret,
}
impl DataClass {
pub fn as_str(&self) -> &'static str {
match self {
Self::Public => "PUBLIC",
Self::Internal => "INTERNAL",
Self::Pii => "PII",
Self::Phi => "PHI",
Self::Pci => "PCI",
Self::Secret => "SECRET", }
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ClassificationRule {
pub pattern: String,
pub classification: DataClass,
}
impl ClassificationRule {
pub fn new(pattern: impl Into<String>, classification: DataClass) -> Self {
Self {
pattern: pattern.into(),
classification,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ClassificationPolicy {
pub public: String,
pub internal: String,
pub pii: String,
pub phi: String,
pub pci: String,
pub secret: String,
}
impl ClassificationPolicy {
pub fn lookup_action(&self, label: &str) -> &str {
match label {
"PUBLIC" => &self.public,
"INTERNAL" => &self.internal,
"PII" => &self.pii,
"PHI" => &self.phi,
"PCI" => &self.pci,
"SECRET" => &self.secret, _ => "pass",
}
}
}
impl Default for ClassificationPolicy {
fn default() -> Self {
Self {
public: "pass".to_string(),
internal: "pass".to_string(),
pii: "redact".to_string(),
phi: "drop".to_string(),
pci: "hash".to_string(),
secret: "drop".to_string(), }
}
}
static POLICY: OnceLock<Mutex<ClassificationPolicy>> = OnceLock::new();
#[cfg_attr(test, mutants::skip)] fn default_policy_mutex() -> Mutex<ClassificationPolicy> {
Mutex::new(ClassificationPolicy::default())
}
fn policy() -> &'static Mutex<ClassificationPolicy> {
POLICY.get_or_init(default_policy_mutex)
}
pub fn set_classification_policy(p: ClassificationPolicy) {
*crate::_lock::lock(policy()) = p;
}
pub fn get_classification_policy() -> ClassificationPolicy {
crate::_lock::lock(policy()).clone()
}
static RULES: OnceLock<Mutex<Vec<ClassificationRule>>> = OnceLock::new();
#[cfg_attr(test, mutants::skip)] fn empty_rules_mutex() -> Mutex<Vec<ClassificationRule>> {
Mutex::new(Vec::new())
}
fn rules() -> &'static Mutex<Vec<ClassificationRule>> {
RULES.get_or_init(empty_rules_mutex)
}
fn match_glob(pattern: &str, key: &str) -> bool {
if !pattern.contains(['*', '?']) {
return pattern == key;
}
fn glob_match(p: &[u8], s: &[u8]) -> bool {
match (p.first(), s.first()) {
(None, None) => true,
(None, Some(_)) => false,
(Some(b'*'), _) => {
let p_rest = &p[1..];
for offset in 0..=s.len() {
if glob_match(p_rest, &s[offset..]) {
return true;
}
}
false
}
(Some(b'?'), Some(_)) => glob_match(&p[1..], &s[1..]),
(Some(b'?'), None) => false,
(Some(&pc), Some(&sc)) => pc == sc && glob_match(&p[1..], &s[1..]),
(Some(_), None) => false,
}
}
glob_match(pattern.as_bytes(), key.as_bytes())
}
pub fn register_classification_rule(rule: ClassificationRule) {
crate::_lock::lock(rules()).push(rule);
}
pub fn register_classification_rules(next: Vec<ClassificationRule>) {
crate::_lock::lock(rules()).extend(next);
}
pub fn clear_classification_rules() {
crate::_lock::lock(rules()).clear();
}
pub fn classify_key(key: &str) -> Option<String> {
let rules = crate::_lock::lock(rules());
for rule in rules.iter() {
if match_glob(&rule.pattern, key) {
return Some(rule.classification.as_str().to_string());
}
}
None
}
#[cfg(test)]
#[path = "classification_tests.rs"]
mod tests;