use std::{
borrow::Borrow,
collections::{HashMap, HashSet},
fmt::Debug,
sync::Arc,
};
use dyn_problem::{define_anon_problem_types, Problem};
use futures::{stream::FuturesUnordered, TryFutureExt, TryStreamExt};
use rdf_utils::model::{description::DescriptionExt, graph::InfallibleGraph, term::ArcTerm};
use rdf_vocabularies::ns;
use sophia_api::term::Term;
use tower::ServiceExt;
use tracing::{debug, error, log::info};
use super::attribute_match_svc::{AttributeMatchRequest, BoxedAttributeMatchService};
use crate::{
attribute_match_svc::impl_::{
AgentMatchService, ClientMatchService, IssuerMatchService, VcMatchService,
},
model::{
access_mode::HAccessMode, acr::DAccessControlResource, context::DContext,
matcher::DMatcher, policy::DPolicy,
},
};
pub type AccessGrantSet = HashSet<HAccessMode<ArcTerm>>;
pub struct AcpEngine<G, WG>
where
G: InfallibleGraph,
WG: Borrow<G> + Debug,
{
attribute_match_svcs: Arc<HashMap<ArcTerm, BoxedAttributeMatchService<ArcTerm, G, WG>>>,
}
impl<G, WG> Clone for AcpEngine<G, WG>
where
G: InfallibleGraph,
WG: Borrow<G> + Debug,
{
#[inline]
fn clone(&self) -> Self {
Self {
attribute_match_svcs: self.attribute_match_svcs.clone(),
}
}
}
impl<G, WG> Debug for AcpEngine<G, WG>
where
G: InfallibleGraph,
WG: Borrow<G> + Debug,
{
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AcpEngine").finish()
}
}
impl<G> Default for AcpEngine<G, Arc<G>>
where
G: InfallibleGraph + Clone + Send + Sync + 'static,
{
#[inline]
fn default() -> Self {
Self::new(Arc::new(
[
(
ns::acp::agent,
Box::new(AgentMatchService) as BoxedAttributeMatchService<ArcTerm, G, Arc<G>>,
),
(ns::acp::client, Box::new(ClientMatchService)),
(ns::acp::issuer, Box::new(IssuerMatchService)),
(ns::acp::vc, Box::new(VcMatchService)),
]
.into_iter()
.map(|(a, s)| (a.into_term(), s))
.collect(),
))
}
}
impl<G, WG> AcpEngine<G, WG>
where
G: InfallibleGraph + Clone,
WG: Borrow<G> + Clone + Debug + Send + Sync,
{
#[inline]
pub fn new(
attribute_match_svcs: Arc<HashMap<ArcTerm, BoxedAttributeMatchService<ArcTerm, G, WG>>>,
) -> Self {
Self {
attribute_match_svcs,
}
}
#[inline]
pub fn supported_attrs(&self) -> impl Iterator<Item = ArcTerm> + '_ {
self.attribute_match_svcs.keys().cloned()
}
#[tracing::instrument(skip_all)]
pub async fn resolve_access_control(
&self,
opt_acr: Option<DAccessControlResource<G, WG>>,
ancestor_acrs: Vec<Option<DAccessControlResource<G, WG>>>,
context: DContext<G, WG>,
) -> Result<AccessGrantSet, Problem> {
let effective_policies = self.gather_effective_policies(opt_acr, ancestor_acrs);
let allowed_access_modes = self
.grant_access_modes(effective_policies, context)
.inspect_err(|e| error!("Error in resolving granted access modes. Error:\n {}", e))
.await?;
Ok(allowed_access_modes)
}
fn gather_effective_policies(
&self,
opt_acr: Option<DAccessControlResource<G, WG>>,
ancestor_acrs: Vec<Option<DAccessControlResource<G, WG>>>,
) -> Vec<DPolicy<G, WG>> {
let own_acls = if let Some(acr) = &opt_acr {
Box::new(acr.access_control()) as Box<dyn Iterator<Item = _>>
} else {
Box::new(std::iter::empty())
};
let inh_acls = ancestor_acrs
.iter()
.filter_map(|opt_acr| opt_acr.as_ref())
.flat_map(|ancestor_acr| ancestor_acr.member_access_control());
own_acls
.chain(inh_acls)
.fold(Vec::new(), |mut effective_policies, acl| {
effective_policies.extend(acl.apply());
effective_policies
})
}
async fn grant_access_modes(
&self,
policies: Vec<DPolicy<G, WG>>,
context: DContext<G, WG>,
) -> Result<AccessGrantSet, Problem> {
let mut allowed_access_modes = HashSet::new();
let mut denied_access_modes = HashSet::new();
for policy in policies.into_iter() {
if self
.is_satisfied_policy(policy.clone(), context.clone())
.await?
{
allowed_access_modes.extend(policy.h_allow());
denied_access_modes.extend(policy.h_deny());
}
}
denied_access_modes.iter().for_each(|mode| {
allowed_access_modes.remove(mode);
});
Ok(allowed_access_modes)
}
async fn is_satisfied_policy(
&self,
policy: DPolicy<G, WG>,
context: DContext<G, WG>,
) -> Result<bool, Problem> {
let none_of = policy.none_of().collect::<Vec<_>>();
if none_of
.into_iter()
.map(|matcher| self.is_satisfied_matcher(matcher, context.clone()))
.collect::<FuturesUnordered<_>>()
.try_collect::<Vec<_>>()
.await?
.contains(&true)
{
debug!("Policy has a noneOf matcher that is satisfied.");
return Ok(false);
}
let all_of = policy.all_of().collect::<Vec<_>>();
if all_of
.into_iter()
.map(|matcher| self.is_satisfied_matcher(matcher, context.clone()))
.collect::<FuturesUnordered<_>>()
.try_collect::<Vec<_>>()
.await?
.contains(&false)
{
debug!("Policy has an allOf matcher that is not satisfied.");
return Ok(false);
}
let any_of = policy.any_of().collect::<Vec<_>>();
if any_of
.into_iter()
.map(|matcher| self.is_satisfied_matcher(matcher, context.clone()))
.collect::<FuturesUnordered<_>>()
.try_collect::<Vec<_>>()
.await?
.contains(&true)
{
debug!("Policy has an anyOf matcher that is satisfied.");
return Ok(true);
}
Ok(policy.has_any(&ns::acp::allOf) && !policy.has_any(&ns::acp::anyOf))
}
async fn is_satisfied_matcher(
&self,
matcher: DMatcher<G, WG>,
context: DContext<G, WG>,
) -> Result<bool, Problem> {
let attribute_match_futs = self
.attribute_match_svcs
.iter()
.map(|(attribute, svc)| {
(
attribute.clone(),
matcher
.get_all(attribute)
.into_term_owning::<ArcTerm>()
.map(|value| {
svc.clone().oneshot(AttributeMatchRequest {
value,
context: context.clone(),
})
})
.collect::<FuturesUnordered<_>>(),
)
})
.filter(|(_, futs)| !futs.is_empty())
.collect::<HashMap<_, _>>();
if attribute_match_futs.is_empty() {
info!("Matcher doesn't define any known attribute.");
return Ok(false);
}
for (attribute, futs) in attribute_match_futs.into_iter() {
let mut is_match = false;
let mut last_error = None;
for fut in futs {
match fut.await {
Ok(true) => {
debug!("{:?} attribute matched", attribute);
is_match = true;
break;
}
Err(e) => {
info!(
"Unknown error in resolving attribute value match. Error:\n {}",
e
);
last_error = Some(e);
}
_ => {}
}
}
if !is_match {
if let Some(e) = last_error {
error!("Error in resolving attribute match.");
return Err(INTERNAL_MATCHER_ERROR
.new_problem_builder()
.source(e)
.finish());
}
info!(
"None of attribute values matched against context. attribute: {:?}",
attribute
);
return Ok(false);
}
}
Ok(true)
}
}
define_anon_problem_types!(
INTERNAL_MATCHER_ERROR: ("Internal matcher error.");
);