use crate::policy::enforce_requirements;
use crate::{CapabilityError, CapabilityRequest, CapabilityResult};
use appcore_contracts::ServiceId;
use appcore_core::{CapabilityDescriptor, CapabilityName, CoreIdentity};
use appcore_distributed_contracts::ServiceLeadershipGuard;
use std::collections::HashMap;
pub struct CapabilityEnforcementContext<'a> {
pub(crate) identity: &'a CoreIdentity,
pub(crate) service_id: &'a ServiceId,
pub(crate) leadership: Option<&'a dyn ServiceLeadershipGuard>,
pub(crate) now_ms: u64,
pub(crate) writes_allowed: bool,
}
impl<'a> CapabilityEnforcementContext<'a> {
pub fn new(identity: &'a CoreIdentity, service_id: &'a ServiceId, now_ms: u64) -> Self {
Self {
identity,
service_id,
leadership: None,
now_ms,
writes_allowed: true,
}
}
pub fn with_leadership(mut self, leadership: &'a dyn ServiceLeadershipGuard) -> Self {
self.leadership = Some(leadership);
self
}
pub fn with_writes_allowed(mut self, writes_allowed: bool) -> Self {
self.writes_allowed = writes_allowed;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct CapabilityCatalog {
descriptors: HashMap<CapabilityName, CapabilityDescriptor>,
}
impl CapabilityCatalog {
pub fn new() -> Self {
Self::default()
}
pub fn from_descriptors(
descriptors: impl IntoIterator<Item = CapabilityDescriptor>,
) -> CapabilityResult<Self> {
let mut catalog = Self::new();
for descriptor in descriptors {
catalog.register_descriptor(descriptor)?;
}
Ok(catalog)
}
pub fn register_descriptor(
&mut self,
descriptor: CapabilityDescriptor,
) -> CapabilityResult<()> {
if self.descriptors.contains_key(&descriptor.name) {
return Err(CapabilityError::DescriptorAlreadyRegistered(
descriptor.name.clone(),
));
}
self.descriptors.insert(descriptor.name.clone(), descriptor);
Ok(())
}
pub fn descriptor(&self, capability: &CapabilityName) -> Option<&CapabilityDescriptor> {
self.descriptors.get(capability)
}
pub fn descriptors(&self) -> Vec<&CapabilityDescriptor> {
let mut descriptors = self.descriptors.values().collect::<Vec<_>>();
descriptors.sort_by(|left, right| left.name.as_str().cmp(right.name.as_str()));
descriptors
}
pub fn resolve_local(
&self,
request: &CapabilityRequest,
) -> CapabilityResult<&CapabilityDescriptor> {
let descriptor = self
.descriptor(&request.capability)
.ok_or_else(|| CapabilityError::CapabilityNotDeclared(request.capability.clone()))?;
crate::policy::enforce_request_requirements(request, descriptor)?;
Ok(descriptor)
}
pub fn authorize_local(
&self,
request: &CapabilityRequest,
context: CapabilityEnforcementContext<'_>,
) -> CapabilityResult<()> {
let descriptor = self.resolve_local(request)?;
enforce_requirements(
context.identity,
context.service_id,
request,
descriptor,
&context.identity.core_id,
context.leadership,
context.writes_allowed,
context.now_ms,
)
}
}