Skip to main content

steel_core/server/
permissions.rs

1use super::{
2    Arc, FnServerJob, OP_GROUP, PermissionGroupManager, PermissionGroupManagerError,
3    PermissionGroupUpdateError, PermissionGroupsConfig, PermissionSet, PermissionSubjectState,
4    Player, PlayerPermissionUpdateError, Server, ServerJobContext, Uuid,
5};
6
7pub(super) fn validate_player_permission_group_update<E>(
8    manager: &PermissionGroupManager,
9    previous_groups: &[String],
10    updated_groups: &[String],
11) -> Result<(), PlayerPermissionUpdateError<E>> {
12    for group in updated_groups {
13        let already_assigned = previous_groups.iter().any(|current| current == group);
14        if !already_assigned && !manager.contains_group(group) {
15            return Err(PlayerPermissionUpdateError::UnknownGroup(group.clone()));
16        }
17    }
18    Ok(())
19}
20
21impl Server {
22    pub(super) fn apply_cached_or_default_permission_state(&self, player: &Player) -> u64 {
23        let state = self
24            .player_permission_states
25            .read()
26            .get(player.gameprofile.id)
27            .cloned()
28            .unwrap_or_default();
29        self.apply_player_permission_state(player, state)
30    }
31
32    fn apply_player_permission_state(&self, player: &Player, state: PermissionSubjectState) -> u64 {
33        let (groups, overrides, metadata_overrides) = state.into_parts();
34        for group in &groups {
35            if !self.permission_groups.contains_group(group) {
36                log::warn!(
37                    "Player {} has unknown permission group {group}",
38                    player.gameprofile.name
39                );
40            }
41        }
42        let effective = self
43            .permission_groups
44            .effective_permissions(&groups, &overrides);
45        let effective_metadata = self
46            .permission_groups
47            .effective_metadata(&groups, &metadata_overrides);
48        player.set_permission_state(
49            groups,
50            overrides,
51            metadata_overrides,
52            effective,
53            effective_metadata,
54        )
55    }
56
57    /// Returns one player's cached persisted permission state.
58    #[must_use]
59    pub fn player_permission_state(&self, uuid: Uuid) -> Option<PermissionSubjectState> {
60        self.player_permission_states.read().get(uuid).cloned()
61    }
62
63    /// Returns whether the latest published subject state assigns the operator group.
64    #[must_use]
65    pub(crate) fn is_operator(&self, uuid: Uuid) -> bool {
66        self.player_permission_states
67            .read()
68            .get(uuid)
69            .is_some_and(|state| state.groups().iter().any(|group| group == OP_GROUP))
70    }
71
72    /// Captures effective command permissions from the latest published subject and group state.
73    #[must_use]
74    pub(crate) fn command_permission_snapshot(&self, uuid: Uuid) -> PermissionSet {
75        let subject = self.player_permission_state(uuid).unwrap_or_default();
76        self.permission_groups
77            .effective_permissions(subject.groups(), subject.overrides())
78    }
79
80    /// Atomically edits one player's persisted permission state.
81    ///
82    /// Persistence completes before the cache is published. An online player is
83    /// refreshed from the latest cached snapshot at the server job tick stage.
84    ///
85    /// # Errors
86    ///
87    /// Returns an edit error, an unknown newly assigned group, or a storage error.
88    pub async fn try_update_player_permissions<T, E>(
89        self: &Arc<Self>,
90        uuid: Uuid,
91        update: impl FnOnce(PermissionSubjectState) -> Result<(PermissionSubjectState, T), E> + Send,
92    ) -> Result<(PermissionSubjectState, T), PlayerPermissionUpdateError<E>>
93    where
94        T: Send,
95        E: Send,
96    {
97        let _guard = self.player_permission_updates.lock().await;
98        let mut states = self.player_permission_states.read().clone();
99        let current = states.get(uuid).cloned().unwrap_or_default();
100        let previous_groups = current.groups().to_vec();
101        let (updated, result) = update(current).map_err(PlayerPermissionUpdateError::Edit)?;
102        validate_player_permission_group_update(
103            &self.permission_groups,
104            &previous_groups,
105            updated.groups(),
106        )?;
107
108        if updated.is_empty() {
109            states.remove(uuid);
110        } else {
111            states.set(uuid, updated.clone());
112        }
113        self.player_data_storage
114            .save_permission_subjects(&states)
115            .await?;
116
117        *self.player_permission_states.write() = states;
118        self.queue_player_permission_refresh(uuid);
119        Ok((updated, result))
120    }
121
122    /// Replaces the complete permission group config and refreshes online players.
123    ///
124    /// # Errors
125    ///
126    /// Returns an error when validation or persistence fails.
127    pub async fn replace_permission_groups(
128        self: &Arc<Self>,
129        config: PermissionGroupsConfig,
130    ) -> Result<(), PermissionGroupManagerError> {
131        self.permission_groups.replace_config(config).await?;
132        self.queue_online_permission_group_refresh();
133        Ok(())
134    }
135
136    /// Edits the latest permission group config and refreshes online players.
137    ///
138    /// # Errors
139    ///
140    /// Returns an error when validation or persistence fails.
141    pub async fn update_permission_groups(
142        self: &Arc<Self>,
143        update: impl FnOnce(&mut PermissionGroupsConfig) + Send,
144    ) -> Result<(), PermissionGroupManagerError> {
145        self.permission_groups.update_config(update).await?;
146        self.queue_online_permission_group_refresh();
147        Ok(())
148    }
149
150    /// Applies a fallible permission group edit and refreshes online players.
151    ///
152    /// # Errors
153    ///
154    /// Returns the caller edit error or a validation/persistence error.
155    pub async fn try_update_permission_groups<T, E>(
156        self: &Arc<Self>,
157        update: impl FnOnce(&mut PermissionGroupsConfig) -> Result<T, E> + Send,
158    ) -> Result<T, PermissionGroupUpdateError<E>>
159    where
160        T: Send,
161        E: Send,
162    {
163        let result = self.permission_groups.try_update_config(update).await?;
164        self.queue_online_permission_group_refresh();
165        Ok(result)
166    }
167
168    fn queue_player_permission_refresh(self: &Arc<Self>, uuid: Uuid) {
169        self.jobs
170            .spawn(FnServerJob::new(move |context: &mut ServerJobContext| {
171                if let Some(server) = context.server() {
172                    server.refresh_player_permission_state(uuid);
173                }
174            }));
175    }
176
177    pub(crate) fn refresh_player_permission_state(self: &Arc<Self>, uuid: Uuid) {
178        let Some(player) = self.online_players.get_by_uuid(&uuid) else {
179            return;
180        };
181        let state = self.player_permission_state(uuid).unwrap_or_default();
182        self.apply_player_permission_state(&player, state);
183        self.resend_player_permission_context(&player);
184    }
185
186    fn queue_online_permission_group_refresh(self: &Arc<Self>) {
187        self.jobs
188            .spawn(FnServerJob::new(|context: &mut ServerJobContext| {
189                if let Some(server) = context.server() {
190                    server.refresh_online_permission_groups();
191                }
192            }));
193    }
194
195    fn refresh_online_permission_groups(self: &Arc<Self>) {
196        for player in self.get_players() {
197            let state = self
198                .player_permission_state(player.gameprofile.id)
199                .unwrap_or_default();
200            self.apply_player_permission_state(&player, state);
201            self.resend_player_permission_context(&player);
202        }
203    }
204}