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}