diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index 7bff3215..8965c96f 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -44,36 +44,27 @@ public class RecoverEmailCommand extends PlayerCommand { commandService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS); return; } - if (dataSource.isAuthAvailable(playerName)) { - if (playerCache.isAuthenticated(playerName)) { - commandService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); - return; - } - - PlayerAuth auth; - if (playerCache.isAuthenticated(playerName)) { - auth = playerCache.getAuth(playerName); - } else if (dataSource.isAuthAvailable(playerName)) { - auth = dataSource.getAuth(playerName); - } else { - commandService.send(player, MessageKey.UNKNOWN_USER); - return; - } - - if (!playerMail.equalsIgnoreCase(auth.getEmail()) || "your@email.com".equalsIgnoreCase(playerMail) - || "your@email.com".equalsIgnoreCase(auth.getEmail())) { - commandService.send(player, MessageKey.INVALID_EMAIL); - return; - } - - String thePass = RandomString.generate(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)); - HashedPassword hashNew = passwordSecurity.computeHash(thePass, playerName); - auth.setPassword(hashNew); - dataSource.updatePassword(auth); - sendMailSsl.sendPasswordMail(auth, thePass); - commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); - } else { - commandService.send(player, MessageKey.REGISTER_EMAIL_MESSAGE); + if (playerCache.isAuthenticated(playerName)) { + commandService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); + return; } + + PlayerAuth auth = dataSource.getAuth(playerName); + if (auth == null) { + commandService.send(player, MessageKey.REGISTER_EMAIL_MESSAGE); + return; + } + + if (!playerMail.equalsIgnoreCase(auth.getEmail()) || "your@email.com".equalsIgnoreCase(auth.getEmail())) { + commandService.send(player, MessageKey.INVALID_EMAIL); + return; + } + + String thePass = RandomString.generate(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)); + HashedPassword hashNew = passwordSecurity.computeHash(thePass, playerName); + auth.setPassword(hashNew); + dataSource.updatePassword(auth); + sendMailSsl.sendPasswordMail(auth, thePass); + commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); } } diff --git a/src/test/java/fr/xephi/authme/ConsoleLoggerTest.java b/src/test/java/fr/xephi/authme/ConsoleLoggerTest.java new file mode 100644 index 00000000..5bf775ec --- /dev/null +++ b/src/test/java/fr/xephi/authme/ConsoleLoggerTest.java @@ -0,0 +1,142 @@ +package fr.xephi.authme; + +import fr.xephi.authme.output.LogLevel; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.PluginSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.StringUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.List; +import java.util.logging.Logger; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.anyString; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Test for {@link ConsoleLogger}. + */ +@RunWith(MockitoJUnitRunner.class) +public class ConsoleLoggerTest { + + @Mock + private Logger logger; + + @Mock + private NewSetting settings; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private File logFile; + + @Before + public void setMockLogger() throws IOException { + ConsoleLogger.setLogger(logger); + File folder = temporaryFolder.newFolder(); + File logFile = new File(folder, "authme.log"); + if (!logFile.createNewFile()) { + throw new IOException("Could not create file '" + logFile.getPath() + "'"); + } + ConsoleLogger.setLogFile(logFile); + this.logFile = logFile; + } + + @After + public void closeFileHandlers() { + ConsoleLogger.close(); + } + + @Test + public void shouldLogToFile() throws IOException { + // given + ConsoleLogger.setLoggingOptions(newSettings(true, LogLevel.FINE)); + + // when + ConsoleLogger.fine("Logging a FINE message"); + ConsoleLogger.debug("Logging a DEBUG message"); + ConsoleLogger.info("This is an INFO message"); + + // then + verify(logger, times(2)).info(anyString()); + verifyNoMoreInteractions(logger); + List loggedLines = Files.readAllLines(logFile.toPath(), Charset.forName("UTF-8")); + assertThat(loggedLines, hasSize(2)); + assertThat(loggedLines.get(0), containsString("[FINE] Logging a FINE message")); + assertThat(loggedLines.get(1), containsString("[INFO] This is an INFO message")); + } + + @Test + public void shouldNotLogToFile() throws IOException { + // given + ConsoleLogger.setLoggingOptions(newSettings(false, LogLevel.DEBUG)); + + // when + ConsoleLogger.debug("Created test"); + ConsoleLogger.warning("Encountered a warning"); + + // then + verify(logger).info("Debug: Created test"); + verify(logger).warning("Encountered a warning"); + verifyNoMoreInteractions(logger); + assertThat(logFile.length(), equalTo(0L)); + } + + @Test + public void shouldLogStackTraceToFile() throws IOException { + // given + ConsoleLogger.setLoggingOptions(newSettings(true, LogLevel.INFO)); + Exception e = new IllegalStateException("Test exception message"); + + // when + ConsoleLogger.info("Info text"); + ConsoleLogger.debug("Debug message"); + ConsoleLogger.fine("Fine-level message"); + ConsoleLogger.logException("Exception occurred:", e); + + // then + verify(logger).info("Info text"); + verify(logger).warning("Exception occurred: [IllegalStateException]: Test exception message"); + verifyNoMoreInteractions(logger); + List loggedLines = Files.readAllLines(logFile.toPath(), Charset.forName("UTF-8")); + assertThat(loggedLines.size(), greaterThan(3)); + assertThat(loggedLines.get(0), containsString("[INFO] Info text")); + assertThat(loggedLines.get(1), + containsString("[WARN] Exception occurred: [IllegalStateException]: Test exception message")); + // Check that we have this class' full name somewhere in the file -> stacktrace of Exception e + assertThat(StringUtils.join("", loggedLines), containsString(getClass().getCanonicalName())); + } + + @Test + public void shouldHaveHiddenConstructor() { + TestHelper.validateHasOnlyPrivateEmptyConstructor(ConsoleLogger.class); + } + + private static NewSetting newSettings(boolean logToFile, LogLevel logLevel) { + NewSetting settings = mock(NewSetting.class); + given(settings.getProperty(SecuritySettings.USE_LOGGING)).willReturn(logToFile); + given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(logLevel); + return settings; + } +} diff --git a/src/test/java/fr/xephi/authme/TestHelper.java b/src/test/java/fr/xephi/authme/TestHelper.java index 58f3355c..40cff709 100644 --- a/src/test/java/fr/xephi/authme/TestHelper.java +++ b/src/test/java/fr/xephi/authme/TestHelper.java @@ -52,7 +52,7 @@ public final class TestHelper { */ public static Path getJarPath(String path) { String sqlFilePath = getUriOrThrow(path).getPath(); - // Windows preprends the path with a '/' or '\', which Paths cannot handle + // Windows prepends the path with a '/' or '\', which Paths cannot handle String appropriatePath = System.getProperty("os.name").contains("indow") ? sqlFilePath.substring(1) : sqlFilePath; diff --git a/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java new file mode 100644 index 00000000..bbbef57e --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java @@ -0,0 +1,217 @@ +package fr.xephi.authme.command.executable.email; + +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.command.CommandService; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.mail.SendMailSSL; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.settings.properties.EmailSettings; +import org.bukkit.entity.Player; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import java.util.Collections; + +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link RecoverEmailCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class RecoverEmailCommandTest { + + private static final String DEFAULT_EMAIL = "your@email.com"; + + @InjectMocks + private RecoverEmailCommand command; + + @Mock + private PasswordSecurity passwordSecurity; + + @Mock + private CommandService commandService; + + @Mock + private DataSource dataSource; + + @Mock + private PlayerCache playerCache; + + @Mock + private SendMailSSL sendMailSsl; + + @BeforeClass + public static void initLogger() { + TestHelper.setupLogger(); + } + + @Test + public void shouldHandleMissingMailProperties() { + // given + given(sendMailSsl.hasAllInformation()).willReturn(false); + Player sender = mock(Player.class); + + // when + command.executeCommand(sender, Collections.singletonList("some@email.tld")); + + // then + verify(commandService).send(sender, MessageKey.INCOMPLETE_EMAIL_SETTINGS); + verifyZeroInteractions(dataSource, passwordSecurity); + } + + @Test + public void shouldShowErrorForAuthenticatedUser() { + // given + String name = "Bobby"; + Player sender = mock(Player.class); + given(sender.getName()).willReturn(name); + given(sendMailSsl.hasAllInformation()).willReturn(true); + given(playerCache.isAuthenticated(name)).willReturn(true); + + // when + command.executeCommand(sender, Collections.singletonList("bobby@example.org")); + + // then + verify(sendMailSsl).hasAllInformation(); + verifyZeroInteractions(dataSource); + verify(commandService).send(sender, MessageKey.ALREADY_LOGGED_IN_ERROR); + } + + @Test + public void shouldShowRegisterMessageForUnregisteredPlayer() { + // given + String name = "Player123"; + Player sender = mock(Player.class); + given(sender.getName()).willReturn(name); + given(sendMailSsl.hasAllInformation()).willReturn(true); + given(playerCache.isAuthenticated(name)).willReturn(false); + given(dataSource.getAuth(name)).willReturn(null); + + // when + command.executeCommand(sender, Collections.singletonList("someone@example.com")); + + // then + verify(sendMailSsl).hasAllInformation(); + verify(dataSource).getAuth(name); + verifyNoMoreInteractions(dataSource); + verify(commandService).send(sender, MessageKey.REGISTER_EMAIL_MESSAGE); + } + + @Test + public void shouldHandleDefaultEmail() { + // given + String name = "Tract0r"; + Player sender = mock(Player.class); + given(sender.getName()).willReturn(name); + given(sendMailSsl.hasAllInformation()).willReturn(true); + given(playerCache.isAuthenticated(name)).willReturn(false); + given(dataSource.getAuth(name)).willReturn(authWithEmail(DEFAULT_EMAIL)); + + // when + command.executeCommand(sender, Collections.singletonList(DEFAULT_EMAIL)); + + // then + verify(sendMailSsl).hasAllInformation(); + verify(dataSource).getAuth(name); + verifyNoMoreInteractions(dataSource); + verify(commandService).send(sender, MessageKey.INVALID_EMAIL); + } + + @Test + public void shouldHandleInvalidEmailInput() { + // given + String name = "Rapt0r"; + Player sender = mock(Player.class); + given(sender.getName()).willReturn(name); + given(sendMailSsl.hasAllInformation()).willReturn(true); + given(playerCache.isAuthenticated(name)).willReturn(false); + given(dataSource.getAuth(name)).willReturn(authWithEmail("raptor@example.org")); + + // when + command.executeCommand(sender, Collections.singletonList("wrong-email@example.com")); + + // then + verify(sendMailSsl).hasAllInformation(); + verify(dataSource).getAuth(name); + verifyNoMoreInteractions(dataSource); + verify(commandService).send(sender, MessageKey.INVALID_EMAIL); + } + + @Test + public void shouldResetPasswordAndSendEmail() { + // given + String name = "Vultur3"; + Player sender = mock(Player.class); + given(sender.getName()).willReturn(name); + given(sendMailSsl.hasAllInformation()).willReturn(true); + given(playerCache.isAuthenticated(name)).willReturn(false); + String email = "vulture@example.com"; + PlayerAuth auth = authWithEmail(email); + given(dataSource.getAuth(name)).willReturn(auth); + given(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20); + given(passwordSecurity.computeHash(anyString(), eq(name))) + .willAnswer(new Answer() { + @Override + public HashedPassword answer(InvocationOnMock invocationOnMock) { + return new HashedPassword((String) invocationOnMock.getArguments()[0]); + } + }); + + // when + command.executeCommand(sender, Collections.singletonList(email.toUpperCase())); + + // then + verify(sendMailSsl).hasAllInformation(); + verify(dataSource).getAuth(name); + verify(passwordSecurity).computeHash(anyString(), eq(name)); + verify(dataSource).updatePassword(auth); + assertThat(auth.getPassword().getHash(), stringWithLength(20)); + verify(sendMailSsl).sendPasswordMail(eq(auth), argThat(stringWithLength(20))); + verify(commandService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); + } + + + private static PlayerAuth authWithEmail(String email) { + return PlayerAuth.builder() + .name("tester") + .email(email) + .build(); + } + + private static Matcher stringWithLength(final int length) { + return new TypeSafeMatcher() { + + @Override + public void describeTo(Description description) { + description.appendText("String of length " + length); + } + + @Override + protected boolean matchesSafely(String s) { + return s.length() == length; + } + }; + } + +}