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 fn preset(name: &str) -> Option<CapabilitySet> {
86 match name {
87 "operator" => Some(
88 ["exec", "session.read", "session.manage"]
89 .into_iter()
90 .collect(),
91 ),
92 "read-only" => Some(["session.read"].into_iter().collect()),
93 "full-control" => Some(CapabilitySet::wildcard()),
94 _ => None,
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn test_empty_set_satisfies_nothing() {
104 let set = CapabilitySet::new();
105 assert!(set.is_empty());
106 assert!(!set.satisfies("exec"));
107 assert!(!set.is_wildcard());
108 }
109
110 #[test]
111 fn test_wildcard_satisfies_everything() {
112 let set = CapabilitySet::wildcard();
113 assert!(set.is_wildcard());
114 assert!(set.satisfies("exec"));
115 assert!(set.satisfies("session.read"));
116 assert!(set.satisfies("anything.at.all"));
117 }
118
119 #[test]
120 fn test_membership_is_exact() {
121 let set: CapabilitySet = ["session.read"].into_iter().collect();
122 assert!(set.satisfies("session.read"));
123 assert!(!set.satisfies("session.manage"));
125 assert!(!set.satisfies("session"));
126 assert!(!set.is_wildcard());
127 }
128
129 #[test]
130 fn test_multiple_capabilities() {
131 let set: CapabilitySet = ["exec", "session.read", "session.manage"]
132 .into_iter()
133 .collect();
134 assert_eq!(set.len(), 3);
135 assert!(set.satisfies("exec"));
136 assert!(set.satisfies("session.read"));
137 assert!(set.satisfies("session.manage"));
138 assert!(!set.satisfies("fs.read"));
139 }
140
141 #[test]
142 fn test_insert() {
143 let mut set = CapabilitySet::new();
144 set.insert("exec");
145 assert!(set.satisfies("exec"));
146 assert_eq!(set.len(), 1);
147 }
148
149 #[test]
150 fn test_presets() {
151 let operator = preset("operator").unwrap();
152 assert!(operator.satisfies("exec"));
153 assert!(operator.satisfies("session.read"));
154 assert!(operator.satisfies("session.manage"));
155 assert!(!operator.is_wildcard());
156
157 let read_only = preset("read-only").unwrap();
158 assert!(read_only.satisfies("session.read"));
159 assert!(!read_only.satisfies("session.manage"));
160 assert!(!read_only.satisfies("exec"));
161
162 let full = preset("full-control").unwrap();
163 assert!(full.is_wildcard());
164 assert!(full.satisfies("anything"));
165
166 assert!(preset("nonexistent").is_none());
167 }
168}