From b0ba89382723fabfcbf0382e8e9e9a602026898c Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 17 Jan 2016 20:41:19 +0100 Subject: [PATCH] #437 Add/change email should check if email is already used - Untested/incomplete implementation --- .../authme/datasource/CacheDataSource.java | 5 + .../xephi/authme/datasource/DataSource.java | 2 + .../fr/xephi/authme/datasource/FlatFile.java | 174 +----------------- .../fr/xephi/authme/datasource/MySQL.java | 29 ++- .../fr/xephi/authme/datasource/SQLite.java | 19 ++ .../fr/xephi/authme/output/MessageKey.java | 4 +- .../authme/process/email/AsyncAddEmail.java | 5 +- .../process/email/AsyncChangeEmail.java | 2 + src/main/resources/messages/messages_en.yml | 1 + .../process/email/AsyncAddEmailTest.java | 22 +++ .../process/email/AsyncChangeEmailTest.java | 19 ++ 11 files changed, 105 insertions(+), 177 deletions(-) diff --git a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java index e573c6d04..6fe3f2267 100644 --- a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java @@ -289,4 +289,9 @@ public class CacheDataSource implements DataSource { public List getLoggedPlayers() { return new ArrayList<>(PlayerCache.getInstance().getCache().values()); } + + @Override + public boolean isEmailStored(String email) { + return source.isEmailStored(email); + } } diff --git a/src/main/java/fr/xephi/authme/datasource/DataSource.java b/src/main/java/fr/xephi/authme/datasource/DataSource.java index 2f466defc..b59e1daa5 100644 --- a/src/main/java/fr/xephi/authme/datasource/DataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/DataSource.java @@ -218,6 +218,8 @@ public interface DataSource { */ List getLoggedPlayers(); + boolean isEmailStored(String email); + enum DataSourceType { MYSQL, FILE, diff --git a/src/main/java/fr/xephi/authme/datasource/FlatFile.java b/src/main/java/fr/xephi/authme/datasource/FlatFile.java index 4d7b4b38e..fd93047c0 100644 --- a/src/main/java/fr/xephi/authme/datasource/FlatFile.java +++ b/src/main/java/fr/xephi/authme/datasource/FlatFile.java @@ -52,13 +52,6 @@ public class FlatFile implements DataSource { } } - /** - * Method isAuthAvailable. - * - * @param user String - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) - */ @Override public synchronized boolean isAuthAvailable(String user) { BufferedReader br = null; @@ -97,13 +90,6 @@ public class FlatFile implements DataSource { return null; } - /** - * Method saveAuth. - * - * @param auth PlayerAuth - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) - */ @Override public synchronized boolean saveAuth(PlayerAuth auth) { if (isAuthAvailable(auth.getNickname())) { @@ -127,13 +113,6 @@ public class FlatFile implements DataSource { return true; } - /** - * Method updatePassword. - * - * @param auth PlayerAuth - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) - */ @Override public synchronized boolean updatePassword(PlayerAuth auth) { return updatePassword(auth.getNickname(), auth.getPassword()); @@ -200,13 +179,6 @@ public class FlatFile implements DataSource { return true; } - /** - * Method updateSession. - * - * @param auth PlayerAuth - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) - */ @Override public boolean updateSession(PlayerAuth auth) { if (!isAuthAvailable(auth.getNickname())) { @@ -266,13 +238,6 @@ public class FlatFile implements DataSource { return true; } - /** - * Method updateQuitLoc. - * - * @param auth PlayerAuth - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) - */ @Override public boolean updateQuitLoc(PlayerAuth auth) { if (!isAuthAvailable(auth.getNickname())) { @@ -311,13 +276,6 @@ public class FlatFile implements DataSource { return true; } - /** - * Method getIps. - * - * @param ip String - * - * @return int * @see fr.xephi.authme.datasource.DataSource#getIps(String) - */ @Override public int getIps(String ip) { BufferedReader br = null; @@ -348,13 +306,6 @@ public class FlatFile implements DataSource { } } - /** - * Method purgeDatabase. - * - * @param until long - * - * @return int * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) - */ @Override public int purgeDatabase(long until) { BufferedReader br = null; @@ -401,13 +352,6 @@ public class FlatFile implements DataSource { return cleared; } - /** - * Method autoPurgeDatabase. - * - * @param until long - * - * @return List of String * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) - */ @Override public List autoPurgeDatabase(long until) { BufferedReader br = null; @@ -454,13 +398,6 @@ public class FlatFile implements DataSource { return cleared; } - /** - * Method removeAuth. - * - * @param user String - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) - */ @Override public synchronized boolean removeAuth(String user) { if (!isAuthAvailable(user)) { @@ -505,13 +442,6 @@ public class FlatFile implements DataSource { return true; } - /** - * Method getAuth. - * - * @param user String - * - * @return PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getAuth(String) - */ @Override public synchronized PlayerAuth getAuth(String user) { BufferedReader br = null; @@ -554,31 +484,14 @@ public class FlatFile implements DataSource { return null; } - /** - * Method close. - * - * @see fr.xephi.authme.datasource.DataSource#close() - */ @Override public synchronized void close() { } - /** - * Method reload. - * - * @see fr.xephi.authme.datasource.DataSource#reload() - */ @Override public void reload() { } - /** - * Method updateEmail. - * - * @param auth PlayerAuth - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) - */ @Override public boolean updateEmail(PlayerAuth auth) { if (!isAuthAvailable(auth.getNickname())) { @@ -617,13 +530,6 @@ public class FlatFile implements DataSource { return true; } - /** - * Method getAllAuthsByName. - * - * @param auth PlayerAuth - * - * @return List of String * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) - */ @Override public List getAllAuthsByName(PlayerAuth auth) { BufferedReader br = null; @@ -654,13 +560,6 @@ public class FlatFile implements DataSource { } } - /** - * Method getAllAuthsByIp. - * - * @param ip String - * - * @return List of String * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) - */ @Override public List getAllAuthsByIp(String ip) { BufferedReader br = null; @@ -691,13 +590,6 @@ public class FlatFile implements DataSource { } } - /** - * Method getAllAuthsByEmail. - * - * @param email String - * - * @return List of String * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) - */ @Override public List getAllAuthsByEmail(String email) { BufferedReader br = null; @@ -728,13 +620,6 @@ public class FlatFile implements DataSource { } } - /** - * Method purgeBanned. - * - * @param banned List of String - * - * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) - */ @Override public void purgeBanned(List banned) { BufferedReader br = null; @@ -776,64 +661,28 @@ public class FlatFile implements DataSource { } } - /** - * Method getType. - * - * @return DataSourceType * @see fr.xephi.authme.datasource.DataSource#getType() - */ @Override public DataSourceType getType() { return DataSourceType.FILE; } - /** - * Method isLogged. - * - * @param user String - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#isLogged(String) - */ @Override public boolean isLogged(String user) { return PlayerCache.getInstance().isAuthenticated(user); } - /** - * Method setLogged. - * - * @param user String - * - * @see fr.xephi.authme.datasource.DataSource#setLogged(String) - */ @Override public void setLogged(String user) { } - /** - * Method setUnlogged. - * - * @param user String - * - * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) - */ @Override public void setUnlogged(String user) { } - /** - * Method purgeLogged. - * - * @see fr.xephi.authme.datasource.DataSource#purgeLogged() - */ @Override public void purgeLogged() { } - /** - * Method getAccountsRegistered. - * - * @return int * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() - */ @Override public int getAccountsRegistered() { BufferedReader br = null; @@ -857,14 +706,6 @@ public class FlatFile implements DataSource { return result; } - /** - * Method updateName. - * - * @param oldOne String - * @param newOne String - * - * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) - */ @Override public void updateName(String oldOne, String newOne) { PlayerAuth auth = this.getAuth(oldOne); @@ -873,11 +714,6 @@ public class FlatFile implements DataSource { this.removeAuth(oldOne); } - /** - * Method getAllAuths. - * - * @return List of PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getAllAuths() - */ @Override public List getAllAuths() { BufferedReader br = null; @@ -925,13 +761,13 @@ public class FlatFile implements DataSource { return auths; } - /** - * Method getLoggedPlayers. - * - * @return List of PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() - */ @Override public List getLoggedPlayers() { return new ArrayList<>(); } + + @Override + public boolean isEmailStored(String email) { + throw new UnsupportedOperationException("Flat file no longer supported"); + } } diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 9e459ad71..21987160e 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -971,9 +971,8 @@ public class MySQL implements DataSource { pst.close(); rs.close(); st.close(); - } catch (Exception ex) { - ConsoleLogger.showError(ex.getMessage()); - ConsoleLogger.writeStackTrace(ex); + } catch (SQLException ex) { + logSqlException(ex); } return auths; } @@ -1015,11 +1014,29 @@ public class MySQL implements DataSource { } auths.add(pAuth); } - } catch (Exception ex) { - ConsoleLogger.showError(ex.getMessage()); - ConsoleLogger.writeStackTrace(ex); + } catch (SQLException ex) { + logSqlException(ex); } return auths; } + @Override + public synchronized boolean isEmailStored(String email) { + String sql = "SELECT 1 FROM " + tableName + " WHERE " + columnEmail + " = ?"; + try (Connection con = ds.getConnection()) { + PreparedStatement pst = con.prepareStatement(sql); + pst.setString(1, email); + ResultSet rs = pst.executeQuery(); + return rs.next(); + } catch (SQLException e) { + logSqlException(e); + } + return false; + } + + private static void logSqlException(SQLException e) { + ConsoleLogger.showError("Error executing SQL query: " + StringUtils.formatException(e)); + ConsoleLogger.writeStackTrace(e); + } + } diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java index 9b14b1cc0..6ef8a406e 100644 --- a/src/main/java/fr/xephi/authme/datasource/SQLite.java +++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java @@ -684,6 +684,25 @@ public class SQLite implements DataSource { return auths; } + @Override + public synchronized boolean isEmailStored(String email) { + try { + PreparedStatement ps = con.prepareStatement( + "SELECT 1 FROM " + tableName + " WHERE LOWER(" + columnEmail + ") = LOWER(?)"); + ps.setString(1, email); + ResultSet rs = ps.executeQuery(); + return rs.next(); + } catch (SQLException e) { + logSqlException(e); + } + return false; + } + + private static void logSqlException(SQLException e) { + ConsoleLogger.showError("Error while executing SQL statement: " + StringUtils.formatException(e)); + ConsoleLogger.writeStackTrace(e); + } + private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException { String salt = !columnSalt.isEmpty() ? row.getString(columnSalt) : null; diff --git a/src/main/java/fr/xephi/authme/output/MessageKey.java b/src/main/java/fr/xephi/authme/output/MessageKey.java index fe006686a..0f33dc735 100644 --- a/src/main/java/fr/xephi/authme/output/MessageKey.java +++ b/src/main/java/fr/xephi/authme/output/MessageKey.java @@ -121,7 +121,9 @@ public enum MessageKey { ANTIBOT_AUTO_ENABLED_MESSAGE("antibot_auto_enabled"), - ANTIBOT_AUTO_DISABLED_MESSAGE("antibot_auto_disabled", "%m"); + ANTIBOT_AUTO_DISABLED_MESSAGE("antibot_auto_disabled", "%m"), + + EMAIL_ALREADY_USED_ERROR("email_already_used"); private String key; diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java index 92472ff05..a096d1507 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java @@ -36,10 +36,13 @@ public class AsyncAddEmail { PlayerAuth auth = playerCache.getAuth(playerName); String currentEmail = auth.getEmail(); - if (currentEmail != null) { + if (currentEmail != null && !"your@mail.com".equals(currentEmail)) { + System.out.println("Email is currentEmail " + currentEmail); // FIXME remove messages.send(player, MessageKey.USAGE_CHANGE_EMAIL); } else if (isEmailInvalid(email)) { messages.send(player, MessageKey.INVALID_EMAIL); + } else if (dataSource.isEmailStored(email)) { + messages.send(player, MessageKey.EMAIL_ALREADY_USED_ERROR); } else { auth.setEmail(email); playerCache.updatePlayer(auth); diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java index c69e37952..a17c262d9 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java @@ -44,6 +44,8 @@ public class AsyncChangeEmail { m.send(player, MessageKey.INVALID_NEW_EMAIL); } else if (!oldEmail.equals(currentEmail)) { m.send(player, MessageKey.INVALID_OLD_EMAIL); + } else if (dataSource.isEmailStored(newEmail)) { + m.send(player, MessageKey.EMAIL_ALREADY_USED_ERROR); } else { saveNewEmail(auth); } diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml index 2d26cb1f6..308593861 100644 --- a/src/main/resources/messages/messages_en.yml +++ b/src/main/resources/messages/messages_en.yml @@ -57,3 +57,4 @@ email_exists: '&cA recovery email was already sent! You can discard it and send country_banned: '&4Your country is banned from this server!' antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' antibot_auto_disabled: '&2[AntiBotService] AntiBot disabled disabled after %m minutes!' +email_already_used: '&4The email address is already being used' diff --git a/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java b/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java index 6d73b4710..97a6a9903 100644 --- a/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java +++ b/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java @@ -53,6 +53,7 @@ public class AsyncAddEmailTest { PlayerAuth auth = mock(PlayerAuth.class); given(auth.getEmail()).willReturn(null); given(playerCache.getAuth("tester")).willReturn(auth); + given(dataSource.isEmailStored("my.mail@example.org")).willReturn(false); // when process.process(); @@ -72,6 +73,7 @@ public class AsyncAddEmailTest { PlayerAuth auth = mock(PlayerAuth.class); given(auth.getEmail()).willReturn("another@mail.tld"); given(playerCache.getAuth("my_player")).willReturn(auth); + given(dataSource.isEmailStored("some.mail@example.org")).willReturn(false); // when process.process(); @@ -90,6 +92,7 @@ public class AsyncAddEmailTest { PlayerAuth auth = mock(PlayerAuth.class); given(auth.getEmail()).willReturn(null); given(playerCache.getAuth("my_player")).willReturn(auth); + given(dataSource.isEmailStored("invalid_mail")).willReturn(false); // when process.process(); @@ -99,6 +102,25 @@ public class AsyncAddEmailTest { verify(playerCache, never()).updatePlayer(any(PlayerAuth.class)); } + @Test + public void shouldNotAddMailIfAlreadyUsed() { + // given + AsyncAddEmail process = createProcess("player@mail.tld"); + given(player.getName()).willReturn("TestName"); + given(playerCache.isAuthenticated("testname")).willReturn(true); + PlayerAuth auth = mock(PlayerAuth.class); + given(auth.getEmail()).willReturn(null); + given(playerCache.getAuth("testname")).willReturn(auth); + given(dataSource.isEmailStored("player@mail.tld")).willReturn(true); + + // when + process.process(); + + // then + verify(messages).send(player, MessageKey.EMAIL_ALREADY_USED_ERROR); + verify(playerCache, never()).updatePlayer(any(PlayerAuth.class)); + } + @Test public void shouldShowLoginMessage() { // given diff --git a/src/test/java/fr/xephi/authme/process/email/AsyncChangeEmailTest.java b/src/test/java/fr/xephi/authme/process/email/AsyncChangeEmailTest.java index 92cfec679..4faf14837 100644 --- a/src/test/java/fr/xephi/authme/process/email/AsyncChangeEmailTest.java +++ b/src/test/java/fr/xephi/authme/process/email/AsyncChangeEmailTest.java @@ -136,6 +136,25 @@ public class AsyncChangeEmailTest { verify(messages).send(player, MessageKey.INVALID_OLD_EMAIL); } + @Test + public void shouldRejectAlreadyUsedEmail() { + // given + AsyncChangeEmail process = createProcess("old@example.com", "new@example.com"); + given(player.getName()).willReturn("Username"); + given(playerCache.isAuthenticated("username")).willReturn(true); + PlayerAuth auth = authWithMail("old@example.com"); + given(playerCache.getAuth("username")).willReturn(auth); + given(dataSource.isEmailStored("new@example.com")).willReturn(true); + + // when + process.process(); + + // then + verify(dataSource, never()).updateEmail(any(PlayerAuth.class)); + verify(playerCache, never()).updatePlayer(any(PlayerAuth.class)); + verify(messages).send(player, MessageKey.EMAIL_ALREADY_USED_ERROR); + } + @Test public void shouldSendLoginMessage() { // given