Skip to main content

dpp_crypto/access/
filter.rs

1//! Access filter engine — applies a `SectorAccessPolicy` to a JSON document.
2
3use dpp_domain::Audience;
4
5use super::policy::SectorAccessPolicy;
6
7/// The result of a policy evaluation.
8#[derive(Debug, Clone)]
9pub struct PolicyDecision {
10    /// The audience the document was filtered for.
11    pub audience: Audience,
12    /// Fields that were redacted (not visible to this caller).
13    pub redacted_fields: Vec<String>,
14    /// The filtered JSON document.
15    pub filtered_data: serde_json::Value,
16}
17
18/// Filter a JSON document according to the sector access policy and the
19/// caller's audience.
20///
21/// **Path-aware and recursive:** every key — at every nesting depth, including
22/// inside arrays of objects — is classified by [`SectorAccessPolicy::disclosure_for_field`]
23/// and removed when [`Audience::may_see`] says that audience may not see its
24/// class. A field kept at one level is still descended into, so a restricted
25/// field nested inside an otherwise-public object cannot leak.
26///
27/// Visibility is a lattice, not a threshold: an `Authority` is not a superset of
28/// a `LegitimateInterest` holder. Individual-item data (Annex XIII point 4) is
29/// withheld from authorities, and conformity evidence (point 3) is withheld from
30/// legitimate-interest holders. Redacted keys are reported as dotted paths
31/// (e.g. `sectorData.svhcSubstances`, `criticalRawMaterials[0].casNumber`).
32///
33/// Non-object/array inputs are returned unchanged.
34pub fn filter_by_audience(
35    data: &serde_json::Value,
36    policy: &SectorAccessPolicy,
37    audience: Audience,
38) -> PolicyDecision {
39    let mut redacted_fields = Vec::new();
40    let filtered_data = filter_value(data, policy, audience, "", &mut redacted_fields);
41    PolicyDecision {
42        audience,
43        redacted_fields,
44        filtered_data,
45    }
46}
47
48fn filter_value(
49    data: &serde_json::Value,
50    policy: &SectorAccessPolicy,
51    audience: Audience,
52    prefix: &str,
53    redacted: &mut Vec<String>,
54) -> serde_json::Value {
55    match data {
56        serde_json::Value::Object(map) => {
57            let mut filtered = serde_json::Map::new();
58            for (key, value) in map {
59                let path = if prefix.is_empty() {
60                    key.clone()
61                } else {
62                    format!("{prefix}.{key}")
63                };
64                if audience.may_see(policy.disclosure_for_field(key)) {
65                    filtered.insert(
66                        key.clone(),
67                        filter_value(value, policy, audience, &path, redacted),
68                    );
69                } else {
70                    redacted.push(path);
71                }
72            }
73            serde_json::Value::Object(filtered)
74        }
75        serde_json::Value::Array(items) => serde_json::Value::Array(
76            items
77                .iter()
78                .enumerate()
79                .map(|(i, v)| {
80                    filter_value(v, policy, audience, &format!("{prefix}[{i}]"), redacted)
81                })
82                .collect(),
83        ),
84        other => other.clone(),
85    }
86}