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//! Decides per topic / per operation whether an authenticated
7//! participant may perform the action. Inputs:
8//! * Governance XML — topic-level rules (discovery, publishing,
9//!   subscribing, encrypt flag).
10//! * Permissions XML — who may do what (signed with the permissions CA).
11//!
12//! The XML files are passed as properties (paths). The
13//! plugin parses + validates + caches internally.
14//!
15//! zerodds-lint: allow no_dyn_in_safe
16//! (The plugin SPI needs `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/// Opaque handle for validated permissions. Created by
27/// [`AccessControlPlugin::validate_local_permissions`] or
28/// `validate_remote_permissions`.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
30pub struct PermissionsHandle(pub u64);
31
32/// Whether an action may be performed. Deliberately sparse — no
33/// reason string, because the reason must not reveal anything to the
34/// remote (logging via [`crate::LoggingPlugin`]).
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum AccessDecision {
37    /// Allowed.
38    Permit,
39    /// Denied.
40    Deny,
41}
42
43impl AccessDecision {
44    /// `true` if `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    /// Validates local permissions (Governance.xml + Permissions.xml
54    /// + signature check against the 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    /// Validates remote permissions from the 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    /// May this participant create a DataWriter on this topic?
76    ///
77    /// # Spec §8.4.2.9.4 `check_create_datawriter`.
78    fn check_create_datawriter(
79        &self,
80        perms: PermissionsHandle,
81        topic_name: &str,
82    ) -> SecurityResult<AccessDecision>;
83
84    /// May this participant create a DataReader on this topic?
85    ///
86    /// # Spec §8.4.2.9.5 `check_create_datareader`.
87    fn check_create_datareader(
88        &self,
89        perms: PermissionsHandle,
90        topic_name: &str,
91    ) -> SecurityResult<AccessDecision>;
92
93    /// May the local reader match the remote's publication?
94    ///
95    /// # Spec §8.4.2.9.17 `check_remote_datawriter_match`.
96    fn check_remote_datawriter_match(
97        &self,
98        local_perms: PermissionsHandle,
99        remote_perms: PermissionsHandle,
100        topic_name: &str,
101    ) -> SecurityResult<AccessDecision>;
102
103    /// Mirror image: may a remote reader match our writer?
104    fn check_remote_datareader_match(
105        &self,
106        local_perms: PermissionsHandle,
107        remote_perms: PermissionsHandle,
108        topic_name: &str,
109    ) -> SecurityResult<AccessDecision>;
110
111    /// Plugin class id (e.g. "DDS:Access:Permissions:1.2") for SPDP
112    /// announcing.
113    fn plugin_class_id(&self) -> &str;
114
115    /// Spec §9.4.2.5: `check_create_participant`. Default: permit
116    /// (no plugin-specific filtering).
117    ///
118    /// # Errors
119    /// Implementation-specific.
120    fn check_create_participant(
121        &self,
122        _local_perms: PermissionsHandle,
123        _domain_id: u32,
124    ) -> SecurityResult<AccessDecision> {
125        Ok(AccessDecision::Permit)
126    }
127
128    /// Spec §9.4.2.6: `check_remote_participant` — may the remote
129    /// participant join our domain? Default: permit.
130    ///
131    /// # Errors
132    /// Implementation-specific.
133    fn check_remote_participant(
134        &self,
135        _local_perms: PermissionsHandle,
136        _remote_perms: PermissionsHandle,
137        _domain_id: u32,
138    ) -> SecurityResult<AccessDecision> {
139        Ok(AccessDecision::Permit)
140    }
141
142    /// Spec §9.4.2.10: `check_create_topic` — may the local
143    /// subject create a topic with that name? Default: permit.
144    ///
145    /// # Errors
146    /// Implementation-specific.
147    fn check_create_topic(
148        &self,
149        _local_perms: PermissionsHandle,
150        _topic_name: &str,
151    ) -> SecurityResult<AccessDecision> {
152        Ok(AccessDecision::Permit)
153    }
154
155    /// Spec §9.4.2.13: `get_permissions_token` — opaque
156    /// permissions token for SPDP announcing
157    /// (`PID_PERMISSIONS_TOKEN` 0x1002). Default: empty.
158    ///
159    /// # Errors
160    /// Implementation-specific.
161    fn get_permissions_token(
162        &self,
163        _local_perms: PermissionsHandle,
164    ) -> SecurityResult<alloc::vec::Vec<u8>> {
165        Ok(alloc::vec::Vec::new())
166    }
167
168    /// Spec §9.4.2.14: `get_permissions_credential_token` — opaque
169    /// credential passed on in the authentication plugin via
170    /// `set_permissions_credential_and_token`.
171    /// Default: empty.
172    ///
173    /// # Errors
174    /// Implementation-specific.
175    fn get_permissions_credential_token(
176        &self,
177        _local_perms: PermissionsHandle,
178    ) -> SecurityResult<alloc::vec::Vec<u8>> {
179        Ok(alloc::vec::Vec::new())
180    }
181}
182
183/// Factory alias.
184pub type AccessControlBox = Box<dyn AccessControlPlugin>;
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn access_decision_helper() {
192        assert!(AccessDecision::Permit.is_permitted());
193        assert!(!AccessDecision::Deny.is_permitted());
194    }
195}