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}