acp/
engine.rs

1//! I define an implementation of acp engine.
2//!
3
4use 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
30// fn sy<T: Send>(v: T) -> T {
31//     v
32// }
33
34/// Alias for type of set of access modes.
35pub type AccessGrantSet = HashSet<HAccessMode<ArcTerm>>;
36
37/// [`AcpEngine`] resolves permissions to access controlled resources
38/// in conformance with ACP access control resolution algorithm
39pub struct AcpEngine<G, WG>
40where
41    G: InfallibleGraph,
42    WG: Borrow<G> + Debug,
43{
44    /// Attribute match services.
45    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
95// TODO should refactor logic into respective models.
96
97impl<G, WG> AcpEngine<G, WG>
98where
99    G: InfallibleGraph + Clone,
100    WG: Borrow<G> + Clone + Debug + Send + Sync,
101{
102    /// Get a new [`AcpEngine] with given attribute match services.
103    #[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    /// Get the list of supported context attributes.
113    #[inline]
114    pub fn supported_attrs(&self) -> impl Iterator<Item = ArcTerm> + '_ {
115        self.attribute_match_svcs.keys().cloned()
116    }
117
118    /// Resolve [access control](https://solid.github.io/authorization-panel/acp-specification/#resolved-access-control)
119    ///  for given context against given acrs.
120    /// Returns grant graph on successful resolution.
121    ///
122    /// >  An ACP engine MUST grant exactly those Access Modes allowed by Effective Policies.
123    #[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        // Gather effective policies.
131        let effective_policies = self.gather_effective_policies(opt_acr, ancestor_acrs);
132
133        // Resolve allowed access modes.
134        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    /// Gather [effective policies](https://solid.github.io/authorization-panel/acp-specification/#effective-policies).
143    ///
144    /// > Effective Policies are the Policies controlling access to a resource.
145    /// > A Policy MUST control access to a resource if:
146    ///
147    /// > - it is applied by an Access Control of an ACR of the resource; or,
148    /// > - it is applied by a member Access Control of an ACR of an ancestor of the resource.
149    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        // Direct access controls of own ACR.
155        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        // Inherited member access controls fro ancestor ACRs.
162        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    /// Resolves granted access modes.
176    /// >    An Access Mode MUST be granted if and only if in the set of Effective Policies:
177    /// > - a satisfied policy allows the Access Mode; and,
178    /// > - no satisfied policy denies it.
179    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        // Gather allowed and denied access modes from satisfied policies
188        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        // Deny overrules allow.
199        denied_access_modes.iter().for_each(|mode| {
200            allowed_access_modes.remove(mode);
201        });
202
203        Ok(allowed_access_modes)
204    }
205
206    /// Resolves if policy is satisfied.
207    /// > A Policy MUST be satisfied if and only if:
208    /// > - it references at least one Matcher via an acp:allOf or acp:anyOf property; and,
209    /// > - all of its acp:allOf Matchers are satisfied; and,
210    /// > - at least one of its acp:anyOf Matchers is satisfied; and,
211    /// > - none of its acp:noneOf Matchers are satisfied.
212    async fn is_satisfied_policy(
213        &self,
214        policy: DPolicy<G, WG>,
215        context: DContext<G, WG>,
216    ) -> Result<bool, Problem> {
217        // If any 'none of' matcher is satisfied then the policy is not satisfied.
218        // For now evaluate all matchers without short circuit,
219        // to simplify handling of fallible evaluation.
220        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        // If any 'all of' matcher is not satisfied then the policy is not satisfied.
234        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        // If any 'any of' matcher is satisfied then the policy is satisfied.
248        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            // Conservative on evaluation error, even though  few cases can be resolved.
255            .await?
256            .contains(&true)
257        {
258            debug!("Policy has an anyOf matcher that is satisfied.");
259            return Ok(true);
260        }
261
262        // At this point there are
263        // - no satisfied 'none of' matchers,
264        // - no unsatisfied 'all of' matchers and
265        // - no satisfied 'any of' matchers.
266
267        // Hence, the policy is satisfied if it has
268        // - an 'all of' condition and
269        // - no 'any of' condition.
270        Ok(policy.has_any(&ns::acp::allOf) && !policy.has_any(&ns::acp::anyOf))
271    }
272
273    /// Resolves if a matcher is satisfied against given context.
274    ///
275    /// > A Matcher MUST be satisfied if and only if:
276    ///
277    /// > - it defines at least one attribute; and,
278    /// > - at least one value of each defined attribute matches the Context.
279    ///
280    /// > ACP engines MUST match the context attributes defined
281    /// by this specification according to IRI equality and
282    /// literal term equality.
283    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
294                    attribute.clone(),
295                    // Value matching futures.
296                    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        // An empty matcher is never satisfied.
312        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        // At this point, the matcher is satisfied because
357        // - there was at least one defined attribute and
358        // - at least one value of each defined attribute matched the context.
359        Ok(true)
360    }
361}
362
363define_anon_problem_types!(
364    /// Internal matvher error.
365    INTERNAL_MATCHER_ERROR: ("Internal matcher error.");
366);