use crate::config::PolicyConfig;
#[derive(Clone, Default, Debug, PartialEq)]
pub struct Policy {
pub allow: Vec<String>,
pub deny: Vec<String>,
pub redact_keys: Vec<String>,
pub require_approval: Vec<String>,
pub approval_timeout_secs: u64,
pub webhook: Option<crate::config::WebhookConfig>,
}
impl Policy {
pub fn from_config(config: Option<PolicyConfig>) -> Self {
let Some(config) = config else {
return Self::default();
};
Self {
allow: config.allow,
deny: config.deny,
redact_keys: config.redact_keys,
require_approval: config.require_approval,
approval_timeout_secs: config.approval_timeout_secs.unwrap_or(300),
webhook: config.webhook,
}
}
pub fn allows(&self, id: impl AsRef<str>) -> bool {
let id_ref = id.as_ref();
if self
.deny
.iter()
.any(|pattern| wildcard_match(pattern, id_ref))
{
return false;
}
if self.allow.is_empty() {
return true;
}
self.allow
.iter()
.any(|pattern| wildcard_match(pattern, id_ref))
}
pub fn requires_approval(&self, id: impl AsRef<str>) -> bool {
let id_ref = id.as_ref();
self.require_approval
.iter()
.any(|pattern| wildcard_match(pattern, id_ref))
}
}
pub fn wildcard_match(pattern: &str, value: &str) -> bool {
if pattern == "*" {
return true;
}
if !pattern.contains('*') {
return pattern == value;
}
let parts: Vec<&str> = pattern.split('*').collect();
let mut remaining = value;
if !pattern.starts_with('*') {
let first = parts[0];
if !remaining.starts_with(first) {
return false;
}
remaining = &remaining[first.len()..];
}
if !pattern.ends_with('*') {
let last = parts[parts.len() - 1];
if !remaining.ends_with(last) {
return false;
}
remaining = &remaining[..remaining.len() - last.len()];
}
if parts.len() > 2 {
for part in &parts[1..parts.len() - 1] {
if part.is_empty() {
continue;
}
if let Some(pos) = remaining.find(part) {
remaining = &remaining[pos + part.len()..];
} else {
return false;
}
}
}
true
}