Skip to main content

zerodds_security/
access_control.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Access-Control-Plugin SPI (OMG DDS-Security 1.1 §8.4).
5//!
6//! Entscheidet pro Topic / pro Operation, ob ein authentifizierter
7//! Participant die Aktion ausfuehren darf. Eingaben:
8//! * Governance-XML — Topic-Level-Rules (Discovery, Publishing,
9//!   Subscribing, Encrypt-Flag).
10//! * Permissions-XML — wer darf was (signiert mit Permissions-CA).
11//!
12//! Die XML-Dateien werden als Properties uebergeben (Pfade). Das
13//! Plugin parst + validiert + cacht intern.
14//!
15//! zerodds-lint: allow no_dyn_in_safe
16//! (Plugin-SPI benötigt `Box<dyn AccessControlPlugin>`.)
17
18extern crate alloc;
19
20use alloc::boxed::Box;
21
22use crate::authentication::IdentityHandle;
23use crate::error::SecurityResult;
24use crate::properties::PropertyList;
25
26/// Opaker Handle fuer validierte Permissions. Erzeugt durch
27/// [`AccessControlPlugin::validate_local_permissions`] bzw.
28/// `validate_remote_permissions`.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
30pub struct PermissionsHandle(pub u64);
31
32/// Ob eine Aktion durchgefuehrt werden darf. Bewusst sparsam — keine
33/// Reason-String, weil der Reason an den Remote nichts verraten soll
34/// (Logging via [`crate::LoggingPlugin`]).
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum AccessDecision {
37    /// Erlaubt.
38    Permit,
39    /// Verweigert.
40    Deny,
41}
42
43impl AccessDecision {
44    /// `true` wenn `Permit`.
45    #[must_use]
46    pub fn is_permitted(self) -> bool {
47        matches!(self, Self::Permit)
48    }
49}
50
51/// Access-Control-Plugin-Trait (Spec §8.4.2.9).
52pub trait AccessControlPlugin: Send + Sync {
53    /// Validiert lokale Permissions (Governance.xml + Permissions.xml
54    /// + Signatur-Check gegen Permissions-CA).
55    ///
56    /// # Spec §8.4.2.9.1
57    fn validate_local_permissions(
58        &mut self,
59        local: IdentityHandle,
60        participant_guid: [u8; 16],
61        props: &PropertyList,
62    ) -> SecurityResult<PermissionsHandle>;
63
64    /// Validiert Remote-Permissions aus dem SEDP-Handshake.
65    ///
66    /// # Spec §8.4.2.9.2
67    fn validate_remote_permissions(
68        &mut self,
69        local: IdentityHandle,
70        remote: IdentityHandle,
71        remote_permissions_token: &[u8],
72        remote_credential: &[u8],
73    ) -> SecurityResult<PermissionsHandle>;
74
75    /// Darf dieser Participant einen DataWriter auf diesem Topic
76    /// erzeugen?
77    ///
78    /// # Spec §8.4.2.9.4 `check_create_datawriter`.
79    fn check_create_datawriter(
80        &self,
81        perms: PermissionsHandle,
82        topic_name: &str,
83    ) -> SecurityResult<AccessDecision>;
84
85    /// Darf dieser Participant einen DataReader auf diesem Topic
86    /// erzeugen?
87    ///
88    /// # Spec §8.4.2.9.5 `check_create_datareader`.
89    fn check_create_datareader(
90        &self,
91        perms: PermissionsHandle,
92        topic_name: &str,
93    ) -> SecurityResult<AccessDecision>;
94
95    /// Darf der lokale Reader die Publication des Remote matchen?
96    ///
97    /// # Spec §8.4.2.9.17 `check_remote_datawriter_match`.
98    fn check_remote_datawriter_match(
99        &self,
100        local_perms: PermissionsHandle,
101        remote_perms: PermissionsHandle,
102        topic_name: &str,
103    ) -> SecurityResult<AccessDecision>;
104
105    /// Spiegelbildlich: darf Remote-Reader unseren Writer matchen?
106    fn check_remote_datareader_match(
107        &self,
108        local_perms: PermissionsHandle,
109        remote_perms: PermissionsHandle,
110        topic_name: &str,
111    ) -> SecurityResult<AccessDecision>;
112
113    /// Plugin-Class-Id (z.B. "DDS:Access:Permissions:1.2") fuer SPDP-
114    /// Annoncierung.
115    fn plugin_class_id(&self) -> &str;
116
117    /// Spec §9.4.2.5: `check_create_participant`. Default: permit
118    /// (kein Plugin-spezifisches Filtern).
119    ///
120    /// # Errors
121    /// Implementations-spezifisch.
122    fn check_create_participant(
123        &self,
124        _local_perms: PermissionsHandle,
125        _domain_id: u32,
126    ) -> SecurityResult<AccessDecision> {
127        Ok(AccessDecision::Permit)
128    }
129
130    /// Spec §9.4.2.6: `check_remote_participant` — darf Remote-
131    /// Participant in unsere Domain joinen? Default: permit.
132    ///
133    /// # Errors
134    /// Implementations-spezifisch.
135    fn check_remote_participant(
136        &self,
137        _local_perms: PermissionsHandle,
138        _remote_perms: PermissionsHandle,
139        _domain_id: u32,
140    ) -> SecurityResult<AccessDecision> {
141        Ok(AccessDecision::Permit)
142    }
143
144    /// Spec §9.4.2.10: `check_create_topic` — darf der lokale
145    /// Subject ein Topic mit dem Namen erzeugen? Default: permit.
146    ///
147    /// # Errors
148    /// Implementations-spezifisch.
149    fn check_create_topic(
150        &self,
151        _local_perms: PermissionsHandle,
152        _topic_name: &str,
153    ) -> SecurityResult<AccessDecision> {
154        Ok(AccessDecision::Permit)
155    }
156
157    /// Spec §9.4.2.13: `get_permissions_token` — opaker
158    /// Permissions-Token fuer SPDP-Annoncierung
159    /// (`PID_PERMISSIONS_TOKEN` 0x1002). Default: leer.
160    ///
161    /// # Errors
162    /// Implementations-spezifisch.
163    fn get_permissions_token(
164        &self,
165        _local_perms: PermissionsHandle,
166    ) -> SecurityResult<alloc::vec::Vec<u8>> {
167        Ok(alloc::vec::Vec::new())
168    }
169
170    /// Spec §9.4.2.14: `get_permissions_credential_token` — opake
171    /// Credential, die im Authentication-Plugin via
172    /// `set_permissions_credential_and_token` weitergereicht wird.
173    /// Default: leer.
174    ///
175    /// # Errors
176    /// Implementations-spezifisch.
177    fn get_permissions_credential_token(
178        &self,
179        _local_perms: PermissionsHandle,
180    ) -> SecurityResult<alloc::vec::Vec<u8>> {
181        Ok(alloc::vec::Vec::new())
182    }
183}
184
185/// Factory-Alias.
186pub type AccessControlBox = Box<dyn AccessControlPlugin>;
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn access_decision_helper() {
194        assert!(AccessDecision::Permit.is_permitted());
195        assert!(!AccessDecision::Deny.is_permitted());
196    }
197}