diff --git a/docs/hash_algorithms.md b/docs/hash_algorithms.md index ed1a6702d..45fa65948 100644 --- a/docs/hash_algorithms.md +++ b/docs/hash_algorithms.md @@ -1,5 +1,5 @@ - + ## Hash Algorithms AuthMe supports the following hash algorithms for storing your passwords safely. @@ -10,11 +10,9 @@ Algorithm | Recommendation | Hash length | ASCII | | Salt type | Length | Se BCRYPT | Recommended | 60 | | | Text | | BCRYPT2Y | Recommended | 60 | | | Text | 22 | CRAZYCRYPT1 | Do not use | 128 | | | Username | | -DOUBLEMD5 | Deprecated | 32 | | | None | | IPB3 | Acceptable | 32 | | | Text | 5 | Y IPB4 | Does not work | 60 | | | Text | 22 | Y JOOMLA | Acceptable | 65 | | | Text | 32 | -MD5 | Deprecated | 32 | | | None | | MD5VB | Acceptable | 56 | | | Text | 16 | MYBB | Acceptable | 32 | | | Text | 8 | Y PBKDF2 | Recommended | 165 | | | Text | 16 | @@ -24,14 +22,11 @@ PHPFUSION | Do not use | 64 | Y | | | | Y ROYALAUTH | Do not use | 128 | | | None | | SALTED2MD5 | Acceptable | 32 | | | Text | | Y SALTEDSHA512 | Recommended | 128 | | | | | Y -SHA1 | Deprecated | 40 | | | None | | SHA256 | Recommended | 86 | | | Text | 16 | -SHA512 | Deprecated | 128 | | | None | | -SMF | Do not use | 40 | | | Username | | +SMF | Do not use | 40 | | | Username | | Y TWO_FACTOR | Does not work | 16 | | | None | | WBB3 | Acceptable | 40 | | | Text | 40 | Y WBB4 | Recommended | 60 | | | Text | 8 | -WHIRLPOOL | Deprecated | 128 | | | None | | WORDPRESS | Acceptable | 34 | | | Text | 9 | XAUTH | Recommended | 140 | | | Text | 12 | XFBCRYPT | | 60 | | | | | @@ -83,4 +78,4 @@ or bad. --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Sep 17 11:29:07 CEST 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Thu Oct 19 21:41:21 CEST 2017 diff --git a/pom.xml b/pom.xml index 1405a4a36..e44421cc9 100644 --- a/pom.xml +++ b/pom.xml @@ -325,7 +325,7 @@ com.google.guava guava - 23.0 + 23.2-jre compile true @@ -360,7 +360,7 @@ com.zaxxer HikariCP - 2.7.1 + 2.7.2 compile diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 6ce2d103e..728b469b8 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -23,7 +23,6 @@ import fr.xephi.authme.listener.PlayerListener18; import fr.xephi.authme.listener.PlayerListener19; import fr.xephi.authme.listener.PlayerListener19Spigot; import fr.xephi.authme.listener.ServerListener; -import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.crypts.Sha256; import fr.xephi.authme.service.BackupService; import fr.xephi.authme.service.BukkitService; @@ -268,13 +267,6 @@ public class AuthMe extends JavaPlugin { && settings.getProperty(EmailSettings.SMTP_PORT) != 25) { ConsoleLogger.warning("Note: You have set Email.useTls to false but this only affects mail over port 25"); } - - // Unsalted hashes will be deprecated in 5.4 (see Github issue #1016) - HashAlgorithm hash = settings.getProperty(SecuritySettings.PASSWORD_HASH); - if (OnStartupTasks.isHashDeprecatedIn54(hash)) { - ConsoleLogger.warning("You are using an unsalted hash (" + hash + "). Support for this will be removed " - + "in 5.4 -- do you still need it? Comment on https://github.com/AuthMe/AuthMeReloaded/issues/1016"); - } } /** diff --git a/src/main/java/fr/xephi/authme/ConsoleLogger.java b/src/main/java/fr/xephi/authme/ConsoleLogger.java index 9d18820cd..0d9332985 100644 --- a/src/main/java/fr/xephi/authme/ConsoleLogger.java +++ b/src/main/java/fr/xephi/authme/ConsoleLogger.java @@ -17,6 +17,7 @@ import java.util.Date; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; /** * The plugin's static logger. @@ -168,7 +169,7 @@ public final class ConsoleLogger { * @param message the message * @param param1 parameter to replace in the message */ - public static void debug(String message, String param1) { + public static void debug(String message, Object param1) { if (logLevel.includes(LogLevel.DEBUG)) { String debugMessage = "[DEBUG] " + message; logger.log(Level.INFO, debugMessage, param1); @@ -184,24 +185,9 @@ public final class ConsoleLogger { * @param param2 second param to replace in message */ // Avoids array creation if DEBUG level is disabled - public static void debug(String message, String param1, String param2) { + public static void debug(String message, Object param1, Object param2) { if (logLevel.includes(LogLevel.DEBUG)) { - debug(message, new String[]{param1, param2}); - } - } - - /** - * Log the DEBUG message. - * - * @param message the message - * @param params the params to replace in the message - */ - // Equivalent to debug(String, Object...) but avoids conversions - public static void debug(String message, String... params) { - if (logLevel.includes(LogLevel.DEBUG)) { - String debugMessage = "[DEBUG] " + message; - logger.log(Level.INFO, debugMessage, params); - writeLog(debugMessage + " {" + String.join(", ", params) + "}"); + debug(message, new Object[]{param1, param2}); } } @@ -213,7 +199,10 @@ public final class ConsoleLogger { */ public static void debug(String message, Object... params) { if (logLevel.includes(LogLevel.DEBUG)) { - debug(message, Arrays.stream(params).map(String::valueOf).toArray(String[]::new)); + String debugMessage = "[DEBUG] " + message; + logger.log(Level.INFO, debugMessage, params); + writeLog(debugMessage + " {" + + Arrays.stream(params).map(String::valueOf).collect(Collectors.joining(", ")) + "}"); } } diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 45240a1ac..06a5422b6 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -583,7 +583,7 @@ public class MySQL implements DataSource { public boolean hasSession(String user) { String sql = "SELECT " + col.HAS_SESSION + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, user); + pst.setString(1, user.toLowerCase()); try (ResultSet rs = pst.executeQuery()) { return rs.next() && (rs.getInt(col.HAS_SESSION) == 1); } diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java index 7497858fb..dba210157 100644 --- a/src/main/java/fr/xephi/authme/datasource/SQLite.java +++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java @@ -509,7 +509,7 @@ public class SQLite implements DataSource { public boolean hasSession(String user) { String sql = "SELECT " + col.HAS_SESSION + " FROM " + tableName + " WHERE LOWER(" + col.NAME + ")=?;"; try (PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, user); + pst.setString(1, user.toLowerCase()); try (ResultSet rs = pst.executeQuery()) { if (rs.next()) { return rs.getInt(col.HAS_SESSION) == 1; @@ -526,7 +526,7 @@ public class SQLite implements DataSource { String sql = "UPDATE " + tableName + " SET " + col.HAS_SESSION + "=? WHERE LOWER(" + col.NAME + ")=?;"; try (PreparedStatement pst = con.prepareStatement(sql)) { pst.setInt(1, 1); - pst.setString(2, user); + pst.setString(2, user.toLowerCase()); pst.executeUpdate(); } catch (SQLException ex) { logSqlException(ex); @@ -538,7 +538,7 @@ public class SQLite implements DataSource { String sql = "UPDATE " + tableName + " SET " + col.HAS_SESSION + "=? WHERE LOWER(" + col.NAME + ")=?;"; try (PreparedStatement pst = con.prepareStatement(sql)) { pst.setInt(1, 0); - pst.setString(2, user); + pst.setString(2, user.toLowerCase()); pst.executeUpdate(); } catch (SQLException ex) { logSqlException(ex); diff --git a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java index e9ae353b4..77d18264a 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java +++ b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java @@ -7,9 +7,6 @@ import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; import fr.xephi.authme.output.ConsoleFilter; import fr.xephi.authme.output.Log4JFilter; -import fr.xephi.authme.security.HashAlgorithm; -import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.DatabaseSettings; @@ -103,23 +100,4 @@ public class OnStartupTasks { } }, 1, TICKS_PER_MINUTE * settings.getProperty(EmailSettings.DELAY_RECALL)); } - - /** - * Returns whether the hash algorithm is deprecated and won't be able - * to be actively used anymore in 5.4. - * - * @param hash the hash algorithm to check - * @return true if the hash will be deprecated, false otherwise - * @see #1016 - */ - public static boolean isHashDeprecatedIn54(HashAlgorithm hash) { - if (hash.getClazz() == null || hash == HashAlgorithm.PLAINTEXT) { - // Exclude PLAINTEXT from this check because it already has a mandatory migration, which takes care of - // sending all the necessary messages and warnings. - return false; - } - - Recommendation recommendation = hash.getClazz().getAnnotation(Recommendation.class); - return recommendation != null && recommendation.value() == Usage.DEPRECATED; - } } diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index f12da678d..5ce3e92a5 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -10,34 +10,34 @@ public enum HashAlgorithm { BCRYPT(fr.xephi.authme.security.crypts.BCrypt.class), BCRYPT2Y(fr.xephi.authme.security.crypts.BCrypt2y.class), CRAZYCRYPT1(fr.xephi.authme.security.crypts.CrazyCrypt1.class), - DOUBLEMD5(fr.xephi.authme.security.crypts.DoubleMd5.class), IPB3(fr.xephi.authme.security.crypts.Ipb3.class), IPB4(fr.xephi.authme.security.crypts.Ipb4.class), JOOMLA(fr.xephi.authme.security.crypts.Joomla.class), - MD5(fr.xephi.authme.security.crypts.Md5.class), MD5VB(fr.xephi.authme.security.crypts.Md5vB.class), MYBB(fr.xephi.authme.security.crypts.MyBB.class), PBKDF2(fr.xephi.authme.security.crypts.Pbkdf2.class), PBKDF2DJANGO(fr.xephi.authme.security.crypts.Pbkdf2Django.class), PHPBB(fr.xephi.authme.security.crypts.PhpBB.class), PHPFUSION(fr.xephi.authme.security.crypts.PhpFusion.class), - @Deprecated - PLAINTEXT(fr.xephi.authme.security.crypts.PlainText.class), ROYALAUTH(fr.xephi.authme.security.crypts.RoyalAuth.class), SALTED2MD5(fr.xephi.authme.security.crypts.Salted2Md5.class), SALTEDSHA512(fr.xephi.authme.security.crypts.SaltedSha512.class), - SHA1(fr.xephi.authme.security.crypts.Sha1.class), SHA256(fr.xephi.authme.security.crypts.Sha256.class), - SHA512(fr.xephi.authme.security.crypts.Sha512.class), SMF(fr.xephi.authme.security.crypts.Smf.class), TWO_FACTOR(fr.xephi.authme.security.crypts.TwoFactor.class), WBB3(fr.xephi.authme.security.crypts.Wbb3.class), WBB4(fr.xephi.authme.security.crypts.Wbb4.class), - WHIRLPOOL(fr.xephi.authme.security.crypts.Whirlpool.class), WORDPRESS(fr.xephi.authme.security.crypts.Wordpress.class), XAUTH(fr.xephi.authme.security.crypts.XAuth.class), XFBCRYPT(fr.xephi.authme.security.crypts.XfBCrypt.class), - CUSTOM(null); + CUSTOM(null), + + @Deprecated DOUBLEMD5(fr.xephi.authme.security.crypts.DoubleMd5.class), + @Deprecated MD5(fr.xephi.authme.security.crypts.Md5.class), + @Deprecated PLAINTEXT(fr.xephi.authme.security.crypts.PlainText.class), + @Deprecated SHA1(fr.xephi.authme.security.crypts.Sha1.class), + @Deprecated SHA512(fr.xephi.authme.security.crypts.Sha512.class), + @Deprecated WHIRLPOOL(fr.xephi.authme.security.crypts.Whirlpool.class); private final Class clazz; diff --git a/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java b/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java index 54158419b..12e968082 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java @@ -5,6 +5,7 @@ import fr.xephi.authme.security.crypts.description.Usage; import static fr.xephi.authme.security.HashUtils.md5; +@Deprecated @Recommendation(Usage.DEPRECATED) public class DoubleMd5 extends UnsaltedMethod { diff --git a/src/main/java/fr/xephi/authme/security/crypts/Md5.java b/src/main/java/fr/xephi/authme/security/crypts/Md5.java index a5f6d91d2..55e07a173 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Md5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Md5.java @@ -4,6 +4,7 @@ import fr.xephi.authme.security.HashUtils; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; +@Deprecated @Recommendation(Usage.DEPRECATED) public class Md5 extends UnsaltedMethod { diff --git a/src/main/java/fr/xephi/authme/security/crypts/Sha1.java b/src/main/java/fr/xephi/authme/security/crypts/Sha1.java index 3430752b5..6d095b4a2 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Sha1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Sha1.java @@ -4,6 +4,7 @@ import fr.xephi.authme.security.HashUtils; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; +@Deprecated @Recommendation(Usage.DEPRECATED) public class Sha1 extends UnsaltedMethod { diff --git a/src/main/java/fr/xephi/authme/security/crypts/Sha512.java b/src/main/java/fr/xephi/authme/security/crypts/Sha512.java index 4e9682ffe..36dc5a342 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Sha512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Sha512.java @@ -4,6 +4,7 @@ import fr.xephi.authme.security.HashUtils; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; +@Deprecated @Recommendation(Usage.DEPRECATED) public class Sha512 extends UnsaltedMethod { diff --git a/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java b/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java index c1ea747a9..7c62b2619 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java @@ -64,6 +64,7 @@ import fr.xephi.authme.security.crypts.description.Usage; import java.util.Arrays; +@Deprecated @Recommendation(Usage.DEPRECATED) public class Whirlpool extends UnsaltedMethod { diff --git a/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java b/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java index 901c9fc1e..b18944aac 100644 --- a/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java +++ b/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java @@ -3,8 +3,8 @@ package fr.xephi.authme.settings; import ch.jalu.configme.properties.Property; import ch.jalu.configme.resource.PropertyResource; +import java.util.Collection; import java.util.LinkedHashSet; -import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -27,9 +27,9 @@ public class EnumSetProperty> extends Property> { @Override protected Set getFromResource(PropertyResource resource) { - List elements = resource.getList(getPath()); - if (elements != null) { - return elements.stream() + Object entry = resource.getObject(getPath()); + if (entry instanceof Collection) { + return ((Collection) entry).stream() .map(val -> toEnum(String.valueOf(val))) .filter(e -> e != null) .collect(Collectors.toCollection(LinkedHashSet::new)); diff --git a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java index c2dd46558..9be278807 100644 --- a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java +++ b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java @@ -10,6 +10,7 @@ import fr.xephi.authme.initialization.DataFolder; 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.PluginSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.SecuritySettings; @@ -20,6 +21,7 @@ import java.io.FileWriter; import java.io.IOException; import java.util.Collections; import java.util.List; +import java.util.Set; import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; @@ -73,6 +75,7 @@ public class SettingsMigrationService extends PlainMigrationService { | hasSupportOldPasswordProperty(resource) | convertToRegistrationType(resource) | mergeAndMovePermissionGroupSettings(resource) + | moveDeprecatedHashAlgorithmIntoLegacySection(resource) || hasDeprecatedProperties(resource); } @@ -286,6 +289,31 @@ public class SettingsMigrationService extends PlainMigrationService { 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 resource The property resource + * @return True if the configuration has changed, false otherwise + */ + private static boolean moveDeprecatedHashAlgorithmIntoLegacySection(PropertyResource resource) { + HashAlgorithm currentHash = SecuritySettings.PASSWORD_HASH.getValue(resource); + // 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)) { + resource.setValue(SecuritySettings.PASSWORD_HASH.getPath(), HashAlgorithm.SHA256); + Set legacyHashes = SecuritySettings.LEGACY_HASHES.getValue(resource); + legacyHashes.add(currentHash); + resource.setValue(SecuritySettings.LEGACY_HASHES.getPath(), legacyHashes); + ConsoleLogger.warning("The hash algorithm '" + currentHash + + "' is no longer supported for active use. New hashes will be in SHA256."); + return true; + } + } + return false; + } + /** * 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. * diff --git a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java index 07d0eaf96..0a32c5584 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java @@ -53,7 +53,7 @@ public final class SecuritySettings implements SettingsHolder { newProperty("settings.security.passwordMaxLength", 30); @Comment({ - "Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL,", + "Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512,", "MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB,", "PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (for developers only). See full list at", "https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/hash_algorithms.md" diff --git a/src/test/java/fr/xephi/authme/ConsoleLoggerTest.java b/src/test/java/fr/xephi/authme/ConsoleLoggerTest.java index 7d01a7db1..b8deb9349 100644 --- a/src/test/java/fr/xephi/authme/ConsoleLoggerTest.java +++ b/src/test/java/fr/xephi/authme/ConsoleLoggerTest.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.List; +import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; @@ -142,25 +143,22 @@ public class ConsoleLoggerTest { ConsoleLogger.setLoggingOptions(newSettings(true, LogLevel.DEBUG)); // when - ConsoleLogger.debug("Got {0} entries", "test"); - ConsoleLogger.debug("Player `{0}` is in world `{1}`", "Bobby", "world"); - ConsoleLogger.debug("The {0} is {1} the {2}", "fox", "behind", "chicken"); + ConsoleLogger.debug("Got {0} entries", 17); + ConsoleLogger.debug("Player `{0}` is in world `{1}`", "Bobby", new World("world")); ConsoleLogger.debug("{0} quick {1} jump over {2} lazy {3} (reason: {4})", 5, "foxes", 3, "dogs", null); ConsoleLogger.debug(() -> "Too little too late"); // then - verify(logger).log(Level.INFO, "[DEBUG] Got {0} entries", "test"); - verify(logger).log(Level.INFO, "[DEBUG] Player `{0}` is in world `{1}`", new Object[]{"Bobby", "world"}); - verify(logger).log(Level.INFO, "[DEBUG] The {0} is {1} the {2}", new Object[]{"fox", "behind", "chicken"}); + verify(logger).log(Level.INFO, "[DEBUG] Got {0} entries", 17); + verify(logger).log(Level.INFO, "[DEBUG] Player `{0}` is in world `{1}`", new Object[]{"Bobby", new World("world")}); verify(logger).log(Level.INFO, "[DEBUG] {0} quick {1} jump over {2} lazy {3} (reason: {4})", - new Object[]{"5", "foxes", "3", "dogs", "null"}); + new Object[]{5, "foxes", 3, "dogs", null}); verify(logger).info("[DEBUG] Too little too late"); List loggedLines = Files.readAllLines(logFile.toPath(), StandardCharsets.UTF_8); assertThat(loggedLines, contains( - containsString("[DEBUG] Got {0} entries {test}"), - containsString("[DEBUG] Player `{0}` is in world `{1}` {Bobby, world}"), - containsString("[DEBUG] The {0} is {1} the {2} {fox, behind, chicken}"), + containsString("[DEBUG] Got {0} entries {17}"), + containsString("[DEBUG] Player `{0}` is in world `{1}` {Bobby, w[world]}"), containsString("[DEBUG] {0} quick {1} jump over {2} lazy {3} (reason: {4}) {5, foxes, 3, dogs, null}"), containsString("[DEBUG] Too little too late"))); } @@ -176,4 +174,25 @@ public class ConsoleLoggerTest { given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(logLevel); return settings; } + + private static final class World { + private final String name; + + World(String name) { + this.name = name; + } + + @Override + public String toString() { + return "w[" + name + "]"; + } + + @Override + public boolean equals(Object other) { + if (other instanceof World) { + return Objects.equals(this.name, ((World) other).name); + } + return false; + } + } } diff --git a/src/test/java/fr/xephi/authme/initialization/OnStartupTasksTest.java b/src/test/java/fr/xephi/authme/initialization/OnStartupTasksTest.java deleted file mode 100644 index e3febf41f..000000000 --- a/src/test/java/fr/xephi/authme/initialization/OnStartupTasksTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package fr.xephi.authme.initialization; - -import fr.xephi.authme.security.HashAlgorithm; -import org.junit.Test; - -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertThat; - -/** - * Test for {@link OnStartupTasks}. - */ -public class OnStartupTasksTest { - - @Test - public void shouldCheckIfHashIsDeprecatedIn54() { - // given / when / then - assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.CUSTOM), equalTo(false)); - assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.IPB3), equalTo(false)); - assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.PLAINTEXT), equalTo(false)); - assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.SHA256), equalTo(false)); - assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.WORDPRESS), equalTo(false)); - - assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.MD5), equalTo(true)); - assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.SHA512), equalTo(true)); - assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.WHIRLPOOL), equalTo(true)); - } -} diff --git a/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java b/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java index 4f3704175..bba4f968a 100644 --- a/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java @@ -4,6 +4,8 @@ import ch.jalu.injector.Injector; import ch.jalu.injector.InjectorBuilder; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.SecuritySettings; @@ -12,6 +14,8 @@ import org.junit.BeforeClass; import org.junit.Test; import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import static org.hamcrest.Matchers.equalTo; @@ -70,4 +74,29 @@ public class HashAlgorithmIntegrationTest { } } + @Test + public void shouldBeDeprecatedIfEncryptionClassIsDeprecated() throws NoSuchFieldException { + // given + List failedEntries = new LinkedList<>(); + + // when + for (HashAlgorithm hashAlgorithm : HashAlgorithm.values()) { + if (hashAlgorithm != HashAlgorithm.CUSTOM) { + boolean isEnumDeprecated = HashAlgorithm.class.getDeclaredField(hashAlgorithm.name()) + .isAnnotationPresent(Deprecated.class); + boolean isDeprecatedClass = hashAlgorithm.getClazz().isAnnotationPresent(Deprecated.class); + Recommendation recommendation = hashAlgorithm.getClazz().getAnnotation(Recommendation.class); + boolean hasDeprecatedUsage = recommendation != null && recommendation.value() == Usage.DEPRECATED; + if (isEnumDeprecated != isDeprecatedClass || isEnumDeprecated != hasDeprecatedUsage) { + failedEntries.add(hashAlgorithm + ": enum @Deprecated = " + isEnumDeprecated + + ", @Deprecated class = " + isDeprecatedClass + ", usage Deprecated = " + hasDeprecatedUsage); + } + } + } + + // then + if (!failedEntries.isEmpty()) { + fail("Found inconsistencies:\n" + String.join("\n", failedEntries)); + } + } } diff --git a/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java b/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java index da7c270be..0a952bb0b 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java @@ -1,12 +1,16 @@ package fr.xephi.authme.settings; +import ch.jalu.configme.configurationdata.ConfigurationData; +import ch.jalu.configme.properties.Property; import ch.jalu.configme.resource.PropertyResource; import ch.jalu.configme.resource.YamlFileResource; import com.google.common.io.Files; import fr.xephi.authme.TestHelper; +import fr.xephi.authme.initialization.DataFolder; 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.AuthMeSettingsRetriever; import org.junit.BeforeClass; import org.junit.Rule; @@ -16,6 +20,8 @@ import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import static fr.xephi.authme.TestHelper.getJarFile; import static fr.xephi.authme.settings.properties.PluginSettings.ENABLE_PERMISSION_CHECK; @@ -28,6 +34,8 @@ import static fr.xephi.authme.settings.properties.RegistrationSettings.REGISTRAT 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; +import static fr.xephi.authme.settings.properties.SecuritySettings.LEGACY_HASHES; +import static fr.xephi.authme.settings.properties.SecuritySettings.PASSWORD_HASH; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; @@ -47,8 +55,9 @@ public class SettingsMigrationServiceTest { TestHelper.setupLogger(); } + /* When settings are loaded, test that migrations are applied and immediately available in memory. */ @Test - public void shouldPerformMigrations() throws IOException { + public void shouldPerformMigrationsInMemory() throws IOException { // given File dataFolder = temporaryFolder.newFolder(); File configFile = new File(dataFolder, "config.yml"); @@ -61,22 +70,31 @@ public class SettingsMigrationServiceTest { dataFolder, resource, migrationService, AuthMeSettingsRetriever.buildConfigurationData()); // then - assertThat(settings.getProperty(ALLOWED_NICKNAME_CHARACTERS), equalTo(ALLOWED_NICKNAME_CHARACTERS.getDefaultValue())); - assertThat(settings.getProperty(DELAY_JOIN_MESSAGE), equalTo(true)); - assertThat(settings.getProperty(FORCE_SPAWN_LOCATION_AFTER_LOGIN), equalTo(true)); - assertThat(settings.getProperty(FORCE_SPAWN_ON_WORLDS), contains("survival", "survival_nether", "creative")); - assertThat(settings.getProperty(LOG_LEVEL), equalTo(LogLevel.INFO)); - assertThat(settings.getProperty(REGISTRATION_TYPE), equalTo(RegistrationType.EMAIL)); - assertThat(settings.getProperty(REGISTER_SECOND_ARGUMENT), equalTo(RegisterSecondaryArgument.CONFIRMATION)); - assertThat(settings.getProperty(ENABLE_PERMISSION_CHECK), equalTo(true)); - assertThat(settings.getProperty(REGISTERED_GROUP), equalTo("unLoggedinGroup")); - assertThat(settings.getProperty(UNREGISTERED_GROUP), equalTo("")); + verifyHasUpToDateSettings(settings, dataFolder); + } - // Check migration of old setting to email.html - assertThat(Files.readLines(new File(dataFolder, "email.html"), StandardCharsets.UTF_8), - contains("Dear ,

This is your new AuthMe password for the server " - + "

:



Do not forget to " - + "change password after login!
/changepassword newPassword")); + /* + * When settings are loaded, test that migrations are applied and persisted to disk, + * i.e. when the settings are loaded again from the file, no migrations should be necessary. + */ + @Test + public void shouldPerformMigrationsAndPersistToDisk() throws IOException { + // given + File dataFolder = temporaryFolder.newFolder(); + File configFile = new File(dataFolder, "config.yml"); + Files.copy(getJarFile(OLD_CONFIG_FILE), configFile); + PropertyResource resource = new YamlFileResource(configFile); + TestMigrationServiceExtension migrationService = new TestMigrationServiceExtension(dataFolder); + ConfigurationData configurationData = AuthMeSettingsRetriever.buildConfigurationData(); + + // when + new Settings(dataFolder, resource, migrationService, configurationData); + resource = new YamlFileResource(configFile); + Settings settings = new Settings(dataFolder, resource, migrationService, configurationData); + + // then + verifyHasUpToDateSettings(settings, dataFolder); + assertThat(migrationService.returnedValues, contains(true, false)); } @Test @@ -97,4 +115,40 @@ public class SettingsMigrationServiceTest { assertThat(migrationService.getOnRegisterCommands(), contains("me registers", "msg CONSOLE hi")); assertThat(migrationService.getOnRegisterConsoleCommands(), contains("sethome %p:regloc")); } + + private void verifyHasUpToDateSettings(Settings settings, File dataFolder) throws IOException { + assertThat(settings.getProperty(ALLOWED_NICKNAME_CHARACTERS), equalTo(ALLOWED_NICKNAME_CHARACTERS.getDefaultValue())); + assertThat(settings.getProperty(DELAY_JOIN_MESSAGE), equalTo(true)); + assertThat(settings.getProperty(FORCE_SPAWN_LOCATION_AFTER_LOGIN), equalTo(true)); + assertThat(settings.getProperty(FORCE_SPAWN_ON_WORLDS), contains("survival", "survival_nether", "creative")); + assertThat(settings.getProperty(LOG_LEVEL), equalTo(LogLevel.INFO)); + assertThat(settings.getProperty(REGISTRATION_TYPE), equalTo(RegistrationType.EMAIL)); + assertThat(settings.getProperty(REGISTER_SECOND_ARGUMENT), equalTo(RegisterSecondaryArgument.CONFIRMATION)); + assertThat(settings.getProperty(ENABLE_PERMISSION_CHECK), equalTo(true)); + assertThat(settings.getProperty(REGISTERED_GROUP), equalTo("unLoggedinGroup")); + assertThat(settings.getProperty(UNREGISTERED_GROUP), equalTo("")); + assertThat(settings.getProperty(PASSWORD_HASH), equalTo(HashAlgorithm.SHA256)); + assertThat(settings.getProperty(LEGACY_HASHES), contains(HashAlgorithm.PBKDF2, HashAlgorithm.WORDPRESS, HashAlgorithm.SHA512)); + + // Check migration of old setting to email.html + assertThat(Files.readLines(new File(dataFolder, "email.html"), StandardCharsets.UTF_8), + contains("Dear ,

This is your new AuthMe password for the server " + + "

:



Do not forget to " + + "change password after login!
/changepassword newPassword")); + } + + private static class TestMigrationServiceExtension extends SettingsMigrationService { + private List returnedValues = new ArrayList<>(); + + TestMigrationServiceExtension(@DataFolder File pluginFolder) { + super(pluginFolder); + } + + @Override + protected boolean performMigrations(PropertyResource resource, List> properties) { + boolean result = super.performMigrations(resource, properties); + returnedValues.add(result); + return result; + } + } } diff --git a/src/test/resources/fr/xephi/authme/settings/config-old.yml b/src/test/resources/fr/xephi/authme/settings/config-old.yml index 65e2614ce..0d2e425c5 100644 --- a/src/test/resources/fr/xephi/authme/settings/config-old.yml +++ b/src/test/resources/fr/xephi/authme/settings/config-old.yml @@ -191,7 +191,10 @@ settings: # PLAINTEXT ( unhashed password), # MYBB, IPB3, PHPFUSION, SMF, XENFORO, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512, # DOUBLEMD5, PBKDF2, PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM(for developpers only) - passwordHash: SHA256 + passwordHash: SHA512 + legacyHashes: + - PBKDF2 + - WORDPRESS # salt length for the SALTED2MD5 MD5(MD5(password)+salt) doubleMD5SaltLength: 8 # If password checking return false , do we need to check with all