Skip to main content

ldp_protocol/types/
trust.rs

1//! LDP trust domain types.
2//!
3//! Trust domains partition the delegate ecosystem into security boundaries.
4//! Cross-domain communication requires explicit policy approval.
5
6use serde::{Deserialize, Serialize};
7
8/// A trust domain — a named security boundary for delegates.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
10pub struct TrustDomain {
11    /// Domain name (e.g. "acme-prod", "research-sandbox").
12    pub name: String,
13
14    /// Whether cross-domain requests are allowed from this domain.
15    pub allow_cross_domain: bool,
16
17    /// Domains explicitly trusted for cross-domain communication.
18    #[serde(default)]
19    pub trusted_peers: Vec<String>,
20}
21
22impl Default for TrustDomain {
23    fn default() -> Self {
24        Self::new("default")
25    }
26}
27
28impl TrustDomain {
29    /// Create a new trust domain.
30    pub fn new(name: impl Into<String>) -> Self {
31        let name = name.into();
32        assert!(!name.is_empty(), "Trust domain name must not be empty");
33        Self {
34            name,
35            allow_cross_domain: false,
36            trusted_peers: Vec::new(),
37        }
38    }
39
40    /// Check if this domain trusts a peer domain.
41    pub fn trusts(&self, peer: &str) -> bool {
42        if self.name == peer {
43            return true; // Same domain always trusted.
44        }
45        self.allow_cross_domain && self.trusted_peers.contains(&peer.to_string())
46    }
47
48    /// Check if two domains mutually trust each other.
49    pub fn mutually_trusts(&self, peer: &TrustDomain) -> bool {
50        self.trusts(&peer.name) && peer.trusts(&self.name)
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn same_domain_always_trusted() {
60        let domain = TrustDomain::new("acme-prod");
61        assert!(domain.trusts("acme-prod"));
62    }
63
64    #[test]
65    fn cross_domain_denied_by_default() {
66        let domain = TrustDomain::new("acme-prod");
67        assert!(!domain.trusts("external"));
68    }
69
70    #[test]
71    fn cross_domain_with_explicit_peer() {
72        let domain = TrustDomain {
73            name: "acme-prod".into(),
74            allow_cross_domain: true,
75            trusted_peers: vec!["partner-corp".into()],
76        };
77        assert!(domain.trusts("partner-corp"));
78        assert!(!domain.trusts("unknown-corp"));
79    }
80
81    #[test]
82    fn mutual_trust_requires_both_sides() {
83        let domain_a = TrustDomain {
84            name: "acme".into(),
85            allow_cross_domain: true,
86            trusted_peers: vec!["partner".into()],
87        };
88        let domain_b = TrustDomain {
89            name: "partner".into(),
90            allow_cross_domain: true,
91            trusted_peers: vec!["acme".into()],
92        };
93        let domain_c = TrustDomain {
94            name: "partner".into(),
95            allow_cross_domain: true,
96            trusted_peers: vec![],
97        };
98        assert!(domain_a.mutually_trusts(&domain_b));
99        assert!(!domain_a.mutually_trusts(&domain_c));
100    }
101
102    #[test]
103    fn same_domain_mutual_trust() {
104        let domain = TrustDomain::new("acme");
105        let other = TrustDomain::new("acme");
106        assert!(domain.mutually_trusts(&other));
107    }
108
109    #[test]
110    fn default_trust_domain() {
111        let domain = TrustDomain::default();
112        assert_eq!(domain.name, "default");
113        assert!(!domain.allow_cross_domain);
114    }
115
116    #[test]
117    #[should_panic(expected = "Trust domain name must not be empty")]
118    fn empty_name_panics() {
119        TrustDomain::new("");
120    }
121}