shell_tunnel/security/
capability.rs1use std::collections::HashSet;
13
14pub const WILDCARD: &str = "*";
16
17#[derive(Debug, Clone, Default, PartialEq, Eq)]
22pub struct CapabilitySet(HashSet<String>);
23
24impl CapabilitySet {
25 pub fn new() -> Self {
27 Self(HashSet::new())
28 }
29
30 pub fn wildcard() -> Self {
35 let mut set = HashSet::new();
36 set.insert(WILDCARD.to_string());
37 Self(set)
38 }
39
40 pub fn satisfies(&self, required: &str) -> bool {
45 self.0.contains(WILDCARD) || self.0.contains(required)
46 }
47
48 pub fn is_wildcard(&self) -> bool {
50 self.0.contains(WILDCARD)
51 }
52
53 pub fn insert(&mut self, capability: impl Into<String>) {
55 self.0.insert(capability.into());
56 }
57
58 pub fn len(&self) -> usize {
60 self.0.len()
61 }
62
63 pub fn is_empty(&self) -> bool {
65 self.0.is_empty()
66 }
67
68 pub fn iter(&self) -> impl Iterator<Item = &String> {
70 self.0.iter()
71 }
72}
73
74impl<S: Into<String>> FromIterator<S> for CapabilitySet {
75 fn from_iter<I: IntoIterator<Item = S>>(iter: I) -> Self {
76 Self(iter.into_iter().map(Into::into).collect())
77 }
78}
79
80pub const KNOWN_CAPABILITIES: &[&str] = &[
91 "exec",
92 "session.read",
93 "session.manage",
94 "fs.read",
95 "fs.write",
96];
97
98pub fn preset(name: &str) -> Option<CapabilitySet> {
104 match name {
105 "operator" => Some(
106 ["exec", "session.read", "session.manage"]
107 .into_iter()
108 .collect(),
109 ),
110 "read-only" => Some(["session.read"].into_iter().collect()),
111 "full-control" => Some(CapabilitySet::wildcard()),
112 _ => None,
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_empty_set_satisfies_nothing() {
122 let set = CapabilitySet::new();
123 assert!(set.is_empty());
124 assert!(!set.satisfies("exec"));
125 assert!(!set.is_wildcard());
126 }
127
128 #[test]
129 fn test_wildcard_satisfies_everything() {
130 let set = CapabilitySet::wildcard();
131 assert!(set.is_wildcard());
132 assert!(set.satisfies("exec"));
133 assert!(set.satisfies("session.read"));
134 assert!(set.satisfies("anything.at.all"));
135 }
136
137 #[test]
138 fn test_membership_is_exact() {
139 let set: CapabilitySet = ["session.read"].into_iter().collect();
140 assert!(set.satisfies("session.read"));
141 assert!(!set.satisfies("session.manage"));
143 assert!(!set.satisfies("session"));
144 assert!(!set.is_wildcard());
145 }
146
147 #[test]
148 fn test_multiple_capabilities() {
149 let set: CapabilitySet = ["exec", "session.read", "session.manage"]
150 .into_iter()
151 .collect();
152 assert_eq!(set.len(), 3);
153 assert!(set.satisfies("exec"));
154 assert!(set.satisfies("session.read"));
155 assert!(set.satisfies("session.manage"));
156 assert!(!set.satisfies("fs.read"));
157 }
158
159 #[test]
160 fn test_insert() {
161 let mut set = CapabilitySet::new();
162 set.insert("exec");
163 assert!(set.satisfies("exec"));
164 assert_eq!(set.len(), 1);
165 }
166
167 #[test]
168 fn test_presets() {
169 let operator = preset("operator").unwrap();
170 assert!(operator.satisfies("exec"));
171 assert!(operator.satisfies("session.read"));
172 assert!(operator.satisfies("session.manage"));
173 assert!(!operator.is_wildcard());
174
175 let read_only = preset("read-only").unwrap();
176 assert!(read_only.satisfies("session.read"));
177 assert!(!read_only.satisfies("session.manage"));
178 assert!(!read_only.satisfies("exec"));
179
180 let full = preset("full-control").unwrap();
181 assert!(full.is_wildcard());
182 assert!(full.satisfies("anything"));
183
184 assert!(preset("nonexistent").is_none());
185 }
186}