1use crate::package::config::PackagePermissions;
2use anyhow::{bail, Result};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Permission {
7 Process,
8 Network,
9 Auth,
10 Storage,
11 Pipeline,
12 Routes,
13 Events,
14 Log,
15 Config,
16 Scheduler,
17 Ui,
18}
19
20impl std::fmt::Display for Permission {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 match self {
23 Self::Process => write!(f, "process"),
24 Self::Network => write!(f, "network"),
25 Self::Auth => write!(f, "auth"),
26 Self::Storage => write!(f, "storage"),
27 Self::Pipeline => write!(f, "pipeline"),
28 Self::Routes => write!(f, "routes"),
29 Self::Events => write!(f, "events"),
30 Self::Log => write!(f, "log"),
31 Self::Config => write!(f, "config"),
32 Self::Scheduler => write!(f, "scheduler"),
33 Self::Ui => write!(f, "ui"),
34 }
35 }
36}
37
38pub struct PermissionChecker {
40 package_name: String,
41 permissions: PackagePermissions,
42}
43
44impl PermissionChecker {
45 pub fn new(package_name: &str, permissions: PackagePermissions) -> Self {
46 Self {
47 package_name: package_name.to_string(),
48 permissions,
49 }
50 }
51
52 pub fn check(&self, perm: Permission) -> Result<()> {
54 let allowed = match perm {
55 Permission::Process => self.permissions.process,
56 Permission::Network => self.permissions.network,
57 Permission::Auth => self.permissions.auth,
58 Permission::Storage => self.permissions.storage,
59 Permission::Pipeline => self.permissions.pipeline,
60 Permission::Routes => self.permissions.routes,
61 Permission::Events => self.permissions.events,
62 Permission::Log => self.permissions.log,
63 Permission::Config => self.permissions.config,
64 Permission::Scheduler => self.permissions.scheduler,
65 Permission::Ui => self.permissions.ui,
66 };
67
68 if allowed {
69 Ok(())
70 } else {
71 bail!(
72 "Package '{}' does not have '{}' permission. Declare it in package.toml [permissions].",
73 self.package_name,
74 perm
75 )
76 }
77 }
78}
79
80impl Permission {
81 pub const ALL: [Permission; 11] = [
83 Permission::Process,
84 Permission::Network,
85 Permission::Auth,
86 Permission::Storage,
87 Permission::Pipeline,
88 Permission::Routes,
89 Permission::Events,
90 Permission::Log,
91 Permission::Config,
92 Permission::Scheduler,
93 Permission::Ui,
94 ];
95}
96
97pub fn escalated_permissions(
101 old: &PackagePermissions,
102 new: &PackagePermissions,
103) -> Vec<Permission> {
104 let granted = |perms: &PackagePermissions, perm: Permission| -> bool {
105 let checker = PermissionChecker::new("", perms.clone());
106 checker.check(perm).is_ok()
107 };
108 Permission::ALL
109 .into_iter()
110 .filter(|&perm| !granted(old, perm) && granted(new, perm))
111 .collect()
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_check_allowed() {
120 let perms = PackagePermissions {
121 process: true,
122 network: true,
123 ..Default::default()
124 };
125 let checker = PermissionChecker::new("test-package", perms);
126 assert!(checker.check(Permission::Process).is_ok());
127 assert!(checker.check(Permission::Network).is_ok());
128 }
129
130 #[test]
131 fn test_check_denied() {
132 let perms = PackagePermissions::default();
133 let checker = PermissionChecker::new("test-package", perms);
134 let err = checker.check(Permission::Process).unwrap_err();
135 assert!(err.to_string().contains("test-package"));
136 assert!(err.to_string().contains("process"));
137 }
138
139 #[test]
140 fn no_escalation_when_permissions_unchanged() {
141 let p = PackagePermissions {
142 storage: true,
143 network: true,
144 ..Default::default()
145 };
146 assert!(escalated_permissions(&p, &p).is_empty());
147 }
148
149 #[test]
150 fn detects_added_permissions() {
151 let old = PackagePermissions {
152 storage: true,
153 ..Default::default()
154 };
155 let new = PackagePermissions {
156 storage: true,
157 process: true, network: true, ..Default::default()
160 };
161 let esc = escalated_permissions(&old, &new);
162 assert_eq!(esc.len(), 2);
163 assert!(esc.contains(&Permission::Process));
164 assert!(esc.contains(&Permission::Network));
165 }
166
167 #[test]
168 fn dropping_permissions_is_not_escalation() {
169 let old = PackagePermissions {
170 storage: true,
171 process: true,
172 ..Default::default()
173 };
174 let new = PackagePermissions {
175 storage: true,
176 ..Default::default()
177 };
178 assert!(escalated_permissions(&old, &new).is_empty());
179 }
180}