Skip to main content

styrene_rbac/
capability.rs

1//! Capability strings and per-tier capability sets.
2//!
3//! Capabilities are dot-separated strings (`chat.send`, `rpc.exec`).
4//! Each role tier has a cumulative set — higher tiers inherit all
5//! capabilities from tiers below.
6
7/// Capability string constants.
8///
9/// Organized by the minimum tier required to hold each capability.
10/// Orthogonal capabilities (`vpn.handshake`, `relay.reject`) are not
11/// included in any tier and require explicit grants.
12pub struct Capability;
13
14impl Capability {
15    // ── Peer (10) ──────────────────────────────────────────────
16    pub const CHAT_SEND: &str = "chat.send";
17    pub const CHAT_RECEIVE: &str = "chat.receive";
18    pub const PAGE_BROWSE: &str = "page.browse";
19    pub const RPC_PING: &str = "rpc.ping";
20    pub const RPC_STATUS: &str = "rpc.status";
21    pub const DATALINK_PING: &str = "datalink.ping";
22    pub const DATALINK_META: &str = "datalink.meta";
23    pub const DATALINK_INFO: &str = "datalink.info";
24    pub const DATALINK_STATUS: &str = "datalink.status";
25    pub const RELAY_REQUEST: &str = "relay.request";
26    pub const RELAY_LIST: &str = "relay.list";
27    pub const RELAY_TEARDOWN: &str = "relay.teardown";
28    pub const RELAY_ACCEPT: &str = "relay.accept";
29
30    // ── Monitor (20) ──────────────────────────────────────────
31    pub const RPC_INBOX_READ: &str = "rpc.inbox_read";
32    pub const WEB_READ: &str = "web.read";
33    pub const DATALINK_ESTABLISH: &str = "datalink.establish";
34    pub const DATALINK_SPEEDTEST: &str = "datalink.speedtest";
35
36    // ── Operator (30) ─────────────────────────────────────────
37    pub const RPC_CONFIG_UPDATE: &str = "rpc.config_update";
38    pub const TERMINAL_RESTRICTED: &str = "terminal.restricted";
39    pub const WEB_WRITE: &str = "web.write";
40    pub const RELAY_REQUEST_PERMANENT: &str = "relay.request_permanent";
41    pub const RELAY_ACCEPT_PERMANENT: &str = "relay.accept_permanent";
42    pub const RELAY_PRIORITIZE: &str = "relay.prioritize";
43    pub const RELAY_BRIDGE: &str = "relay.bridge";
44
45    // ── Admin (40) ────────────────────────────────────────────
46    pub const RPC_EXEC: &str = "rpc.exec";
47    pub const RPC_REBOOT: &str = "rpc.reboot";
48    pub const RPC_SELF_UPDATE: &str = "rpc.self_update";
49    pub const TERMINAL_FULL: &str = "terminal.full";
50    pub const ADAPTER_PROVISION: &str = "adapter.provision";
51    pub const RELAY_ADMIN: &str = "relay.admin";
52
53    // ── Tunnel (tiered) ─────────────────────────────────────────
54    /// View tunnel status and list active tunnels (Peer)
55    pub const TUNNEL_STATUS: &str = "tunnel.status";
56    /// Initiate or accept tunnel establishment (Operator)
57    pub const TUNNEL_ESTABLISH: &str = "tunnel.establish";
58    /// Tear down an active tunnel (Operator)
59    pub const TUNNEL_TEARDOWN: &str = "tunnel.teardown";
60
61    // ── Orthogonal (explicit grant only) ──────────────────────
62    pub const VPN_HANDSHAKE: &str = "vpn.handshake";
63    pub const RELAY_REJECT: &str = "relay.reject";
64    pub const I2P_PROXY: &str = "i2p.proxy";
65
66    // ── Agent-to-agent (aether) ───────────────────────────────
67    pub const AETHER_DELEGATE: &str = "aether.delegate";
68    pub const AETHER_QUERY: &str = "aether.query";
69    pub const AETHER_REPORT: &str = "aether.report";
70}
71
72/// All known capability strings (for validation).
73pub const ALL_CAPABILITIES: &[&str] = &[
74    // Peer
75    Capability::CHAT_SEND,
76    Capability::CHAT_RECEIVE,
77    Capability::PAGE_BROWSE,
78    Capability::RPC_PING,
79    Capability::RPC_STATUS,
80    Capability::DATALINK_PING,
81    Capability::DATALINK_META,
82    Capability::DATALINK_INFO,
83    Capability::DATALINK_STATUS,
84    Capability::RELAY_REQUEST,
85    Capability::RELAY_LIST,
86    Capability::RELAY_TEARDOWN,
87    Capability::RELAY_ACCEPT,
88    // Monitor
89    Capability::RPC_INBOX_READ,
90    Capability::WEB_READ,
91    Capability::DATALINK_ESTABLISH,
92    Capability::DATALINK_SPEEDTEST,
93    // Operator
94    Capability::RPC_CONFIG_UPDATE,
95    Capability::TERMINAL_RESTRICTED,
96    Capability::WEB_WRITE,
97    Capability::RELAY_REQUEST_PERMANENT,
98    Capability::RELAY_ACCEPT_PERMANENT,
99    Capability::RELAY_PRIORITIZE,
100    Capability::RELAY_BRIDGE,
101    // Admin
102    Capability::RPC_EXEC,
103    Capability::RPC_REBOOT,
104    Capability::RPC_SELF_UPDATE,
105    Capability::TERMINAL_FULL,
106    Capability::ADAPTER_PROVISION,
107    Capability::RELAY_ADMIN,
108    // Tunnel
109    Capability::TUNNEL_STATUS,
110    Capability::TUNNEL_ESTABLISH,
111    Capability::TUNNEL_TEARDOWN,
112    // Orthogonal
113    Capability::VPN_HANDSHAKE,
114    Capability::RELAY_REJECT,
115    Capability::I2P_PROXY,
116    // Aether
117    Capability::AETHER_DELEGATE,
118    Capability::AETHER_QUERY,
119    Capability::AETHER_REPORT,
120];
121
122/// Capabilities granted at the Peer tier (cumulative base).
123pub const PEER_CAPS: &[&str] = &[
124    Capability::CHAT_SEND,
125    Capability::CHAT_RECEIVE,
126    Capability::PAGE_BROWSE,
127    Capability::RPC_PING,
128    Capability::RPC_STATUS,
129    Capability::DATALINK_PING,
130    Capability::DATALINK_META,
131    Capability::DATALINK_INFO,
132    Capability::DATALINK_STATUS,
133    Capability::RELAY_REQUEST,
134    Capability::RELAY_LIST,
135    Capability::RELAY_TEARDOWN,
136    Capability::RELAY_ACCEPT,
137    // Tunnel: all peers can view tunnel status
138    Capability::TUNNEL_STATUS,
139    // Aether: all peers can query and report
140    Capability::AETHER_QUERY,
141    Capability::AETHER_REPORT,
142];
143
144/// Capabilities granted at the Monitor tier (includes Peer).
145pub const MONITOR_CAPS: &[&str] = &[
146    // Peer
147    Capability::CHAT_SEND,
148    Capability::CHAT_RECEIVE,
149    Capability::PAGE_BROWSE,
150    Capability::RPC_PING,
151    Capability::RPC_STATUS,
152    Capability::DATALINK_PING,
153    Capability::DATALINK_META,
154    Capability::DATALINK_INFO,
155    Capability::DATALINK_STATUS,
156    Capability::RELAY_REQUEST,
157    Capability::RELAY_LIST,
158    Capability::RELAY_TEARDOWN,
159    Capability::RELAY_ACCEPT,
160    Capability::TUNNEL_STATUS,
161    Capability::AETHER_QUERY,
162    Capability::AETHER_REPORT,
163    // Monitor
164    Capability::RPC_INBOX_READ,
165    Capability::WEB_READ,
166    Capability::DATALINK_ESTABLISH,
167    Capability::DATALINK_SPEEDTEST,
168];
169
170/// Capabilities granted at the Operator tier (includes Monitor).
171pub const OPERATOR_CAPS: &[&str] = &[
172    // Peer
173    Capability::CHAT_SEND,
174    Capability::CHAT_RECEIVE,
175    Capability::PAGE_BROWSE,
176    Capability::RPC_PING,
177    Capability::RPC_STATUS,
178    Capability::DATALINK_PING,
179    Capability::DATALINK_META,
180    Capability::DATALINK_INFO,
181    Capability::DATALINK_STATUS,
182    Capability::RELAY_REQUEST,
183    Capability::RELAY_LIST,
184    Capability::RELAY_TEARDOWN,
185    Capability::RELAY_ACCEPT,
186    Capability::TUNNEL_STATUS,
187    Capability::AETHER_QUERY,
188    Capability::AETHER_REPORT,
189    // Monitor
190    Capability::RPC_INBOX_READ,
191    Capability::WEB_READ,
192    Capability::DATALINK_ESTABLISH,
193    Capability::DATALINK_SPEEDTEST,
194    // Operator
195    Capability::RPC_CONFIG_UPDATE,
196    Capability::TERMINAL_RESTRICTED,
197    Capability::WEB_WRITE,
198    Capability::RELAY_REQUEST_PERMANENT,
199    Capability::RELAY_ACCEPT_PERMANENT,
200    Capability::RELAY_PRIORITIZE,
201    Capability::RELAY_BRIDGE,
202    Capability::TUNNEL_ESTABLISH,
203    Capability::TUNNEL_TEARDOWN,
204    // Operator can delegate
205    Capability::AETHER_DELEGATE,
206];
207
208/// Capabilities granted at the Admin tier (includes Operator).
209/// Note: `vpn.handshake` and `relay.reject` are intentionally excluded —
210/// they are orthogonal and require explicit grants.
211pub const ADMIN_CAPS: &[&str] = &[
212    // Peer
213    Capability::CHAT_SEND,
214    Capability::CHAT_RECEIVE,
215    Capability::PAGE_BROWSE,
216    Capability::RPC_PING,
217    Capability::RPC_STATUS,
218    Capability::DATALINK_PING,
219    Capability::DATALINK_META,
220    Capability::DATALINK_INFO,
221    Capability::DATALINK_STATUS,
222    Capability::RELAY_REQUEST,
223    Capability::RELAY_LIST,
224    Capability::RELAY_TEARDOWN,
225    Capability::RELAY_ACCEPT,
226    Capability::TUNNEL_STATUS,
227    Capability::AETHER_QUERY,
228    Capability::AETHER_REPORT,
229    // Monitor
230    Capability::RPC_INBOX_READ,
231    Capability::WEB_READ,
232    Capability::DATALINK_ESTABLISH,
233    Capability::DATALINK_SPEEDTEST,
234    // Operator
235    Capability::RPC_CONFIG_UPDATE,
236    Capability::TERMINAL_RESTRICTED,
237    Capability::WEB_WRITE,
238    Capability::RELAY_REQUEST_PERMANENT,
239    Capability::RELAY_ACCEPT_PERMANENT,
240    Capability::RELAY_PRIORITIZE,
241    Capability::RELAY_BRIDGE,
242    Capability::TUNNEL_ESTABLISH,
243    Capability::TUNNEL_TEARDOWN,
244    Capability::AETHER_DELEGATE,
245    // Admin
246    Capability::RPC_EXEC,
247    Capability::RPC_REBOOT,
248    Capability::RPC_SELF_UPDATE,
249    Capability::TERMINAL_FULL,
250    Capability::ADAPTER_PROVISION,
251    Capability::RELAY_ADMIN,
252];
253
254use crate::Role;
255
256/// Get the capability set for a given role.
257pub fn capabilities_for_role(role: Role) -> &'static [&'static str] {
258    match role {
259        Role::Blocked | Role::None => &[],
260        Role::Peer => PEER_CAPS,
261        Role::Monitor => MONITOR_CAPS,
262        Role::Operator => OPERATOR_CAPS,
263        Role::Admin => ADMIN_CAPS,
264    }
265}
266
267/// Check whether a capability string is a known capability.
268pub fn is_valid_capability(cap: &str) -> bool {
269    ALL_CAPABILITIES.contains(&cap)
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[test]
277    fn cumulative_inclusion() {
278        // Every peer cap must appear in monitor, operator, and admin sets.
279        for cap in PEER_CAPS {
280            assert!(MONITOR_CAPS.contains(cap), "monitor missing peer cap: {cap}");
281            assert!(OPERATOR_CAPS.contains(cap), "operator missing peer cap: {cap}");
282            assert!(ADMIN_CAPS.contains(cap), "admin missing peer cap: {cap}");
283        }
284        for cap in MONITOR_CAPS {
285            assert!(OPERATOR_CAPS.contains(cap), "operator missing monitor cap: {cap}");
286            assert!(ADMIN_CAPS.contains(cap), "admin missing monitor cap: {cap}");
287        }
288        for cap in OPERATOR_CAPS {
289            assert!(ADMIN_CAPS.contains(cap), "admin missing operator cap: {cap}");
290        }
291    }
292
293    #[test]
294    fn orthogonal_not_in_admin() {
295        assert!(!ADMIN_CAPS.contains(&Capability::VPN_HANDSHAKE));
296        assert!(!ADMIN_CAPS.contains(&Capability::RELAY_REJECT));
297    }
298
299    #[test]
300    fn aether_caps_in_hierarchy() {
301        assert!(PEER_CAPS.contains(&Capability::AETHER_QUERY));
302        assert!(PEER_CAPS.contains(&Capability::AETHER_REPORT));
303        assert!(!PEER_CAPS.contains(&Capability::AETHER_DELEGATE));
304        assert!(OPERATOR_CAPS.contains(&Capability::AETHER_DELEGATE));
305    }
306
307    #[test]
308    fn all_capabilities_contains_everything() {
309        for cap in ADMIN_CAPS {
310            assert!(ALL_CAPABILITIES.contains(cap), "ALL missing admin cap: {cap}");
311        }
312        assert!(ALL_CAPABILITIES.contains(&Capability::VPN_HANDSHAKE));
313        assert!(ALL_CAPABILITIES.contains(&Capability::RELAY_REJECT));
314    }
315
316    #[test]
317    fn tunnel_caps_in_hierarchy() {
318        // tunnel.status is Peer-level
319        assert!(PEER_CAPS.contains(&Capability::TUNNEL_STATUS));
320        assert!(MONITOR_CAPS.contains(&Capability::TUNNEL_STATUS));
321        assert!(OPERATOR_CAPS.contains(&Capability::TUNNEL_STATUS));
322        assert!(ADMIN_CAPS.contains(&Capability::TUNNEL_STATUS));
323
324        // tunnel.establish and tunnel.teardown are Operator-level
325        assert!(!PEER_CAPS.contains(&Capability::TUNNEL_ESTABLISH));
326        assert!(!MONITOR_CAPS.contains(&Capability::TUNNEL_ESTABLISH));
327        assert!(OPERATOR_CAPS.contains(&Capability::TUNNEL_ESTABLISH));
328        assert!(ADMIN_CAPS.contains(&Capability::TUNNEL_ESTABLISH));
329
330        assert!(!PEER_CAPS.contains(&Capability::TUNNEL_TEARDOWN));
331        assert!(OPERATOR_CAPS.contains(&Capability::TUNNEL_TEARDOWN));
332        assert!(ADMIN_CAPS.contains(&Capability::TUNNEL_TEARDOWN));
333    }
334
335    #[test]
336    fn capabilities_for_blocked_is_empty() {
337        assert!(capabilities_for_role(Role::Blocked).is_empty());
338        assert!(capabilities_for_role(Role::None).is_empty());
339    }
340}