origin_platform/
process.rs1use async_trait::async_trait;
8use origin_domain::{AppError, Result};
9use std::fmt::Debug;
10use std::path::Path;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct ProcessOutput {
15 pub status: i32,
16 pub stdout: Vec<u8>,
17 pub stderr: Vec<u8>,
18}
19
20impl ProcessOutput {
21 pub fn success(&self) -> bool {
22 self.status == 0
23 }
24}
25
26#[derive(Debug, Clone, Default)]
33pub struct ProcessAllowlist {
34 programs: Vec<String>,
35}
36
37impl ProcessAllowlist {
38 pub fn new(programs: impl IntoIterator<Item = impl Into<String>>) -> Self {
39 Self {
40 programs: programs.into_iter().map(Into::into).collect(),
41 }
42 }
43
44 pub fn allows(&self, program: &str) -> bool {
45 self.programs.iter().any(|p| p == program)
46 }
47
48 pub fn check(&self, program: &str) -> Result<()> {
54 if self.allows(program) {
55 Ok(())
56 } else {
57 Err(AppError::Permission(format!(
58 "program `{program}` is not in the process allowlist"
59 )))
60 }
61 }
62
63 pub fn entries(&self) -> &[String] {
65 &self.programs
66 }
67}
68
69#[async_trait]
74pub trait ProcessRunner: Debug + Send + Sync + 'static {
75 async fn run(&self, program: &str, args: &[String], cwd: &Path) -> Result<ProcessOutput>;
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn allowlist_rejects_an_unlisted_program_with_permission_error() {
86 let allowlist = ProcessAllowlist::new(["git"]);
87 let result = allowlist.check("rm");
88
89 match result {
90 Err(AppError::Permission(_)) => {} other => panic!("expected Permission error, got {other:?}"),
92 }
93 }
94
95 #[test]
96 fn allowlist_permits_a_listed_program() {
97 let allowlist = ProcessAllowlist::new(["git", "code"]);
98 assert!(allowlist.allows("git"));
99 assert!(allowlist.allows("code"));
100 allowlist
101 .check("git")
102 .expect("listed program must be allowed");
103 }
104}