1use crate::{CommandPermission, Session};
32use orcs_types::SignalScope;
33
34pub trait PermissionPolicy: Send + Sync {
81 fn can_signal(&self, session: &Session, scope: &SignalScope) -> bool;
83
84 fn can_destructive(&self, session: &Session, action: &str) -> bool;
89
90 fn can_execute_command(&self, session: &Session, cmd: &str) -> bool;
92
93 fn can_spawn_child(&self, session: &Session) -> bool;
95
96 fn can_spawn_runner(&self, session: &Session) -> bool;
98
99 fn check_command_permission(&self, session: &Session, cmd: &str) -> CommandPermission {
114 if self.can_execute_command(session, cmd) {
115 CommandPermission::Allowed
116 } else {
117 CommandPermission::Denied("permission denied".to_string())
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use orcs_types::{ChannelId, Principal, PrincipalId};
126
127 struct PermissivePolicy;
128
129 impl PermissionPolicy for PermissivePolicy {
130 fn can_signal(&self, _session: &Session, _scope: &SignalScope) -> bool {
131 true
132 }
133 fn can_destructive(&self, _session: &Session, _action: &str) -> bool {
134 true
135 }
136 fn can_execute_command(&self, _session: &Session, _cmd: &str) -> bool {
137 true
138 }
139 fn can_spawn_child(&self, _session: &Session) -> bool {
140 true
141 }
142 fn can_spawn_runner(&self, _session: &Session) -> bool {
143 true
144 }
145 }
146
147 struct StrictPolicy;
148
149 impl PermissionPolicy for StrictPolicy {
150 fn can_signal(&self, session: &Session, scope: &SignalScope) -> bool {
151 match scope {
152 SignalScope::Global => session.is_elevated(),
153 _ => true,
154 }
155 }
156 fn can_destructive(&self, session: &Session, _action: &str) -> bool {
157 session.is_elevated()
158 }
159 fn can_execute_command(&self, session: &Session, _cmd: &str) -> bool {
160 session.is_elevated()
161 }
162 fn can_spawn_child(&self, session: &Session) -> bool {
163 session.is_elevated()
164 }
165 fn can_spawn_runner(&self, session: &Session) -> bool {
166 session.is_elevated()
167 }
168 }
169
170 fn standard_session() -> Session {
171 Session::new(Principal::User(PrincipalId::new()))
172 }
173
174 fn elevated_session() -> Session {
175 standard_session().elevate(std::time::Duration::from_secs(60))
176 }
177
178 #[test]
179 fn permissive_allows_everything() {
180 let policy = PermissivePolicy;
181 let session = standard_session();
182
183 assert!(policy.can_signal(&session, &SignalScope::Global));
184 assert!(policy.can_destructive(&session, "rm -rf"));
185 assert!(policy.can_execute_command(&session, "ls"));
186 assert!(policy.can_spawn_child(&session));
187 assert!(policy.can_spawn_runner(&session));
188 }
189
190 #[test]
191 fn strict_denies_standard_session() {
192 let policy = StrictPolicy;
193 let session = standard_session();
194
195 assert!(!policy.can_signal(&session, &SignalScope::Global));
196 assert!(!policy.can_destructive(&session, "rm -rf"));
197 assert!(!policy.can_execute_command(&session, "ls"));
198 assert!(!policy.can_spawn_child(&session));
199 assert!(!policy.can_spawn_runner(&session));
200 }
201
202 #[test]
203 fn strict_allows_elevated_session() {
204 let policy = StrictPolicy;
205 let session = elevated_session();
206
207 assert!(policy.can_signal(&session, &SignalScope::Global));
208 assert!(policy.can_destructive(&session, "rm -rf"));
209 assert!(policy.can_execute_command(&session, "ls"));
210 assert!(policy.can_spawn_child(&session));
211 assert!(policy.can_spawn_runner(&session));
212 }
213
214 #[test]
215 fn strict_allows_channel_signal_for_standard() {
216 let policy = StrictPolicy;
217 let session = standard_session();
218 let channel = ChannelId::new();
219
220 assert!(policy.can_signal(&session, &SignalScope::Channel(channel)));
221 }
222
223 #[test]
224 fn default_check_command_permission_wraps_can_execute() {
225 let policy = PermissivePolicy;
226 let session = standard_session();
227
228 let result = policy.check_command_permission(&session, "ls -la");
229 assert!(result.is_allowed());
230 }
231
232 #[test]
233 fn default_check_command_permission_denied_when_not_allowed() {
234 let policy = StrictPolicy;
235 let session = standard_session();
236
237 let result = policy.check_command_permission(&session, "ls -la");
238 assert!(result.is_denied());
239 }
240
241 #[test]
242 fn trait_object_works() {
243 let policy: Box<dyn PermissionPolicy> = Box::new(PermissivePolicy);
244 let session = standard_session();
245
246 assert!(policy.can_execute_command(&session, "ls"));
247 }
248}