veloren_server/settings/
whitelist.rs

1//! Versioned whitelist settings files.
2
3// NOTE: Needed to allow the second-to-last migration to call `try_into()`.
4
5use super::{MIGRATION_UPGRADE_GUARANTEE, WHITELIST_FILENAME as FILENAME};
6use crate::settings::editable::{EditableSetting, Version};
7use core::convert::{Infallible, TryFrom, TryInto};
8use serde::{Deserialize, Serialize};
9
10/// NOTE: Always replace this with the latest whitelist version. Then update the
11/// `WhitelistRaw`, the `TryFrom<WhitelistRaw>` for `Whitelist`, the previously
12/// most recent module, and add a new module for the latest version!  Please
13/// respect the migration upgrade guarantee found in the parent module with any
14/// upgrade.
15pub use self::v1::*;
16
17/// Versioned settings files, one per version (v0 is only here as an example; we
18/// do not expect to see any actual v0 settings files).
19#[derive(Deserialize, Serialize)]
20pub enum WhitelistRaw {
21    V0(v0::Whitelist),
22    V1(Whitelist),
23}
24
25impl From<Whitelist> for WhitelistRaw {
26    fn from(value: Whitelist) -> Self {
27        // Replace variant with that of current latest version.
28        Self::V1(value)
29    }
30}
31
32impl TryFrom<WhitelistRaw> for (Version, Whitelist) {
33    type Error = <Whitelist as EditableSetting>::Error;
34
35    fn try_from(value: WhitelistRaw) -> Result<Self, <Whitelist as EditableSetting>::Error> {
36        use WhitelistRaw::*;
37        Ok(match value {
38            // Old versions
39            V0(value) => (Version::Old, value.try_into()?),
40            // Latest version (move to old section using the pattern of other old version when it
41            // is no longer latest).
42            V1(mut value) => (value.validate()?, value),
43        })
44    }
45}
46
47type Final = Whitelist;
48
49impl EditableSetting for Whitelist {
50    type Error = Infallible;
51    type Legacy = legacy::Whitelist;
52    type Setting = WhitelistRaw;
53
54    const FILENAME: &'static str = FILENAME;
55}
56
57mod legacy {
58    use super::{Final, MIGRATION_UPGRADE_GUARANTEE, v0 as next};
59    use authc::Uuid;
60    use core::convert::TryInto;
61    use hashbrown::HashSet;
62    use serde::{Deserialize, Serialize};
63
64    #[derive(Deserialize, Serialize, Default)]
65    #[serde(transparent)]
66    pub struct Whitelist(pub(super) HashSet<Uuid>);
67
68    impl From<Whitelist> for Final {
69        /// Legacy migrations can be migrated to the latest version through the
70        /// process of "chaining" migrations, starting from
71        /// `next::Whitelist`.
72        ///
73        /// Note that legacy files are always valid, which is why we implement
74        /// `From` rather than `TryFrom`.
75        fn from(value: Whitelist) -> Self {
76            next::Whitelist::migrate(value)
77                .try_into()
78                .expect(MIGRATION_UPGRADE_GUARANTEE)
79        }
80    }
81}
82
83/// This module represents a whitelist version that isn't actually used.  It is
84/// here and part of the migration process to provide an example for how to
85/// perform a migration for an old version; please use this as a reference when
86/// constructing new migrations.
87mod v0 {
88    use super::{Final, MIGRATION_UPGRADE_GUARANTEE, legacy as prev, v1 as next};
89    use crate::settings::editable::{EditableSetting, Version};
90    use authc::Uuid;
91    use core::convert::{TryFrom, TryInto};
92    use hashbrown::HashSet;
93    use serde::{Deserialize, Serialize};
94
95    #[derive(Clone, Deserialize, Serialize, Default)]
96    #[serde(transparent)]
97    pub struct Whitelist(pub(super) HashSet<Uuid>);
98
99    impl Whitelist {
100        /// One-off migration from the previous version.  This must be
101        /// guaranteed to produce a valid settings file as long as it is
102        /// called with a valid settings file from the previous version.
103        pub(super) fn migrate(prev: prev::Whitelist) -> Self { Whitelist(prev.0) }
104
105        /// Perform any needed validation on this whitelist that can't be done
106        /// using parsing.
107        ///
108        /// The returned version being "Old" indicates the loaded setting has
109        /// been modified during validation (this is why validate takes
110        /// `&mut self`).
111        pub(super) fn validate(&mut self) -> Result<Version, <Final as EditableSetting>::Error> {
112            Ok(Version::Latest)
113        }
114    }
115
116    /// Pretty much every `TryFrom` implementation except that of the very last
117    /// version should look exactly like this.
118    impl TryFrom<Whitelist> for Final {
119        type Error = <Final as EditableSetting>::Error;
120
121        #[expect(clippy::useless_conversion)]
122        fn try_from(mut value: Whitelist) -> Result<Final, Self::Error> {
123            value.validate()?;
124            Ok(next::Whitelist::migrate(value)
125                .try_into()
126                .expect(MIGRATION_UPGRADE_GUARANTEE))
127        }
128    }
129}
130
131mod v1 {
132    use super::{Final, v0 as prev};
133    use crate::settings::editable::{EditableSetting, Version};
134    use authc::Uuid;
135    use chrono::{Utc, prelude::*};
136    use common::comp::AdminRole;
137    use core::ops::{Deref, DerefMut};
138    use hashbrown::HashMap;
139    use serde::{Deserialize, Serialize};
140    /* use super::v2 as next; */
141
142    /// Important: even if the role we are storing here appears to be identical
143    /// to one used in another versioned store (like admin::Role), we *must*
144    /// have our own versioned copy!  This ensures that if there's an update
145    /// to the role somewhere else, the conversion function between them
146    /// will break, letting people make an intelligent decision.
147    ///
148    /// In particular, *never remove variants from this enum* (or any other enum
149    /// in a versioned settings file) without bumping the version and
150    /// writing a migration that understands how to properly deal with
151    /// existing instances of the old variant (you can delete From instances
152    /// for the old variants at this point).  Otherwise, we will lose
153    /// compatibility with old settings files, since we won't be able to
154    /// deserialize them!
155    #[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
156    pub enum Role {
157        Moderator = 0,
158        Admin = 1,
159    }
160
161    impl From<AdminRole> for Role {
162        fn from(value: AdminRole) -> Self {
163            match value {
164                AdminRole::Moderator => Self::Moderator,
165                AdminRole::Admin => Self::Admin,
166            }
167        }
168    }
169
170    impl From<Role> for AdminRole {
171        fn from(value: Role) -> Self {
172            match value {
173                Role::Moderator => Self::Moderator,
174                Role::Admin => Self::Admin,
175            }
176        }
177    }
178
179    #[derive(Clone, Deserialize, Serialize)]
180    /// NOTE: May not be present if performed from the command line or from a
181    /// legacy file.
182    pub struct WhitelistInfo {
183        pub username_when_whitelisted: String,
184        pub whitelisted_by: Uuid,
185        /// NOTE: May not be up to date, if we allow username changes.
186        pub whitelisted_by_username: String,
187        /// NOTE: Role of the whitelisting user at the time of the ban.
188        pub whitelisted_by_role: Role,
189    }
190
191    #[derive(Clone, Deserialize, Serialize)]
192    pub struct WhitelistRecord {
193        /// Date when the user was added to the whitelist.
194        pub date: DateTime<Utc>,
195        /// NOTE: Should only be None for migrations from legacy data.
196        pub info: Option<WhitelistInfo>,
197    }
198
199    impl WhitelistRecord {
200        pub fn whitelisted_by_role(&self) -> Role {
201            self.info.as_ref().map(|info| info.whitelisted_by_role)
202                // We know all legacy bans were performed by an admin, since we had no other roles
203                // at the time.
204                .unwrap_or(Role::Admin)
205        }
206    }
207
208    #[derive(Clone, Deserialize, Serialize, Default)]
209    #[serde(transparent)]
210    pub struct Whitelist(pub(super) HashMap<Uuid, WhitelistRecord>);
211
212    impl Deref for Whitelist {
213        type Target = HashMap<Uuid, WhitelistRecord>;
214
215        fn deref(&self) -> &Self::Target { &self.0 }
216    }
217
218    impl DerefMut for Whitelist {
219        fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
220    }
221
222    impl Whitelist {
223        /// One-off migration from the previous version.  This must be
224        /// guaranteed to produce a valid settings file as long as it is
225        /// called with a valid settings file from the previous version.
226        pub(super) fn migrate(prev: prev::Whitelist) -> Self {
227            // The whitelist start date for migrations from legacy is the current one; we
228            // could record that they actually have an unknown start date, but
229            // this would just complicate the format.
230            let date = Utc::now();
231            // We don't have any of the information we need for the whitelist for legacy
232            // records.
233            Whitelist(
234                prev.0
235                    .into_iter()
236                    .map(|uid| {
237                        (uid, WhitelistRecord {
238                            date,
239                            // We have none of the information needed for `WhitelistInfo` for old
240                            // whitelist records.
241                            info: None,
242                        })
243                    })
244                    .collect(),
245            )
246        }
247
248        /// Perform any needed validation on this whitelist that can't be done
249        /// using parsing.
250        ///
251        /// The returned version being "Old" indicates the loaded setting has
252        /// been modified during validation (this is why validate takes
253        /// `&mut self`).
254        pub(super) fn validate(&mut self) -> Result<Version, <Final as EditableSetting>::Error> {
255            Ok(Version::Latest)
256        }
257    }
258
259    // NOTE: Whenever there is a version upgrade, copy this note as well as the
260    // commented-out code below to the next version, then uncomment the code
261    // for this version.
262    /* impl TryFrom<Whitelist> for Final {
263        type Error = <Final as EditableSetting>::Error;
264
265        fn try_from(mut value: Whitelist) -> Result<Final, Self::Error> {
266            value.validate()?;
267            Ok(next::Whitelist::migrate(value).try_into().expect(MIGRATION_UPGRADE_GUARANTEE))
268        }
269    } */
270}