1use std::{
5 borrow::Borrow,
6 collections::{HashMap, HashSet},
7 fmt::Debug,
8 sync::Arc,
9};
10
11use dyn_problem::{define_anon_problem_types, Problem};
12use futures::{stream::FuturesUnordered, TryFutureExt, TryStreamExt};
13use rdf_utils::model::{description::DescriptionExt, graph::InfallibleGraph, term::ArcTerm};
14use rdf_vocabularies::ns;
15use sophia_api::term::Term;
16use tower::ServiceExt;
17use tracing::{debug, error, log::info};
18
19use super::attribute_match_svc::{AttributeMatchRequest, BoxedAttributeMatchService};
20use crate::{
21 attribute_match_svc::impl_::{
22 AgentMatchService, ClientMatchService, IssuerMatchService, VcMatchService,
23 },
24 model::{
25 access_mode::HAccessMode, acr::DAccessControlResource, context::DContext,
26 matcher::DMatcher, policy::DPolicy,
27 },
28};
29
30pub type AccessGrantSet = HashSet<HAccessMode<ArcTerm>>;
36
37pub struct AcpEngine<G, WG>
40where
41 G: InfallibleGraph,
42 WG: Borrow<G> + Debug,
43{
44 attribute_match_svcs: Arc<HashMap<ArcTerm, BoxedAttributeMatchService<ArcTerm, G, WG>>>,
46}
47
48impl<G, WG> Clone for AcpEngine<G, WG>
49where
50 G: InfallibleGraph,
51 WG: Borrow<G> + Debug,
52{
53 #[inline]
54 fn clone(&self) -> Self {
55 Self {
56 attribute_match_svcs: self.attribute_match_svcs.clone(),
57 }
58 }
59}
60
61impl<G, WG> Debug for AcpEngine<G, WG>
62where
63 G: InfallibleGraph,
64 WG: Borrow<G> + Debug,
65{
66 #[inline]
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 f.debug_struct("AcpEngine").finish()
69 }
70}
71
72impl<G> Default for AcpEngine<G, Arc<G>>
73where
74 G: InfallibleGraph + Clone + Send + Sync + 'static,
75{
76 #[inline]
77 fn default() -> Self {
78 Self::new(Arc::new(
79 [
80 (
81 ns::acp::agent,
82 Box::new(AgentMatchService) as BoxedAttributeMatchService<ArcTerm, G, Arc<G>>,
83 ),
84 (ns::acp::client, Box::new(ClientMatchService)),
85 (ns::acp::issuer, Box::new(IssuerMatchService)),
86 (ns::acp::vc, Box::new(VcMatchService)),
87 ]
88 .into_iter()
89 .map(|(a, s)| (a.into_term(), s))
90 .collect(),
91 ))
92 }
93}
94
95impl<G, WG> AcpEngine<G, WG>
98where
99 G: InfallibleGraph + Clone,
100 WG: Borrow<G> + Clone + Debug + Send + Sync,
101{
102 #[inline]
104 pub fn new(
105 attribute_match_svcs: Arc<HashMap<ArcTerm, BoxedAttributeMatchService<ArcTerm, G, WG>>>,
106 ) -> Self {
107 Self {
108 attribute_match_svcs,
109 }
110 }
111
112 #[inline]
114 pub fn supported_attrs(&self) -> impl Iterator<Item = ArcTerm> + '_ {
115 self.attribute_match_svcs.keys().cloned()
116 }
117
118 #[tracing::instrument(skip_all)]
124 pub async fn resolve_access_control(
125 &self,
126 opt_acr: Option<DAccessControlResource<G, WG>>,
127 ancestor_acrs: Vec<Option<DAccessControlResource<G, WG>>>,
128 context: DContext<G, WG>,
129 ) -> Result<AccessGrantSet, Problem> {
130 let effective_policies = self.gather_effective_policies(opt_acr, ancestor_acrs);
132
133 let allowed_access_modes = self
135 .grant_access_modes(effective_policies, context)
136 .inspect_err(|e| error!("Error in resolving granted access modes. Error:\n {}", e))
137 .await?;
138
139 Ok(allowed_access_modes)
140 }
141
142 fn gather_effective_policies(
150 &self,
151 opt_acr: Option<DAccessControlResource<G, WG>>,
152 ancestor_acrs: Vec<Option<DAccessControlResource<G, WG>>>,
153 ) -> Vec<DPolicy<G, WG>> {
154 let own_acls = if let Some(acr) = &opt_acr {
156 Box::new(acr.access_control()) as Box<dyn Iterator<Item = _>>
157 } else {
158 Box::new(std::iter::empty())
159 };
160
161 let inh_acls = ancestor_acrs
163 .iter()
164 .filter_map(|opt_acr| opt_acr.as_ref())
165 .flat_map(|ancestor_acr| ancestor_acr.member_access_control());
166
167 own_acls
168 .chain(inh_acls)
169 .fold(Vec::new(), |mut effective_policies, acl| {
170 effective_policies.extend(acl.apply());
171 effective_policies
172 })
173 }
174
175 async fn grant_access_modes(
180 &self,
181 policies: Vec<DPolicy<G, WG>>,
182 context: DContext<G, WG>,
183 ) -> Result<AccessGrantSet, Problem> {
184 let mut allowed_access_modes = HashSet::new();
185 let mut denied_access_modes = HashSet::new();
186
187 for policy in policies.into_iter() {
189 if self
190 .is_satisfied_policy(policy.clone(), context.clone())
191 .await?
192 {
193 allowed_access_modes.extend(policy.h_allow());
194 denied_access_modes.extend(policy.h_deny());
195 }
196 }
197
198 denied_access_modes.iter().for_each(|mode| {
200 allowed_access_modes.remove(mode);
201 });
202
203 Ok(allowed_access_modes)
204 }
205
206 async fn is_satisfied_policy(
213 &self,
214 policy: DPolicy<G, WG>,
215 context: DContext<G, WG>,
216 ) -> Result<bool, Problem> {
217 let none_of = policy.none_of().collect::<Vec<_>>();
221 if none_of
222 .into_iter()
223 .map(|matcher| self.is_satisfied_matcher(matcher, context.clone()))
224 .collect::<FuturesUnordered<_>>()
225 .try_collect::<Vec<_>>()
226 .await?
227 .contains(&true)
228 {
229 debug!("Policy has a noneOf matcher that is satisfied.");
230 return Ok(false);
231 }
232
233 let all_of = policy.all_of().collect::<Vec<_>>();
235 if all_of
236 .into_iter()
237 .map(|matcher| self.is_satisfied_matcher(matcher, context.clone()))
238 .collect::<FuturesUnordered<_>>()
239 .try_collect::<Vec<_>>()
240 .await?
241 .contains(&false)
242 {
243 debug!("Policy has an allOf matcher that is not satisfied.");
244 return Ok(false);
245 }
246
247 let any_of = policy.any_of().collect::<Vec<_>>();
249 if any_of
250 .into_iter()
251 .map(|matcher| self.is_satisfied_matcher(matcher, context.clone()))
252 .collect::<FuturesUnordered<_>>()
253 .try_collect::<Vec<_>>()
254 .await?
256 .contains(&true)
257 {
258 debug!("Policy has an anyOf matcher that is satisfied.");
259 return Ok(true);
260 }
261
262 Ok(policy.has_any(&ns::acp::allOf) && !policy.has_any(&ns::acp::anyOf))
271 }
272
273 async fn is_satisfied_matcher(
284 &self,
285 matcher: DMatcher<G, WG>,
286 context: DContext<G, WG>,
287 ) -> Result<bool, Problem> {
288 let attribute_match_futs = self
289 .attribute_match_svcs
290 .iter()
291 .map(|(attribute, svc)| {
292 (
293 attribute.clone(),
295 matcher
297 .get_all(attribute)
298 .into_term_owning::<ArcTerm>()
299 .map(|value| {
300 svc.clone().oneshot(AttributeMatchRequest {
301 value,
302 context: context.clone(),
303 })
304 })
305 .collect::<FuturesUnordered<_>>(),
306 )
307 })
308 .filter(|(_, futs)| !futs.is_empty())
309 .collect::<HashMap<_, _>>();
310
311 if attribute_match_futs.is_empty() {
313 info!("Matcher doesn't define any known attribute.");
314 return Ok(false);
315 }
316
317 for (attribute, futs) in attribute_match_futs.into_iter() {
318 let mut is_match = false;
319 let mut last_error = None;
320
321 for fut in futs {
322 match fut.await {
323 Ok(true) => {
324 debug!("{:?} attribute matched", attribute);
325 is_match = true;
326 break;
327 }
328 Err(e) => {
329 info!(
330 "Unknown error in resolving attribute value match. Error:\n {}",
331 e
332 );
333 last_error = Some(e);
334 }
335 _ => {}
336 }
337 }
338
339 if !is_match {
340 if let Some(e) = last_error {
341 error!("Error in resolving attribute match.");
342 return Err(INTERNAL_MATCHER_ERROR
343 .new_problem_builder()
344 .source(e)
345 .finish());
346 }
347
348 info!(
349 "None of attribute values matched against context. attribute: {:?}",
350 attribute
351 );
352 return Ok(false);
353 }
354 }
355
356 Ok(true)
360 }
361}
362
363define_anon_problem_types!(
364 INTERNAL_MATCHER_ERROR: ("Internal matcher error.");
366);