-
Notifications
You must be signed in to change notification settings - Fork 520
/
Copy pathSettingsMigrationService.java
400 lines (360 loc) · 19 KB
/
SettingsMigrationService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
package fr.xephi.authme.settings;
import ch.jalu.configme.configurationdata.ConfigurationData;
import ch.jalu.configme.migration.PlainMigrationService;
import ch.jalu.configme.properties.Property;
import ch.jalu.configme.properties.convertresult.PropertyValue;
import ch.jalu.configme.resource.PropertyReader;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSourceType;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
import fr.xephi.authme.process.register.RegistrationType;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.StringUtils;
import javax.inject.Inject;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static ch.jalu.configme.properties.PropertyInitializer.newListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
import static fr.xephi.authme.settings.properties.DatabaseSettings.MYSQL_POOL_SIZE;
import static fr.xephi.authme.settings.properties.RegistrationSettings.DELAY_JOIN_MESSAGE;
import static fr.xephi.authme.settings.properties.RegistrationSettings.REMOVE_JOIN_MESSAGE;
import static fr.xephi.authme.settings.properties.RegistrationSettings.REMOVE_LEAVE_MESSAGE;
import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS;
import static fr.xephi.authme.settings.properties.RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN;
import static fr.xephi.authme.settings.properties.RestrictionSettings.FORCE_SPAWN_ON_WORLDS;
/**
* Service for verifying that the configuration is up-to-date.
*/
public class SettingsMigrationService extends PlainMigrationService {
private static ConsoleLogger logger = ConsoleLoggerFactory.get(SettingsMigrationService.class);
private final File pluginFolder;
// Stores old "other accounts command" config if present.
// We need to store it in here for retrieval when we build the CommandConfig. Retrieving it from the config.yml is
// not possible since this migration service may trigger the config.yml to be resaved. As the old command settings
// don't exist in the code anymore, as soon as config.yml is resaved we lose this information.
private String oldOtherAccountsCommand;
private int oldOtherAccountsCommandThreshold;
@Inject
SettingsMigrationService(@DataFolder File pluginFolder) {
this.pluginFolder = pluginFolder;
}
@Override
@SuppressWarnings("checkstyle:BooleanExpressionComplexity")
protected boolean performMigrations(PropertyReader reader, ConfigurationData configurationData) {
boolean changes = false;
if ("[a-zA-Z0-9_?]*".equals(reader.getString(ALLOWED_NICKNAME_CHARACTERS.getPath()))) {
configurationData.setValue(ALLOWED_NICKNAME_CHARACTERS, "[a-zA-Z0-9_]*");
changes = true;
}
String driverClass = reader.getString("DataSource.mySQLDriverClassName");
if ("fr.xephi.authme.libs.org.mariadb.jdbc.Driver".equals(driverClass)) {
configurationData.setValue(DatabaseSettings.BACKEND, DataSourceType.MARIADB);
changes = true;
}
setOldOtherAccountsCommandFieldsIfSet(reader);
// Note ljacqu 20160211: Concatenating migration methods with | instead of the usual ||
// ensures that all migrations will be performed
return changes
| performMailTextToFileMigration(reader)
| migrateJoinLeaveMessages(reader, configurationData)
| migrateForceSpawnSettings(reader, configurationData)
| migratePoolSizeSetting(reader, configurationData)
| changeBooleanSettingToLogLevelProperty(reader, configurationData)
| hasOldHelpHeaderProperty(reader)
| hasSupportOldPasswordProperty(reader)
| convertToRegistrationType(reader, configurationData)
| mergeAndMovePermissionGroupSettings(reader, configurationData)
| moveDeprecatedHashAlgorithmIntoLegacySection(reader, configurationData)
| moveSaltColumnConfigWithOtherColumnConfigs(reader, configurationData)
|| hasDeprecatedProperties(reader);
}
private static boolean hasDeprecatedProperties(PropertyReader reader) {
String[] deprecatedProperties = {
"Converter.Rakamak.newPasswordHash", "Hooks.chestshop", "Hooks.legacyChestshop", "Hooks.notifications",
"Passpartu", "Performances", "settings.restrictions.enablePasswordVerifier", "Xenoforo.predefinedSalt",
"VeryGames", "settings.restrictions.allowAllCommandsIfRegistrationIsOptional", "DataSource.mySQLWebsite",
"Hooks.customAttributes", "Security.stop.kickPlayersBeforeStopping",
"settings.restrictions.keepCollisionsDisabled", "settings.forceCommands", "settings.forceCommandsAsConsole",
"settings.forceRegisterCommands", "settings.forceRegisterCommandsAsConsole",
"settings.sessions.sessionExpireOnIpChange", "settings.restrictions.otherAccountsCmd",
"settings.restrictions.otherAccountsCmdThreshold, DataSource.mySQLDriverClassName"};
for (String deprecatedPath : deprecatedProperties) {
if (reader.contains(deprecatedPath)) {
return true;
}
}
return false;
}
// --------
// Old other accounts
// --------
public boolean hasOldOtherAccountsCommand() {
return !StringUtils.isBlank(oldOtherAccountsCommand);
}
public String getOldOtherAccountsCommand() {
return oldOtherAccountsCommand;
}
public int getOldOtherAccountsCommandThreshold() {
return oldOtherAccountsCommandThreshold;
}
// --------
// Specific migrations
// --------
/**
* Check if {@code Email.mailText} is present and move it to the Email.html file if it doesn't exist yet.
*
* @param reader The property reader
* @return True if a migration has been completed, false otherwise
*/
private boolean performMailTextToFileMigration(PropertyReader reader) {
final String oldSettingPath = "Email.mailText";
final String oldMailText = reader.getString(oldSettingPath);
if (oldMailText == null) {
return false;
}
final File emailFile = new File(pluginFolder, "email.html");
final String mailText = oldMailText
.replace("<playername>", "<playername />").replace("%playername%", "<playername />")
.replace("<servername>", "<servername />").replace("%servername%", "<servername />")
.replace("<generatedpass>", "<generatedpass />").replace("%generatedpass%", "<generatedpass />")
.replace("<image>", "<image />").replace("%image%", "<image />");
if (!emailFile.exists()) {
try (FileWriter fw = new FileWriter(emailFile)) {
fw.write(mailText);
} catch (IOException e) {
logger.logException("Could not create email.html configuration file:", e);
}
}
return true;
}
/**
* Detect deprecated {@code settings.delayJoinLeaveMessages} and inform user of new "remove join messages"
* and "remove leave messages" settings.
*
* @param reader The property reader
* @param configData Configuration data
* @return True if the configuration has changed, false otherwise
*/
private static boolean migrateJoinLeaveMessages(PropertyReader reader, ConfigurationData configData) {
Property<Boolean> oldDelayJoinProperty = newProperty("settings.delayJoinLeaveMessages", false);
boolean hasMigrated = moveProperty(oldDelayJoinProperty, DELAY_JOIN_MESSAGE, reader, configData);
if (hasMigrated) {
logger.info(String.format("Note that we now also have the settings %s and %s",
REMOVE_JOIN_MESSAGE.getPath(), REMOVE_LEAVE_MESSAGE.getPath()));
}
return hasMigrated;
}
/**
* Detects old "force spawn loc on join" and "force spawn on these worlds" settings and moves them
* to the new paths.
*
* @param reader The property reader
* @param configData Configuration data
* @return True if the configuration has changed, false otherwise
*/
private static boolean migrateForceSpawnSettings(PropertyReader reader, ConfigurationData configData) {
Property<Boolean> oldForceLocEnabled = newProperty(
"settings.restrictions.ForceSpawnLocOnJoinEnabled", false);
Property<List<String>> oldForceWorlds = newListProperty(
"settings.restrictions.ForceSpawnOnTheseWorlds", "world", "world_nether", "world_the_ed");
return moveProperty(oldForceLocEnabled, FORCE_SPAWN_LOCATION_AFTER_LOGIN, reader, configData)
| moveProperty(oldForceWorlds, FORCE_SPAWN_ON_WORLDS, reader, configData);
}
/**
* Detects the old auto poolSize value and replaces it with the default value.
*
* @param reader The property reader
* @param configData Configuration data
* @return True if the configuration has changed, false otherwise
*/
private static boolean migratePoolSizeSetting(PropertyReader reader, ConfigurationData configData) {
Integer oldValue = reader.getInt(MYSQL_POOL_SIZE.getPath());
if (oldValue == null || oldValue > 0) {
return false;
}
configData.setValue(MYSQL_POOL_SIZE, 10);
return true;
}
/**
* Changes the old boolean property "hide spam from console" to the new property specifying
* the log level.
*
* @param reader The property reader
* @param configData Configuration data
* @return True if the configuration has changed, false otherwise
*/
private static boolean changeBooleanSettingToLogLevelProperty(PropertyReader reader,
ConfigurationData configData) {
final String oldPath = "Security.console.noConsoleSpam";
final Property<LogLevel> newProperty = PluginSettings.LOG_LEVEL;
if (!newProperty.isValidInResource(reader) && reader.contains(oldPath)) {
logger.info("Moving '" + oldPath + "' to '" + newProperty.getPath() + "'");
boolean oldValue = Optional.ofNullable(reader.getBoolean(oldPath)).orElse(false);
LogLevel level = oldValue ? LogLevel.INFO : LogLevel.FINE;
configData.setValue(newProperty, level);
return true;
}
return false;
}
private static boolean hasOldHelpHeaderProperty(PropertyReader reader) {
if (reader.contains("settings.helpHeader")) {
logger.warning("Help header setting is now in messages/help_xx.yml, "
+ "please check the file to set it again");
return true;
}
return false;
}
private static boolean hasSupportOldPasswordProperty(PropertyReader reader) {
String path = "settings.security.supportOldPasswordHash";
if (reader.contains(path)) {
logger.warning("Property '" + path + "' is no longer supported. "
+ "Use '" + SecuritySettings.LEGACY_HASHES.getPath() + "' instead.");
return true;
}
return false;
}
/**
* Converts old boolean configurations for registration to the new enum properties, if applicable.
*
* @param reader The property reader
* @param configData Configuration data
* @return True if the configuration has changed, false otherwise
*/
private static boolean convertToRegistrationType(PropertyReader reader, ConfigurationData configData) {
String oldEmailRegisterPath = "settings.registration.enableEmailRegistrationSystem";
if (RegistrationSettings.REGISTRATION_TYPE.isValidInResource(reader)
|| !reader.contains(oldEmailRegisterPath)) {
return false;
}
boolean useEmail = newProperty(oldEmailRegisterPath, false).determineValue(reader).getValue();
RegistrationType registrationType = useEmail ? RegistrationType.EMAIL : RegistrationType.PASSWORD;
String useConfirmationPath = useEmail
? "settings.registration.doubleEmailCheck"
: "settings.restrictions.enablePasswordConfirmation";
boolean hasConfirmation = newProperty(useConfirmationPath, false).determineValue(reader).getValue();
RegisterSecondaryArgument secondaryArgument = hasConfirmation
? RegisterSecondaryArgument.CONFIRMATION
: RegisterSecondaryArgument.NONE;
logger.warning("Merging old registration settings into '"
+ RegistrationSettings.REGISTRATION_TYPE.getPath() + "'");
configData.setValue(RegistrationSettings.REGISTRATION_TYPE, registrationType);
configData.setValue(RegistrationSettings.REGISTER_SECOND_ARGUMENT, secondaryArgument);
return true;
}
/**
* Migrates old permission group settings to the new configurations.
*
* @param reader The property reader
* @param configData Configuration data
* @return True if the configuration has changed, false otherwise
*/
private static boolean mergeAndMovePermissionGroupSettings(PropertyReader reader, ConfigurationData configData) {
boolean performedChanges;
// We have two old settings replaced by only one: move the first non-empty one
Property<String> oldUnloggedInGroup = newProperty("settings.security.unLoggedinGroup", "");
Property<String> oldRegisteredGroup = newProperty("GroupOptions.RegisteredPlayerGroup", "");
if (!oldUnloggedInGroup.determineValue(reader).getValue().isEmpty()) {
performedChanges = moveProperty(oldUnloggedInGroup, PluginSettings.REGISTERED_GROUP, reader, configData);
} else {
performedChanges = moveProperty(oldRegisteredGroup, PluginSettings.REGISTERED_GROUP, reader, configData);
}
// Move paths of other old options
performedChanges |= moveProperty(newProperty("GroupOptions.UnregisteredPlayerGroup", ""),
PluginSettings.UNREGISTERED_GROUP, reader, configData);
performedChanges |= moveProperty(newProperty("permission.EnablePermissionCheck", false),
PluginSettings.ENABLE_PERMISSION_CHECK, reader, configData);
return performedChanges;
}
/**
* If a deprecated hash is used, it is added to the legacy hashes option and the active hash
* is changed to SHA256.
*
* @param reader The property reader
* @param configData Configuration data
* @return True if the configuration has changed, false otherwise
*/
private static boolean moveDeprecatedHashAlgorithmIntoLegacySection(PropertyReader reader,
ConfigurationData configData) {
HashAlgorithm currentHash = SecuritySettings.PASSWORD_HASH.determineValue(reader).getValue();
// Skip CUSTOM (has no class) and PLAINTEXT (is force-migrated later on in the startup process)
if (currentHash != HashAlgorithm.CUSTOM && currentHash != HashAlgorithm.PLAINTEXT) {
Class<?> encryptionClass = currentHash.getClazz();
if (encryptionClass.isAnnotationPresent(Deprecated.class)) {
configData.setValue(SecuritySettings.PASSWORD_HASH, HashAlgorithm.SHA256);
Set<HashAlgorithm> legacyHashes = SecuritySettings.LEGACY_HASHES.determineValue(reader).getValue();
legacyHashes.add(currentHash);
configData.setValue(SecuritySettings.LEGACY_HASHES, legacyHashes);
logger.warning("The hash algorithm '" + currentHash
+ "' is no longer supported for active use. New hashes will be in SHA256.");
return true;
}
}
return false;
}
/**
* Moves the property for the password salt column name to the same path as all other column name properties.
*
* @param reader The property reader
* @param configData Configuration data
* @return True if the configuration has changed, false otherwise
*/
private static boolean moveSaltColumnConfigWithOtherColumnConfigs(PropertyReader reader,
ConfigurationData configData) {
Property<String> oldProperty = newProperty("ExternalBoardOptions.mySQLColumnSalt",
DatabaseSettings.MYSQL_COL_SALT.getDefaultValue());
return moveProperty(oldProperty, DatabaseSettings.MYSQL_COL_SALT, reader, configData);
}
/**
* Retrieves the old config to run a command when alt accounts are detected and sets them to this instance
* for further processing.
*
* @param reader The property reader
*/
private void setOldOtherAccountsCommandFieldsIfSet(PropertyReader reader) {
Property<String> commandProperty = newProperty("settings.restrictions.otherAccountsCmd", "");
Property<Integer> commandThresholdProperty = newProperty("settings.restrictions.otherAccountsCmdThreshold", 0);
PropertyValue<String> commandPropValue = commandProperty.determineValue(reader);
int commandThreshold = commandThresholdProperty.determineValue(reader).getValue();
if (commandPropValue.isValidInResource() && commandThreshold >= 2) {
oldOtherAccountsCommand = commandPropValue.getValue();
oldOtherAccountsCommandThreshold = commandThreshold;
}
}
/**
* Checks for an old property path and moves it to a new path if it is present and the new path is not yet set.
*
* @param oldProperty The old property (create a temporary {@link Property} object with the path)
* @param newProperty The new property to move the value to
* @param reader The property reader
* @param configData Configuration data
* @param <T> The type of the property
* @return True if a migration has been done, false otherwise
*/
protected static <T> boolean moveProperty(Property<T> oldProperty,
Property<T> newProperty,
PropertyReader reader,
ConfigurationData configData) {
PropertyValue<T> oldPropertyValue = oldProperty.determineValue(reader);
if (oldPropertyValue.isValidInResource()) {
if (reader.contains(newProperty.getPath())) {
logger.info("Detected deprecated property " + oldProperty.getPath());
} else {
logger.info("Renaming " + oldProperty.getPath() + " to " + newProperty.getPath());
configData.setValue(newProperty, oldPropertyValue.getValue());
}
return true;
}
return false;
}
}