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