use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use super::{Policy, PolicyError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum RequiredLevel {
HookOnly,
HookAndOsSandbox,
}
impl RequiredLevel {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::HookOnly => "hook-only",
Self::HookAndOsSandbox => "hook-and-os-sandbox",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Require {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub protection: Option<RequiredLevel>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub agents: BTreeMap<String, RequiredLevel>,
}
impl Require {
#[must_use]
pub fn for_agent(&self, host: Option<&str>) -> Option<RequiredLevel> {
host.and_then(|h| self.agents.get(h).copied())
.or(self.protection)
}
pub fn agent_ids(&self) -> impl Iterator<Item = &str> {
self.agents.keys().map(String::as_str)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RequiredBy {
Policy,
Repository,
Pack(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Requirement {
pub level: RequiredLevel,
pub by: RequiredBy,
}
impl Policy {
#[must_use]
pub fn required_for(&self, host: Option<&str>) -> Option<Requirement> {
let own = self.require.iter().map(|r| (RequiredBy::Policy, r));
let added = self.require_added.iter().map(|(by, r)| (by.clone(), r));
own.chain(added)
.filter_map(|(by, r)| r.for_agent(host).map(|level| Requirement { level, by }))
.fold(
None,
|strictest: Option<Requirement>, next| match strictest {
Some(kept) if kept.level >= next.level => Some(kept),
_ => Some(next),
},
)
}
pub fn add_requirement(&mut self, by: RequiredBy, require: &Require) {
self.require_added.push((by, require.clone()));
}
}
pub(super) fn check(require: Option<&Require>) -> Result<(), PolicyError> {
for id in require.iter().flat_map(|r| r.agent_ids()) {
let valid = !id.is_empty()
&& id
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-');
if !valid {
return Err(PolicyError::Rule {
rule: "require.agents".to_owned(),
problem: format!(
"`{id}` is not an agent id (lowercase letters, digits and `-`, as `openmoat status` names agents)"
),
});
}
}
Ok(())
}
#[cfg(test)]
mod tests;