Skip to main content

steel_core/permission/
groups.rs

1use std::{
2    collections::{BTreeMap, BTreeSet},
3    error::Error,
4    fmt,
5};
6
7use serde::{Deserialize, Serialize};
8
9use super::{
10    PermissionEntry, PermissionKeyError, PermissionMetadataEntry, PermissionMetadataExpression,
11    PermissionMetadataExpressionError, PermissionMetadataSet, PermissionMetadataValue,
12    PermissionRuleExpression, PermissionRuleExpressionError, PermissionSegment, PermissionSet,
13};
14
15/// Built-in operator group assigned by `/op`.
16pub(crate) const OP_GROUP: &str = "op";
17
18/// Parsed `groups.toml` permission configuration.
19#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
20#[serde(default, deny_unknown_fields)]
21pub struct PermissionGroupsConfig {
22    /// Groups every player receives.
23    pub default_groups: Vec<String>,
24    /// Named groups available for assignment.
25    pub groups: BTreeMap<String, PermissionGroupConfig>,
26}
27
28impl Default for PermissionGroupsConfig {
29    fn default() -> Self {
30        let mut groups = BTreeMap::new();
31        groups.insert("default".to_owned(), PermissionGroupConfig::default());
32        groups.insert(
33            OP_GROUP.to_owned(),
34            PermissionGroupConfig {
35                allow: vec!["*".to_owned()],
36                ..PermissionGroupConfig::default()
37            },
38        );
39        Self {
40            default_groups: vec!["default".to_owned()],
41            groups,
42        }
43    }
44}
45
46/// One configured permission group.
47#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
48#[serde(default, deny_unknown_fields)]
49pub struct PermissionGroupConfig {
50    /// Priority used between equally specific group rules. Higher wins.
51    pub priority: i32,
52    /// Parent groups inherited by this group.
53    pub inherits: Vec<String>,
54    /// Permission rule expressions explicitly allowed by this group.
55    pub allow: Vec<String>,
56    /// Permission rule expressions explicitly denied by this group.
57    pub deny: Vec<String>,
58    /// Ordered permission metadata rules.
59    pub metadata: Vec<PermissionMetadataRuleConfig>,
60}
61
62/// One `groups.toml` permission metadata rule.
63#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
64#[serde(deny_unknown_fields)]
65pub struct PermissionMetadataRuleConfig {
66    /// Metadata expression affected by this rule.
67    pub key: String,
68    /// Configured typed value.
69    pub value: PermissionMetadataValue,
70}
71
72/// Validated, parsed permission groups.
73#[derive(Clone, Debug, PartialEq, Eq)]
74pub struct PermissionGroups {
75    default_groups: Vec<String>,
76    groups: BTreeMap<String, PermissionGroup>,
77}
78
79impl PermissionGroups {
80    /// Validates and parses permission group configuration.
81    ///
82    /// # Errors
83    ///
84    /// Returns an error for invalid names, rules, default groups, or inheritance.
85    pub fn from_config(config: PermissionGroupsConfig) -> Result<Self, PermissionConfigError> {
86        for group in &config.default_groups {
87            validate_group_name(group)?;
88            if !config.groups.contains_key(group) {
89                return Err(PermissionConfigError::MissingDefaultGroup(group.to_owned()));
90            }
91        }
92        if !config.groups.contains_key(OP_GROUP) {
93            return Err(PermissionConfigError::MissingRequiredGroup(
94                OP_GROUP.to_owned(),
95            ));
96        }
97        validate_group_inheritance(&config)?;
98
99        let mut groups = BTreeMap::new();
100        for (name, group) in config.groups {
101            validate_group_name(&name)?;
102            let permissions = parse_group_permissions(&name, group.allow, group.deny)?;
103            let metadata = parse_group_metadata(&name, group.metadata)?;
104            groups.insert(
105                name,
106                PermissionGroup {
107                    priority: group.priority,
108                    inherits: group.inherits,
109                    permissions,
110                    metadata,
111                },
112            );
113        }
114        Ok(Self {
115            default_groups: config.default_groups,
116            groups,
117        })
118    }
119
120    /// Returns the configured default group names.
121    #[must_use]
122    pub fn default_groups(&self) -> &[String] {
123        &self.default_groups
124    }
125
126    /// Returns configured groups keyed by name.
127    #[must_use]
128    pub const fn groups(&self) -> &BTreeMap<String, PermissionGroup> {
129        &self.groups
130    }
131
132    /// Returns whether a group exists.
133    #[must_use]
134    pub fn contains_group(&self, group: &str) -> bool {
135        self.groups.contains_key(group)
136    }
137
138    /// Builds effective permissions from defaults, assigned groups, and subject overrides.
139    ///
140    /// Unknown assigned groups have no effect. Each inherited group contributes at most once.
141    #[must_use]
142    pub fn effective_permissions(
143        &self,
144        assigned_groups: &[String],
145        subject_permissions: &PermissionSet,
146    ) -> PermissionSet {
147        let mut effective = PermissionSet::new();
148        let mut appended_groups = BTreeSet::new();
149
150        for group in &self.default_groups {
151            self.append_group(group, &mut effective, &mut appended_groups);
152        }
153        for group in assigned_groups {
154            self.append_group(group, &mut effective, &mut appended_groups);
155        }
156        for entry in subject_permissions.entries() {
157            effective.push(entry.clone());
158        }
159        effective
160    }
161
162    /// Builds effective metadata from defaults, assigned groups, and subject overrides.
163    ///
164    /// Unknown assigned groups have no effect. Each inherited group contributes at most once.
165    #[must_use]
166    pub fn effective_metadata(
167        &self,
168        assigned_groups: &[String],
169        subject_metadata: &PermissionMetadataSet,
170    ) -> PermissionMetadataSet {
171        let mut effective = PermissionMetadataSet::new();
172        let mut appended_groups = BTreeSet::new();
173
174        for group in &self.default_groups {
175            self.append_group_metadata(group, &mut effective, &mut appended_groups);
176        }
177        for group in assigned_groups {
178            self.append_group_metadata(group, &mut effective, &mut appended_groups);
179        }
180        for entry in subject_metadata.entries() {
181            effective.push(entry.clone());
182        }
183        effective
184    }
185
186    fn append_group(
187        &self,
188        group_name: &str,
189        effective: &mut PermissionSet,
190        appended_groups: &mut BTreeSet<String>,
191    ) {
192        if !appended_groups.insert(group_name.to_owned()) {
193            return;
194        }
195        let Some(group) = self.groups.get(group_name) else {
196            return;
197        };
198        for parent in &group.inherits {
199            self.append_group(parent, effective, appended_groups);
200        }
201        for entry in group.permissions.entries() {
202            effective.push_group(entry.clone(), group_name, group.priority);
203        }
204    }
205
206    fn append_group_metadata(
207        &self,
208        group_name: &str,
209        effective: &mut PermissionMetadataSet,
210        appended_groups: &mut BTreeSet<String>,
211    ) {
212        if !appended_groups.insert(group_name.to_owned()) {
213            return;
214        }
215        let Some(group) = self.groups.get(group_name) else {
216            return;
217        };
218        for parent in &group.inherits {
219            self.append_group_metadata(parent, effective, appended_groups);
220        }
221        for entry in group.metadata.entries() {
222            effective.push_group(entry.clone(), group_name, group.priority);
223        }
224    }
225}
226
227fn parse_group_permissions(
228    group: &str,
229    allow: Vec<String>,
230    deny: Vec<String>,
231) -> Result<PermissionSet, PermissionConfigError> {
232    let mut permissions = PermissionSet::new();
233    for rule in allow {
234        let expression = parse_group_rule(group, rule)?;
235        let (key, context) = expression.into_parts();
236        permissions.push(PermissionEntry::allow_with_context(key, context));
237    }
238    for rule in deny {
239        let expression = parse_group_rule(group, rule)?;
240        let (key, context) = expression.into_parts();
241        permissions.push(PermissionEntry::deny_with_context(key, context));
242    }
243    Ok(permissions)
244}
245
246fn parse_group_metadata(
247    group: &str,
248    rules: Vec<PermissionMetadataRuleConfig>,
249) -> Result<PermissionMetadataSet, PermissionConfigError> {
250    let mut metadata = PermissionMetadataSet::new();
251    for rule in rules {
252        let expression = PermissionMetadataExpression::parse(rule.key).map_err(|source| {
253            PermissionConfigError::InvalidMetadataExpression {
254                group: group.to_owned(),
255                source,
256            }
257        })?;
258        let (key, context) = expression.into_parts();
259        metadata.push(PermissionMetadataEntry::new_with_context(
260            key, context, rule.value,
261        ));
262    }
263    Ok(metadata)
264}
265
266fn parse_group_rule(
267    group: &str,
268    rule: String,
269) -> Result<PermissionRuleExpression, PermissionConfigError> {
270    PermissionRuleExpression::parse(rule).map_err(|source| {
271        PermissionConfigError::InvalidPermissionExpression {
272            group: group.to_owned(),
273            source,
274        }
275    })
276}
277
278fn validate_group_name(group: &str) -> Result<(), PermissionConfigError> {
279    PermissionSegment::parse(group).map_or_else(
280        |source| {
281            Err(PermissionConfigError::InvalidGroupName {
282                group: group.to_owned(),
283                source,
284            })
285        },
286        |_| Ok(()),
287    )
288}
289
290fn validate_group_inheritance(
291    config: &PermissionGroupsConfig,
292) -> Result<(), PermissionConfigError> {
293    for (group, group_config) in &config.groups {
294        for parent in &group_config.inherits {
295            validate_group_name(parent)?;
296            if !config.groups.contains_key(parent) {
297                return Err(PermissionConfigError::MissingInheritedGroup {
298                    group: group.to_owned(),
299                    inherited: parent.to_owned(),
300                });
301            }
302        }
303    }
304
305    let mut visited = BTreeSet::new();
306    let mut stack = Vec::new();
307    for (group, group_config) in &config.groups {
308        validate_group_inheritance_node(group, group_config, config, &mut visited, &mut stack)?;
309    }
310    Ok(())
311}
312
313fn validate_group_inheritance_node<'a>(
314    group: &'a str,
315    group_config: &'a PermissionGroupConfig,
316    config: &'a PermissionGroupsConfig,
317    visited: &mut BTreeSet<&'a str>,
318    stack: &mut Vec<&'a str>,
319) -> Result<(), PermissionConfigError> {
320    if visited.contains(group) {
321        return Ok(());
322    }
323    if let Some(cycle_start) = stack.iter().position(|ancestor| *ancestor == group) {
324        let mut cycle = stack[cycle_start..]
325            .iter()
326            .map(|group| (*group).to_owned())
327            .collect::<Vec<_>>();
328        cycle.push(group.to_owned());
329        return Err(PermissionConfigError::InheritedGroupCycle(cycle));
330    }
331
332    stack.push(group);
333    for parent in &group_config.inherits {
334        let Some(parent_config) = config.groups.get(parent) else {
335            return Err(PermissionConfigError::MissingInheritedGroup {
336                group: group.to_owned(),
337                inherited: parent.to_owned(),
338            });
339        };
340        validate_group_inheritance_node(parent, parent_config, config, visited, stack)?;
341    }
342    stack.pop();
343    visited.insert(group);
344    Ok(())
345}
346
347/// One validated permission group.
348#[derive(Clone, Debug, PartialEq, Eq)]
349pub struct PermissionGroup {
350    priority: i32,
351    inherits: Vec<String>,
352    permissions: PermissionSet,
353    metadata: PermissionMetadataSet,
354}
355
356impl PermissionGroup {
357    /// Returns the group's conflict priority.
358    #[must_use]
359    pub const fn priority(&self) -> i32 {
360        self.priority
361    }
362
363    /// Returns the parent groups inherited by this group.
364    #[must_use]
365    pub fn inherits(&self) -> &[String] {
366        &self.inherits
367    }
368
369    /// Returns this group's own permission entries.
370    #[must_use]
371    pub const fn permissions(&self) -> &PermissionSet {
372        &self.permissions
373    }
374
375    /// Returns this group's own metadata entries.
376    #[must_use]
377    pub const fn metadata(&self) -> &PermissionMetadataSet {
378        &self.metadata
379    }
380}
381
382/// Invalid permission group configuration.
383#[derive(Clone, Debug, PartialEq, Eq)]
384pub enum PermissionConfigError {
385    /// A default group does not exist in the group map.
386    MissingDefaultGroup(String),
387    /// A built-in required group does not exist in the group map.
388    MissingRequiredGroup(String),
389    /// A group inherits a group that does not exist.
390    MissingInheritedGroup {
391        /// Group containing the invalid edge.
392        group: String,
393        /// Missing parent group.
394        inherited: String,
395    },
396    /// Group inheritance contains a cycle, including the repeated final name.
397    InheritedGroupCycle(Vec<String>),
398    /// A group contains an invalid permission rule expression.
399    InvalidPermissionExpression {
400        /// Group containing the invalid expression.
401        group: String,
402        /// Parse error.
403        source: PermissionRuleExpressionError,
404    },
405    /// A group contains an invalid metadata expression.
406    InvalidMetadataExpression {
407        /// Group containing the invalid expression.
408        group: String,
409        /// Parse error.
410        source: PermissionMetadataExpressionError,
411    },
412    /// A group name is not a valid command-usable segment.
413    InvalidGroupName {
414        /// Invalid group name.
415        group: String,
416        /// Parse error.
417        source: PermissionKeyError,
418    },
419}
420
421impl fmt::Display for PermissionConfigError {
422    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
423        match self {
424            Self::MissingDefaultGroup(group) => {
425                write!(
426                    formatter,
427                    "default permission group '{group}' is not configured"
428                )
429            }
430            Self::MissingRequiredGroup(group) => {
431                write!(
432                    formatter,
433                    "required permission group '{group}' is not configured"
434                )
435            }
436            Self::MissingInheritedGroup { group, inherited } => write!(
437                formatter,
438                "permission group '{group}' inherits unknown group '{inherited}'"
439            ),
440            Self::InheritedGroupCycle(cycle) => write!(
441                formatter,
442                "permission group inheritance cycle: {}",
443                cycle.join(" -> ")
444            ),
445            Self::InvalidPermissionExpression { group, source } => write!(
446                formatter,
447                "permission group '{group}' contains invalid permission expression: {source}"
448            ),
449            Self::InvalidMetadataExpression { group, source } => write!(
450                formatter,
451                "permission group '{group}' contains invalid metadata expression: {source}"
452            ),
453            Self::InvalidGroupName { group, source } => {
454                write!(
455                    formatter,
456                    "permission group name '{group}' is invalid: {source}"
457                )
458            }
459        }
460    }
461}
462
463impl Error for PermissionConfigError {}