Skip to main content

dpp_crypto/access/
filter.rs

1//! Access tier filter engine — applies a `SectorAccessPolicy` to a JSON document.
2
3use dpp_domain::AccessTier;
4
5use super::policy::SectorAccessPolicy;
6
7/// The result of a policy evaluation.
8#[derive(Debug, Clone)]
9pub struct PolicyDecision {
10    /// The caller's effective access tier.
11    pub granted_tier: AccessTier,
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 caller's tier.
19///
20/// **Path-aware and recursive:** every key — at every nesting depth, including
21/// inside arrays of objects — is classified by [`SectorAccessPolicy::tier_for_field`]
22/// and removed when its tier exceeds the caller's. A field kept at one level is
23/// still descended into, so a Confidential field nested inside an otherwise-public
24/// object cannot leak. Redacted keys are reported as dotted paths
25/// (e.g. `sectorData.svhcSubstances`, `criticalRawMaterials[0].casNumber`).
26///
27/// Non-object/array inputs are returned unchanged.
28pub fn filter_by_access_tier(
29    data: &serde_json::Value,
30    policy: &SectorAccessPolicy,
31    caller_tier: AccessTier,
32) -> PolicyDecision {
33    let mut redacted_fields = Vec::new();
34    let filtered_data = filter_value(data, policy, caller_tier, "", &mut redacted_fields);
35    PolicyDecision {
36        granted_tier: caller_tier,
37        redacted_fields,
38        filtered_data,
39    }
40}
41
42fn filter_value(
43    data: &serde_json::Value,
44    policy: &SectorAccessPolicy,
45    caller_tier: AccessTier,
46    prefix: &str,
47    redacted: &mut Vec<String>,
48) -> serde_json::Value {
49    match data {
50        serde_json::Value::Object(map) => {
51            let mut filtered = serde_json::Map::new();
52            for (key, value) in map {
53                let path = if prefix.is_empty() {
54                    key.clone()
55                } else {
56                    format!("{prefix}.{key}")
57                };
58                if caller_tier >= policy.tier_for_field(key) {
59                    filtered.insert(
60                        key.clone(),
61                        filter_value(value, policy, caller_tier, &path, redacted),
62                    );
63                } else {
64                    redacted.push(path);
65                }
66            }
67            serde_json::Value::Object(filtered)
68        }
69        serde_json::Value::Array(items) => serde_json::Value::Array(
70            items
71                .iter()
72                .enumerate()
73                .map(|(i, v)| {
74                    filter_value(v, policy, caller_tier, &format!("{prefix}[{i}]"), redacted)
75                })
76                .collect(),
77        ),
78        other => other.clone(),
79    }
80}