steel_core/command/builtins/perms/
config.rs1use std::{error::Error, fmt};
4
5use crate::permission::{
6 OP_GROUP, PermissionGroupConfig, PermissionGroupsConfig, PermissionMetadataExpression,
7 PermissionMetadataRuleConfig, PermissionMetadataValue, PermissionRuleExpression,
8 PermissionState,
9};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub(super) enum PermissionGroupEditError {
13 AlreadyExists(String),
14 Missing(String),
15 Required(String),
16 Default(String),
17 InheritedBy { group: String, child: String },
18 SelfInheritance(String),
19}
20
21impl fmt::Display for PermissionGroupEditError {
22 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
23 match self {
24 Self::AlreadyExists(group) => {
25 write!(formatter, "permission group '{group}' already exists")
26 }
27 Self::Missing(group) => write!(formatter, "unknown permission group '{group}'"),
28 Self::Required(group) => write!(formatter, "permission group '{group}' is required"),
29 Self::Default(group) => write!(
30 formatter,
31 "permission group '{group}' is still a default group"
32 ),
33 Self::InheritedBy { group, child } => {
34 write!(
35 formatter,
36 "permission group '{group}' is inherited by '{child}'"
37 )
38 }
39 Self::SelfInheritance(group) => {
40 write!(
41 formatter,
42 "permission group '{group}' cannot inherit itself"
43 )
44 }
45 }
46 }
47}
48
49impl Error for PermissionGroupEditError {}
50
51pub(super) fn create_group(
52 config: &mut PermissionGroupsConfig,
53 group: &str,
54) -> Result<bool, PermissionGroupEditError> {
55 if config.groups.contains_key(group) {
56 return Err(PermissionGroupEditError::AlreadyExists(group.to_owned()));
57 }
58 config
59 .groups
60 .insert(group.to_owned(), PermissionGroupConfig::default());
61 Ok(true)
62}
63
64pub(super) fn delete_group(
65 config: &mut PermissionGroupsConfig,
66 group: &str,
67) -> Result<bool, PermissionGroupEditError> {
68 if group == OP_GROUP {
69 return Err(PermissionGroupEditError::Required(group.to_owned()));
70 }
71 if !config.groups.contains_key(group) {
72 return Err(PermissionGroupEditError::Missing(group.to_owned()));
73 }
74 if config.default_groups.iter().any(|default| default == group) {
75 return Err(PermissionGroupEditError::Default(group.to_owned()));
76 }
77 if let Some((child, _)) = config
78 .groups
79 .iter()
80 .find(|(_, child)| child.inherits.iter().any(|parent| parent == group))
81 {
82 return Err(PermissionGroupEditError::InheritedBy {
83 group: group.to_owned(),
84 child: child.clone(),
85 });
86 }
87 config.groups.remove(group);
88 Ok(true)
89}
90
91pub(super) fn set_default_group(
92 config: &mut PermissionGroupsConfig,
93 group: &str,
94 add: bool,
95) -> Result<bool, PermissionGroupEditError> {
96 if !config.groups.contains_key(group) {
97 return Err(PermissionGroupEditError::Missing(group.to_owned()));
98 }
99 let present = config.default_groups.iter().any(|default| default == group);
100 if add {
101 if present {
102 return Ok(false);
103 }
104 config.default_groups.push(group.to_owned());
105 return Ok(true);
106 }
107 if !present {
108 return Ok(false);
109 }
110 config.default_groups.retain(|default| default != group);
111 Ok(true)
112}
113
114pub(super) fn set_permission(
115 config: &mut PermissionGroupsConfig,
116 group: &str,
117 expression: &PermissionRuleExpression,
118 state: Option<PermissionState>,
119) -> Result<bool, PermissionGroupEditError> {
120 let Some(group_config) = config.groups.get_mut(group) else {
121 return Err(PermissionGroupEditError::Missing(group.to_owned()));
122 };
123 let value = expression.to_string();
124 let allow_count = group_config
125 .allow
126 .iter()
127 .filter(|rule| permission_rule_matches(rule, expression))
128 .count();
129 let deny_count = group_config
130 .deny
131 .iter()
132 .filter(|rule| permission_rule_matches(rule, expression))
133 .count();
134 group_config
135 .allow
136 .retain(|rule| !permission_rule_matches(rule, expression));
137 group_config
138 .deny
139 .retain(|rule| !permission_rule_matches(rule, expression));
140 match state {
141 Some(PermissionState::Allow) => {
142 group_config.allow.push(value);
143 Ok(allow_count != 1 || deny_count != 0)
144 }
145 Some(PermissionState::Deny) => {
146 group_config.deny.push(value);
147 Ok(deny_count != 1 || allow_count != 0)
148 }
149 None => Ok(allow_count != 0 || deny_count != 0),
150 }
151}
152
153pub(super) fn set_metadata(
154 config: &mut PermissionGroupsConfig,
155 group: &str,
156 expression: &PermissionMetadataExpression,
157 value: Option<PermissionMetadataValue>,
158) -> Result<bool, PermissionGroupEditError> {
159 let Some(group_config) = config.groups.get_mut(group) else {
160 return Err(PermissionGroupEditError::Missing(group.to_owned()));
161 };
162 let expression_text = expression.to_string();
163 let previous = group_config
164 .metadata
165 .iter()
166 .find(|rule| permission_metadata_matches(&rule.key, expression))
167 .map(|rule| rule.value.clone());
168 group_config
169 .metadata
170 .retain(|rule| !permission_metadata_matches(&rule.key, expression));
171 let Some(value) = value else {
172 return Ok(previous.is_some());
173 };
174 let changed = previous.as_ref() != Some(&value);
175 group_config.metadata.push(PermissionMetadataRuleConfig {
176 key: expression_text,
177 value,
178 });
179 Ok(changed)
180}
181
182fn permission_rule_matches(configured: &str, expression: &PermissionRuleExpression) -> bool {
183 PermissionRuleExpression::parse(configured).is_ok_and(|parsed| parsed == *expression)
184}
185
186fn permission_metadata_matches(
187 configured: &str,
188 expression: &PermissionMetadataExpression,
189) -> bool {
190 PermissionMetadataExpression::parse(configured).is_ok_and(|parsed| parsed == *expression)
191}
192
193pub(super) fn set_priority(
194 config: &mut PermissionGroupsConfig,
195 group: &str,
196 priority: i32,
197) -> Result<bool, PermissionGroupEditError> {
198 let Some(group_config) = config.groups.get_mut(group) else {
199 return Err(PermissionGroupEditError::Missing(group.to_owned()));
200 };
201 if group_config.priority == priority {
202 return Ok(false);
203 }
204 group_config.priority = priority;
205 Ok(true)
206}
207
208pub(super) fn set_inheritance(
209 config: &mut PermissionGroupsConfig,
210 group: &str,
211 parent: &str,
212 add: bool,
213) -> Result<bool, PermissionGroupEditError> {
214 if group == parent {
215 return Err(PermissionGroupEditError::SelfInheritance(group.to_owned()));
216 }
217 if !config.groups.contains_key(parent) {
218 return Err(PermissionGroupEditError::Missing(parent.to_owned()));
219 }
220 let Some(group_config) = config.groups.get_mut(group) else {
221 return Err(PermissionGroupEditError::Missing(group.to_owned()));
222 };
223 let present = group_config
224 .inherits
225 .iter()
226 .any(|inherited| inherited == parent);
227 if add {
228 if present {
229 return Ok(false);
230 }
231 group_config.inherits.push(parent.to_owned());
232 return Ok(true);
233 }
234 if !present {
235 return Ok(false);
236 }
237 group_config
238 .inherits
239 .retain(|inherited| inherited != parent);
240 Ok(true)
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 fn permission(value: &str) -> PermissionRuleExpression {
248 match PermissionRuleExpression::parse(value) {
249 Ok(expression) => expression,
250 Err(error) => panic!("test permission expression should parse: {error}"),
251 }
252 }
253
254 #[test]
255 fn permission_edits_replace_the_exact_context_only() {
256 let mut config = PermissionGroupsConfig::default();
257 let global = permission("steel.build");
258 let survival = permission("steel.build{domain=survival}");
259
260 assert_eq!(
261 set_permission(
262 &mut config,
263 "default",
264 &global,
265 Some(PermissionState::Allow)
266 ),
267 Ok(true)
268 );
269 assert_eq!(
270 set_permission(
271 &mut config,
272 "default",
273 &survival,
274 Some(PermissionState::Deny)
275 ),
276 Ok(true)
277 );
278 assert_eq!(
279 set_permission(&mut config, "default", &global, None),
280 Ok(true)
281 );
282 assert!(config.groups["default"].allow.is_empty());
283 assert_eq!(
284 config.groups["default"].deny,
285 ["steel.build{domain=survival}"]
286 );
287 }
288
289 #[test]
290 fn permission_edits_replace_semantically_equal_context_orderings() {
291 let mut config = PermissionGroupsConfig::default();
292 let Some(group) = config.groups.get_mut("default") else {
293 panic!("default permission group should exist");
294 };
295 group
296 .allow
297 .push("steel.build{plugin:region=spawn,domain=survival}".to_owned());
298 let expression = permission("steel.build{domain=survival,plugin:region=spawn}");
299
300 assert_eq!(
301 set_permission(
302 &mut config,
303 "default",
304 &expression,
305 Some(PermissionState::Deny)
306 ),
307 Ok(true)
308 );
309 assert!(config.groups["default"].allow.is_empty());
310 assert_eq!(
311 config.groups["default"].deny,
312 ["steel.build{domain=survival,plugin:region=spawn}"]
313 );
314 }
315
316 #[test]
317 fn required_default_and_inherited_groups_cannot_be_deleted() {
318 let mut config = PermissionGroupsConfig::default();
319 assert!(matches!(
320 delete_group(&mut config, OP_GROUP),
321 Err(PermissionGroupEditError::Required(_))
322 ));
323 assert!(matches!(
324 delete_group(&mut config, "default"),
325 Err(PermissionGroupEditError::Default(_))
326 ));
327 assert_eq!(create_group(&mut config, "parent"), Ok(true));
328 assert_eq!(create_group(&mut config, "child"), Ok(true));
329 assert_eq!(
330 set_inheritance(&mut config, "child", "parent", true),
331 Ok(true)
332 );
333 assert!(matches!(
334 delete_group(&mut config, "parent"),
335 Err(PermissionGroupEditError::InheritedBy { .. })
336 ));
337 }
338}