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}