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