Skip to main content

steel_core/command/builtins/perms/
config.rs

1//! Permission group config editing helpers.
2
3use 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}