diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 3b037bfb1..77f419b00 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -338,7 +338,7 @@ public class AuthMe extends JavaPlugin { } database.reload(); messages.reload(newSettings.getMessagesFile()); - passwordSecurity.reload(newSettings); + passwordSecurity.reload(); spawnLoader.initialize(newSettings); } diff --git a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java index 289e11beb..79de45ad7 100644 --- a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java +++ b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java @@ -4,7 +4,6 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.settings.Settings; @@ -35,7 +34,6 @@ public class RakamakConverter implements Converter { @Override // TODO ljacqu 20151229: Restructure this into smaller portions public void run() { - HashAlgorithm hash = Settings.getPasswordHash; boolean useIP = Settings.rakamakUseIp; String fileName = Settings.rakamakUsers; String ipFileName = Settings.rakamakUsersIp; @@ -64,7 +62,7 @@ public class RakamakConverter implements Converter { while ((line = users.readLine()) != null) { if (line.contains("=")) { String[] arguments = line.split("="); - HashedPassword hashedPassword = passwordSecurity.computeHash(hash, arguments[1], arguments[0]); + HashedPassword hashedPassword = passwordSecurity.computeHash(arguments[1], arguments[0]); playerPSW.put(arguments[0], hashedPassword); } diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index da506b6bb..94a8abeb4 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -8,46 +8,75 @@ import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.plugin.PluginManager; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + /** * Manager class for password-related operations. */ public class PasswordSecurity { + private final NewSetting settings; private HashAlgorithm algorithm; private boolean supportOldAlgorithm; private final DataSource dataSource; private final PluginManager pluginManager; public PasswordSecurity(DataSource dataSource, NewSetting settings, PluginManager pluginManager) { + this.settings = settings; this.algorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH); this.supportOldAlgorithm = settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH); this.dataSource = dataSource; this.pluginManager = pluginManager; } + /** + * Compute the hash of the configured algorithm for the given password and username. + * + * @param password The password to hash + * @param playerName The player's name + * + * @return The password hash + */ public HashedPassword computeHash(String password, String playerName) { - return computeHash(algorithm, password, playerName); - } - - public HashedPassword computeHash(HashAlgorithm algorithm, String password, String playerName) { String playerLowerCase = playerName.toLowerCase(); - EncryptionMethod method = initializeEncryptionMethod(algorithm, playerLowerCase); + EncryptionMethod method = initializeEncryptionMethodWithEvent(algorithm, playerLowerCase); return method.computeHash(password, playerLowerCase); } + /** + * Check if the given password matches the player's stored password. + * + * @param password The password to check + * @param playerName The player to check for + * + * @return True if the password is correct, false otherwise + */ public boolean comparePassword(String password, String playerName) { HashedPassword auth = dataSource.getPassword(playerName); return auth != null && comparePassword(password, auth, playerName); } + /** + * Check if the given password matches the given hashed password. + * + * @param password The password to check + * @param hashedPassword The hashed password to check against + * @param playerName The player to check for + * + * @return True if the password matches, false otherwise + */ public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) { - EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); + EncryptionMethod method = initializeEncryptionMethodWithEvent(algorithm, playerName); String playerLowerCase = playerName.toLowerCase(); return methodMatches(method, password, hashedPassword, playerLowerCase) || supportOldAlgorithm && compareWithAllEncryptionMethods(password, hashedPassword, playerLowerCase); } - public void reload(NewSetting settings) { + /** + * Reload the configuration. + */ + public void reload() { this.algorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH); this.supportOldAlgorithm = settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH); } @@ -63,11 +92,10 @@ public class PasswordSecurity { * * @return True if there was a password match with another encryption method, false otherwise */ - private boolean compareWithAllEncryptionMethods(String password, HashedPassword hashedPassword, - String playerName) { + private boolean compareWithAllEncryptionMethods(String password, HashedPassword hashedPassword, String playerName) { for (HashAlgorithm algorithm : HashAlgorithm.values()) { if (!HashAlgorithm.CUSTOM.equals(algorithm)) { - EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm); + EncryptionMethod method = initializeEncryptionMethod(algorithm, settings); if (methodMatches(method, password, hashedPassword, playerName)) { hashPasswordForNewAlgorithm(password, playerName); return true; @@ -85,6 +113,7 @@ public class PasswordSecurity { * @param password The password to check * @param hashedPassword The hash to check against * @param playerName The name of the player + * * @return True if the password matched, false otherwise */ private static boolean methodMatches(EncryptionMethod method, String password, @@ -103,33 +132,45 @@ public class PasswordSecurity { * * @return The encryption method */ - private EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm, String playerName) { - EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm); + private EncryptionMethod initializeEncryptionMethodWithEvent(HashAlgorithm algorithm, String playerName) { + EncryptionMethod method = initializeEncryptionMethod(algorithm, settings); PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); pluginManager.callEvent(event); return event.getMethod(); } /** - * Initialize the encryption method corresponding to the given hash algorithm. + * Initialize the encryption method associated with the given hash algorithm. * * @param algorithm The algorithm to retrieve the encryption method for + * @param settings The settings instance to pass to the constructor if required * - * @return The associated encryption method + * @return The associated encryption method, or null if CUSTOM / deprecated */ - private static EncryptionMethod initializeEncryptionMethodWithoutEvent(HashAlgorithm algorithm) { + public static EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm, + NewSetting settings) { try { - return HashAlgorithm.CUSTOM.equals(algorithm) || HashAlgorithm.PLAINTEXT.equals(algorithm) - ? null - : algorithm.getClazz().newInstance(); - } catch (InstantiationException | IllegalAccessException e) { + if (HashAlgorithm.CUSTOM.equals(algorithm) || HashAlgorithm.PLAINTEXT.equals(algorithm)) { + return null; + } + Constructor constructor = algorithm.getClazz().getConstructors()[0]; + Class[] parameters = constructor.getParameterTypes(); + if (parameters.length == 0) { + return (EncryptionMethod) constructor.newInstance(); + } else if (parameters.length == 1 && parameters[0] == NewSetting.class) { + return (EncryptionMethod) constructor.newInstance(settings); + } else { + throw new UnsupportedOperationException("Did not find default constructor or constructor with settings " + + "parameter in class " + algorithm.getClazz().getSimpleName()); + } + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new UnsupportedOperationException("Constructor for '" + algorithm.getClazz().getSimpleName() + "' could not be invoked. (Is there no default constructor?)", e); } } private void hashPasswordForNewAlgorithm(String password, String playerName) { - HashedPassword hashedPassword = initializeEncryptionMethod(algorithm, playerName) + HashedPassword hashedPassword = initializeEncryptionMethodWithEvent(algorithm, playerName) .computeHash(password, playerName); dataSource.updatePassword(playerName, hashedPassword); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java index 181571b35..a114adc09 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -5,7 +5,8 @@ import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.util.StringUtils; @@ -13,6 +14,12 @@ import fr.xephi.authme.util.StringUtils; @HasSalt(value = SaltType.TEXT) // length depends on Settings.bCryptLog2Rounds public class BCRYPT implements EncryptionMethod { + private final int bCryptLog2Rounds; + + public BCRYPT(NewSetting settings) { + this.bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND); + } + @Override public String computeHash(String password, String salt, String name) { return BCryptService.hashpw(password, salt); @@ -36,7 +43,7 @@ public class BCRYPT implements EncryptionMethod { @Override public String generateSalt() { - return BCryptService.gensalt(Settings.bCryptLog2Rounds); + return BCryptService.gensalt(bCryptLog2Rounds); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java index 26efc1561..b1c0b0034 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java @@ -2,6 +2,10 @@ package fr.xephi.authme.security.crypts; /** * Public interface for custom password encryption methods. + *

+ * Note that {@link fr.xephi.authme.security.PasswordSecurity} requires classes implementing this interface + * to either have the default constructor or an accessible constructor with one parameter of type + * {@link fr.xephi.authme.settings.NewSetting}. */ public interface EncryptionMethod { @@ -31,9 +35,9 @@ public interface EncryptionMethod { /** * Check whether the given hash matches the clear-text password. * - * @param password The clear-text password to verify + * @param password The clear-text password to verify * @param hashedPassword The hash to check the password against - * @param name The player name to do the check for (sometimes required for generating the salt) + * @param name The player name to do the check for (sometimes required for generating the salt) * * @return True if the password matches, false otherwise */ diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java index 613bcd3f8..27066f7a2 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java @@ -5,7 +5,8 @@ import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.SecuritySettings; import static fr.xephi.authme.security.HashUtils.md5; @@ -13,6 +14,12 @@ import static fr.xephi.authme.security.HashUtils.md5; @HasSalt(value = SaltType.TEXT) // length defined by Settings.saltLength public class SALTED2MD5 extends SeparateSaltMethod { + private final int saltLength; + + public SALTED2MD5(NewSetting settings) { + saltLength = settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH); + } + @Override public String computeHash(String password, String salt, String name) { return md5(md5(password) + salt); @@ -20,7 +27,7 @@ public class SALTED2MD5 extends SeparateSaltMethod { @Override public String generateSalt() { - return RandomString.generateHex(Settings.saltLength); + return RandomString.generateHex(saltLength); } } diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index 7b6ce411b..b5d5c3872 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -45,8 +45,7 @@ public final class Settings { rakamakUsers, rakamakUsersIp, defaultWorld, crazyloginFileName; public static int getWarnMessageInterval, getSessionTimeout, getRegistrationTimeout, getMaxNickLength, getMinNickLength, - getNonActivatedGroup, maxLoginTry, captchaLength, saltLength, - bCryptLog2Rounds, getMaxLoginPerIp; + getNonActivatedGroup, maxLoginTry, captchaLength, getMaxLoginPerIp; protected static FileConfiguration configFile; /** @@ -108,11 +107,9 @@ public final class Settings { removePassword = configFile.getBoolean("Security.console.removePassword", true); maxLoginTry = configFile.getInt("Security.captcha.maxLoginTry", 5); captchaLength = configFile.getInt("Security.captcha.captchaLength", 5); - saltLength = configFile.getInt("settings.security.doubleMD5SaltLength", 8); multiverse = load(HooksSettings.MULTIVERSE); bungee = load(HooksSettings.BUNGEECORD); getForcedWorlds = configFile.getStringList("settings.restrictions.ForceSpawnOnTheseWorlds"); - bCryptLog2Rounds = configFile.getInt("ExternalBoardOptions.bCryptLog2Round", 10); defaultWorld = configFile.getString("Purge.defaultWorld", "world"); enableProtection = configFile.getBoolean("Protection.enableProtection", false); countries = configFile.getStringList("Protection.countries"); diff --git a/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java b/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java index 2472c6592..976a7a445 100644 --- a/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java @@ -1,10 +1,11 @@ package fr.xephi.authme.security; -import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.security.crypts.EncryptionMethod; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.HooksSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.util.StringUtils; -import fr.xephi.authme.util.WrapperMock; import org.junit.BeforeClass; import org.junit.Test; @@ -12,19 +13,25 @@ import java.util.HashSet; import java.util.Set; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; /** * Integration test for {@link HashAlgorithm}. */ public class HashAlgorithmIntegrationTest { + private static NewSetting settings; + @BeforeClass public static void setUpWrapper() { - WrapperMock.createInstance(); - Settings.bCryptLog2Rounds = 8; - Settings.saltLength = 16; + settings = mock(NewSetting.class); + given(settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND)).willReturn(8); + given(settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH)).willReturn(16); } @Test @@ -47,8 +54,10 @@ public class HashAlgorithmIntegrationTest { public void shouldBeAbleToInstantiateEncryptionAlgorithms() throws InstantiationException, IllegalAccessException { // given / when / then for (HashAlgorithm algorithm : HashAlgorithm.values()) { - if (!HashAlgorithm.CUSTOM.equals(algorithm)) { - EncryptionMethod method = algorithm.getClazz().newInstance(); + if (!HashAlgorithm.CUSTOM.equals(algorithm) && !HashAlgorithm.PLAINTEXT.equals(algorithm)) { + EncryptionMethod method = PasswordSecurity.initializeEncryptionMethod(algorithm, settings); + assertThat("Encryption method for algorithm '" + algorithm + "' is not null", + method, not(nullValue())); HashedPassword hashedPassword = method.computeHash("pwd", "name"); assertThat("Salt should not be null if method.hasSeparateSalt(), and vice versa. Method: '" + method + "'", StringUtils.isEmpty(hashedPassword.getSalt()), equalTo(!method.hasSeparateSalt())); diff --git a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java index de223e6f2..470b8a2d5 100644 --- a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java +++ b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java @@ -1,22 +1,25 @@ package fr.xephi.authme.security; import fr.xephi.authme.ReflectionTestUtils; -import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.TestHelper; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; -import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.security.crypts.EncryptionMethod; +import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.security.crypts.JOOMLA; -import fr.xephi.authme.security.crypts.PHPBB; import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.util.WrapperMock; import org.bukkit.event.Event; import org.bukkit.plugin.PluginManager; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import static org.hamcrest.Matchers.equalTo; @@ -35,19 +38,24 @@ import static org.mockito.Mockito.verify; /** * Test for {@link PasswordSecurity}. */ +@RunWith(MockitoJUnitRunner.class) public class PasswordSecurityTest { + @Mock private PluginManager pluginManager; + @Mock private DataSource dataSource; + @Mock private EncryptionMethod method; private Class caughtClassInEvent; + @BeforeClass + public static void setUpTest() { + TestHelper.setupLogger(); + } + @Before public void setUpMocks() { - WrapperMock.createInstance(); - pluginManager = mock(PluginManager.class); - dataSource = mock(DataSource.class); - method = mock(EncryptionMethod.class); caughtClassInEvent = null; // When the password encryption event is emitted, replace the encryption method with our mock. @@ -97,7 +105,6 @@ public class PasswordSecurityTest { String playerLowerCase = playerName.toLowerCase(); String clearTextPass = "passw0Rd1"; - PlayerAuth auth = mock(PlayerAuth.class); given(dataSource.getPassword(playerName)).willReturn(password); given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(false); PasswordSecurity security = @@ -165,6 +172,25 @@ public class PasswordSecurityTest { verify(dataSource).updatePassword(playerLowerCase, newPassword); } + @Test + public void shouldTryAllMethodsAndFail() { + // given + HashedPassword password = new HashedPassword("hashNotMatchingAnyMethod", "someBogusSalt"); + String playerName = "asfd"; + String clearTextPass = "someInvalidPassword"; + given(dataSource.getPassword(playerName)).willReturn(password); + given(method.comparePassword(clearTextPass, password, playerName)).willReturn(false); + PasswordSecurity security = + new PasswordSecurity(dataSource, mockSettings(HashAlgorithm.MD5, true), pluginManager); + + // when + boolean result = security.comparePassword(clearTextPass, playerName); + + // then + assertThat(result, equalTo(false)); + verify(dataSource, never()).updatePassword(anyString(), any(HashedPassword.class)); + } + @Test public void shouldHashPassword() { // given @@ -188,28 +214,6 @@ public class PasswordSecurityTest { assertThat(event.getPlayerName(), equalTo(usernameLowerCase)); } - @Test - public void shouldHashPasswordWithGivenAlgorithm() { - // given - String password = "TopSecretPass#112525"; - String username = "someone12"; - HashedPassword hashedPassword = new HashedPassword("~T!est#Hash", "__someSalt__"); - given(method.computeHash(password, username)).willReturn(hashedPassword); - PasswordSecurity security = - new PasswordSecurity(dataSource, mockSettings(HashAlgorithm.JOOMLA, true), pluginManager); - - // when - HashedPassword result = security.computeHash(HashAlgorithm.PHPBB, password, username); - - // then - assertThat(result, equalTo(hashedPassword)); - ArgumentCaptor captor = ArgumentCaptor.forClass(PasswordEncryptionEvent.class); - verify(pluginManager).callEvent(captor.capture()); - PasswordEncryptionEvent event = captor.getValue(); - assertThat(PHPBB.class.equals(caughtClassInEvent), equalTo(true)); - assertThat(event.getPlayerName(), equalTo(username)); - } - @Test public void shouldSkipCheckIfMandatorySaltIsUnavailable() { // given @@ -234,12 +238,13 @@ public class PasswordSecurityTest { @Test public void shouldReloadSettings() { // given - PasswordSecurity passwordSecurity = - new PasswordSecurity(dataSource, mockSettings(HashAlgorithm.BCRYPT, false), pluginManager); - NewSetting updatedSettings = mockSettings(HashAlgorithm.MD5, true); + NewSetting settings = mockSettings(HashAlgorithm.BCRYPT, false); + PasswordSecurity passwordSecurity = new PasswordSecurity(dataSource, settings, pluginManager); + given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.MD5); + given(settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH)).willReturn(true); // when - passwordSecurity.reload(updatedSettings); + passwordSecurity.reload(); // then assertThat(ReflectionTestUtils.getFieldValue(PasswordSecurity.class, passwordSecurity, "algorithm"), @@ -252,6 +257,8 @@ public class PasswordSecurityTest { NewSetting settings = mock(NewSetting.class); given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(algorithm); given(settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH)).willReturn(supportOldPassword); + given(settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND)).willReturn(8); + given(settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH)).willReturn(16); return settings; } diff --git a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java b/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java index 2c27e00a0..d8b6aa023 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java @@ -1,24 +1,25 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.settings.Settings; -import fr.xephi.authme.util.WrapperMock; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.HooksSettings; import org.junit.BeforeClass; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + /** * Test for {@link BCRYPT}. */ public class BcryptTest extends AbstractEncryptionMethodTest { @BeforeClass - public static void setUpSettings() { - WrapperMock.createInstance(); - Settings.bCryptLog2Rounds = 8; + public static void initializeLogger() { TestHelper.setupLogger(); } public BcryptTest() { - super(new BCRYPT(), + super(new BCRYPT(mockSettings()), "$2a$10$6iATmYgwJVc3YONhVcZFve3Cfb5GnwvKhJ20r.hMjmcNkIT9.Uh9K", // password "$2a$10$LOhUxhEcS0vgDPv/jkXvCurNb7LjP9xUlEolJGk.Uhgikqc6FtIOi", // PassWord1 "$2a$10$j9da7SGiaakWhzIms9BtwemLUeIhSEphGUQ3XSlvYgpYsGnGCKRBa", // &^%te$t?Pw@_ @@ -26,4 +27,10 @@ public class BcryptTest extends AbstractEncryptionMethodTest { ); } + private static NewSetting mockSettings() { + NewSetting settings = mock(NewSetting.class); + given(settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND)).willReturn(8); + return settings; + } + } diff --git a/src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java b/src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java index 1b7ac3f1f..325d37d57 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java @@ -1,7 +1,6 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.util.WrapperMock; import org.junit.BeforeClass; /** @@ -11,7 +10,6 @@ public class IPB4Test extends AbstractEncryptionMethodTest { @BeforeClass public static void setUpSettings() { - WrapperMock.createInstance(); TestHelper.setupLogger(); } diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java b/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java index b483421c6..fb51fabff 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java @@ -1,26 +1,28 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.settings.Settings; -import fr.xephi.authme.util.WrapperMock; -import org.junit.Before; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.SecuritySettings; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; /** * Test for {@link SALTED2MD5}. */ public class SALTED2MD5Test extends AbstractEncryptionMethodTest { - @Before - public void setUpAlgorithm() { - WrapperMock.createInstance(); - Settings.saltLength = 8; - } - public SALTED2MD5Test() { - super(new SALTED2MD5(), + super(new SALTED2MD5(mockSettings()), new HashedPassword("9f3d13dc01a6fe61fd669954174399f3", "9b5f5749"), // password new HashedPassword("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"), // PassWord1 new HashedPassword("38dcb83cc68424afe3cda012700c2bb1", "eb2c3394"), // &^%te$t?Pw@_ new HashedPassword("ad25606eae5b760c8a2469d65578ac39", "04eee598")); // âË_3(íù*) } + private static NewSetting mockSettings() { + NewSetting settings = mock(NewSetting.class); + given(settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH)).willReturn(8); + return settings; + } + } diff --git a/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java b/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java index 96c30d1c0..bda1c0f8c 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java @@ -1,7 +1,6 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.util.WrapperMock; import org.junit.BeforeClass; /** @@ -11,7 +10,6 @@ public class XFBCRYPTTest extends AbstractEncryptionMethodTest { @BeforeClass public static void setup() { - WrapperMock.createInstance(); TestHelper.setupLogger(); } diff --git a/src/tools/hashmethods/EncryptionMethodInfoGatherer.java b/src/tools/hashmethods/EncryptionMethodInfoGatherer.java index 237215aff..4ade88ce6 100644 --- a/src/tools/hashmethods/EncryptionMethodInfoGatherer.java +++ b/src/tools/hashmethods/EncryptionMethodInfoGatherer.java @@ -1,11 +1,16 @@ package hashmethods; import fr.xephi.authme.security.HashAlgorithm; +import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HexSaltedMethod; import fr.xephi.authme.security.crypts.description.AsciiRestricted; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.HooksSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; +import org.mockito.BDDMockito; import java.lang.annotation.Annotation; import java.util.HashMap; @@ -14,6 +19,7 @@ import java.util.Map; import java.util.Set; import static com.google.common.collect.Sets.newHashSet; +import static org.mockito.Mockito.mock; /** * Gathers information on {@link EncryptionMethod} implementations based on @@ -25,6 +31,8 @@ public class EncryptionMethodInfoGatherer { private final static Set> RELEVANT_ANNOTATIONS = newHashSet(HasSalt.class, Recommendation.class, AsciiRestricted.class); + private static NewSetting settings = createSettings(); + private Map descriptions; public EncryptionMethodInfoGatherer() { @@ -38,16 +46,19 @@ public class EncryptionMethodInfoGatherer { private void constructDescriptions() { for (HashAlgorithm algorithm : HashAlgorithm.values()) { - Class methodClazz = algorithm.getClazz(); - if (!HashAlgorithm.CUSTOM.equals(algorithm) && !methodClazz.isAnnotationPresent(Deprecated.class)) { - MethodDescription description = createDescription(methodClazz); + if (!HashAlgorithm.CUSTOM.equals(algorithm) && !algorithm.getClazz().isAnnotationPresent(Deprecated.class)) { + MethodDescription description = createDescription(algorithm); descriptions.put(algorithm, description); } } } - private static MethodDescription createDescription(Class clazz) { - EncryptionMethod method = instantiateMethod(clazz); + private static MethodDescription createDescription(HashAlgorithm algorithm) { + Class clazz = algorithm.getClazz(); + EncryptionMethod method = PasswordSecurity.initializeEncryptionMethod(algorithm, settings); + if (method == null) { + throw new NullPointerException("Method for '" + algorithm + "' is null"); + } MethodDescription description = new MethodDescription(clazz); description.setHashLength(method.computeHash("test", "user").getHash().length()); description.setHasSeparateSalt(method.hasSeparateSalt()); @@ -118,18 +129,25 @@ public class EncryptionMethodInfoGatherer { } } - private static EncryptionMethod instantiateMethod(Class clazz) { - try { - return clazz.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new RuntimeException("Could not instantiate " + clazz, e); - } - } - // Convenience method for retrieving an annotation in a typed fashion. // We know implicitly that the key of the map always corresponds to the type of the value private static T returnTyped(Map, Annotation> map, Class key) { return key.cast(map.get(key)); } + private static NewSetting createSettings() { + // TODO #672 Don't mock settings but instantiate a NewSetting object without any validation / migration + NewSetting settings = mock(NewSetting.class); + BDDMockito.given(settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND)).willReturn(8); + BDDMockito.given(settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH)).willReturn(8); + return settings; + + /*try (InputStreamReader isr = new InputStreamReader(getClass().getResourceAsStream("config.yml"))) { + FileConfiguration configuration = YamlConfiguration.loadConfiguration(isr); + return new NewSetting(configuration, null, null, null); + } catch (IOException e) { + throw new UnsupportedOperationException(e); + }*/ + } + } diff --git a/src/tools/hashmethods/HashAlgorithmsDescriptionTask.java b/src/tools/hashmethods/HashAlgorithmsDescriptionTask.java index 8c8cecbee..6d1a57ed1 100644 --- a/src/tools/hashmethods/HashAlgorithmsDescriptionTask.java +++ b/src/tools/hashmethods/HashAlgorithmsDescriptionTask.java @@ -1,8 +1,6 @@ package hashmethods; import fr.xephi.authme.security.HashAlgorithm; -import fr.xephi.authme.settings.Settings; -import fr.xephi.authme.util.WrapperMock; import utils.FileUtils; import utils.TagValue.NestedTagValue; import utils.TagValueHolder; @@ -24,12 +22,6 @@ public class HashAlgorithmsDescriptionTask implements ToolTask { @Override public void execute(Scanner scanner) { - // Unfortunately, we need the Wrapper to be around to work with Settings, and certain encryption methods - // directly read from the Settings file - WrapperMock.createInstance(); - Settings.bCryptLog2Rounds = 8; - Settings.saltLength = 8; - // Gather info and construct a row for each method EncryptionMethodInfoGatherer infoGatherer = new EncryptionMethodInfoGatherer(); Map descriptions = infoGatherer.getDescriptions();