diff --git a/README.md b/README.md index c862068f..ff0b7f4d 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ typing commands or using the inventory. It can also kick players with uncommonly
  • Website Integration
  • Click here for an example of the Config file
  • How to convert from Rakamak -
  • Convert from FlatFile (auths.db but not the sqlite one) to MySQL: /converter +
  • Convert from FlatFile (auths.db but not the sqlite one) to MySQL: /authme converter

  • diff --git a/pom.xml b/pom.xml index 0ed8714c..e5d70cd1 100644 --- a/pom.xml +++ b/pom.xml @@ -372,7 +372,7 @@ org.slf4j slf4j-jdk14 - 1.7.14 + 1.7.16 compile true @@ -400,7 +400,7 @@ com.google.code.gson gson - 2.5 + 2.6.1 compile true @@ -416,9 +416,9 @@ - com.maxmind.geoip2 - geoip2 - 2.6.0 + com.maxmind.geoip + geoip-api + 1.3.1 compile true diff --git a/samples/website_integration/bcrypt/form.php b/samples/website_integration/bcrypt/form.php new file mode 100644 index 00000000..7801be4d --- /dev/null +++ b/samples/website_integration/bcrypt/form.php @@ -0,0 +1,86 @@ + + + + + AuthMe Integration Sample + + + +Login sample +This is a demo form for AuthMe website integration. Enter your AuthMe login details +into the following form to test it. +
    + + + + + + + +
    Name
    Pass
    +
    '; +} + +function get_from_post_or_empty($index_name) { + return trim( + filter_input(INPUT_POST, $index_name, FILTER_UNSAFE_RAW, FILTER_REQUIRE_SCALAR | FILTER_FLAG_STRIP_LOW) + ?: ''); +} + + +// Login logic +function process_login($user, $pass) { + if (authme_check_password($user, $pass)) { + printf('

    Hello, %s!

    ', htmlspecialchars($user)); + echo 'Successful login. Nice to have you back!' + . '
    Back to form'; + return true; + } else { + echo '

    Error

    Invalid username or password.'; + } + return false; +} + +// Register logic +function process_register($user, $pass) { + if (authme_has_user($user)) { + echo '

    Error

    This user already exists.'; + } else { + // Note that we don't validate the password or username at all in this demo... + $register_success = authme_register($user, $pass); + if ($register_success) { + printf('

    Welcome, %s!

    Thanks for registering', htmlspecialchars($user)); + echo '
    Back to form'; + return true; + } else { + echo '

    Error

    Unfortunately, there was an error during the registration.'; + } + } + return false; +} + +?> + + + diff --git a/samples/website_integration/bcrypt/integration.php b/samples/website_integration/bcrypt/integration.php new file mode 100644 index 00000000..75911838 --- /dev/null +++ b/samples/website_integration/bcrypt/integration.php @@ -0,0 +1,107 @@ +prepare('SELECT password FROM ' . AUTHME_TABLE . ' WHERE username = ?'); + $stmt->bind_param('s', $username); + $stmt->execute(); + $stmt->bind_result($password); + if ($stmt->fetch()) { + return $password; + } + } + return null; +} + +/** + * Returns whether the user exists in the database or not. + * + * @param string $username the username to check + * @return bool true if the user exists; false otherwise + */ +function authme_has_user($username) { + $mysqli = authme_get_mysqli(); + if ($mysqli !== null) { + $stmt = $mysqli->prepare('SELECT 1 FROM ' . AUTHME_TABLE . ' WHERE username = ?'); + $stmt->bind_param('s', $username); + $stmt->execute(); + return $stmt->fetch(); + } + + // Defensive default to true; we actually don't know + return true; +} + +/** + * Registers a player with the given username. + * + * @param string $username the username to register + * @param string $password the password to associate to the user + * @return bool whether or not the registration was successful + */ +function authme_register($username, $password) { + $mysqli = authme_get_mysqli(); + if ($mysqli !== null) { + $hash = password_hash($password, PASSWORD_BCRYPT); + $stmt = $mysqli->prepare('INSERT INTO ' . AUTHME_TABLE . ' (username, realname, password, ip) ' + . 'VALUES (?, ?, ?, ?)'); + $username_low = strtolower($username); + $stmt->bind_param('ssss', $username, $username_low, $hash, $_SERVER['REMOTE_ADDR']); + return $stmt->execute(); + } + return false; +} + diff --git a/samples/website_integration/form.php b/samples/website_integration/sha256/form.php similarity index 91% rename from samples/website_integration/form.php rename to samples/website_integration/sha256/form.php index 5f2985cb..5ffecf34 100644 --- a/samples/website_integration/form.php +++ b/samples/website_integration/sha256/form.php @@ -1,5 +1,5 @@ @@ -36,7 +36,7 @@ into the following form to test it. -
    Name
    Pass
    +
    '; } diff --git a/samples/website_integration/integration.php b/samples/website_integration/sha256/integration.php similarity index 97% rename from samples/website_integration/integration.php rename to samples/website_integration/sha256/integration.php index 3008fb98..e0de0bb1 100644 --- a/samples/website_integration/integration.php +++ b/samples/website_integration/sha256/integration.php @@ -1,6 +1,6 @@ baseCommands = ImmutableSet.of( AUTHME_BASE, LOGIN_BASE, @@ -401,8 +400,7 @@ public final class CommandInitializer { UNREGISTER_BASE, CHANGE_PASSWORD_BASE, EMAIL_BASE, - CAPTCHA_BASE, - CONVERTER_BASE); + CAPTCHA_BASE); setHelpOnAllBases(baseCommands); return baseCommands; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java index 5d4d8cda..d3698cfd 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java @@ -30,7 +30,7 @@ public class AccountsCommand implements ExecutableCommand { return; } - List accountList = commandService.getDataSource().getAllAuthsByName(auth); + List accountList = commandService.getDataSource().getAllAuthsByIp(auth.getIp()); if (accountList.isEmpty()) { commandService.send(sender, MessageKey.USER_NOT_REGISTERED); } else if (accountList.size() == 1) { diff --git a/src/main/java/fr/xephi/authme/command/executable/converter/ConverterCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java similarity index 98% rename from src/main/java/fr/xephi/authme/command/executable/converter/ConverterCommand.java rename to src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java index cb3f6c7f..e3414480 100644 --- a/src/main/java/fr/xephi/authme/command/executable/converter/ConverterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.command.executable.converter; +package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.CommandService; diff --git a/src/main/java/fr/xephi/authme/converter/ForceFlatToSqlite.java b/src/main/java/fr/xephi/authme/converter/ForceFlatToSqlite.java index 443e20ac..c87297d8 100644 --- a/src/main/java/fr/xephi/authme/converter/ForceFlatToSqlite.java +++ b/src/main/java/fr/xephi/authme/converter/ForceFlatToSqlite.java @@ -7,8 +7,6 @@ import fr.xephi.authme.datasource.DataSourceType; import fr.xephi.authme.datasource.SQLite; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.properties.DatabaseSettings; -import fr.xephi.authme.util.StringUtils; - import java.sql.SQLException; /** diff --git a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java index fd743289..4d0d4436 100644 --- a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java @@ -115,24 +115,6 @@ public class CacheDataSource implements DataSource { return result; } - @Override - public int getIps(String ip) { - return source.getIps(ip); - } - - @Override - public int purgeDatabase(long until) { - int cleared = source.purgeDatabase(until); - if (cleared > 0) { - for (Optional auth : cachedAuths.asMap().values()) { - if (auth.isPresent() && auth.get().getLastLogin() < until) { - cachedAuths.invalidate(auth.get().getNickname()); - } - } - } - return cleared; - } - @Override public List autoPurgeDatabase(long until) { List cleared = source.autoPurgeDatabase(until); @@ -172,11 +154,6 @@ public class CacheDataSource implements DataSource { return result; } - @Override - public synchronized List getAllAuthsByName(PlayerAuth auth) { - return source.getAllAuthsByName(auth); - } - @Override public synchronized List getAllAuthsByIp(final String ip) { return source.getAllAuthsByIp(ip); @@ -239,6 +216,15 @@ public class CacheDataSource implements DataSource { return result; } + @Override + public boolean updateIp(String user, String ip) { + boolean result = source.updateIp(user, ip); + if (result) { + cachedAuths.refresh(user); + } + return result; + } + @Override public List getAllAuths() { return source.getAllAuths(); diff --git a/src/main/java/fr/xephi/authme/datasource/DataSource.java b/src/main/java/fr/xephi/authme/datasource/DataSource.java index ab46051c..43693313 100644 --- a/src/main/java/fr/xephi/authme/datasource/DataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/DataSource.java @@ -6,146 +6,132 @@ import fr.xephi.authme.security.crypts.HashedPassword; import java.util.List; /** + * Interface for manipulating {@link PlayerAuth} objects from a data source. */ public interface DataSource { /** - * Method isAuthAvailable. + * Return whether there is a record for the given username. * - * @param user String + * @param user The username to look up * - * @return boolean + * @return True if there is a record, false otherwise */ boolean isAuthAvailable(String user); /** - * Method getPassword. + * Return the hashed password of the player. * - * @param user String + * @param user The user whose password should be retrieve * - * @return String + * @return The password hash of the player */ HashedPassword getPassword(String user); /** - * Method getAuth. + * Retrieve the entire PlayerAuth object associated with the username. * - * @param user String + * @param user The user to retrieve * - * @return PlayerAuth + * @return The PlayerAuth object for the given username */ PlayerAuth getAuth(String user); /** - * Method saveAuth. + * Save a new PlayerAuth object. * - * @param auth PlayerAuth + * @param auth The new PlayerAuth to persist * - * @return boolean + * @return True upon success, false upon failure */ boolean saveAuth(PlayerAuth auth); /** - * Method updateSession. + * Update the session of a record (IP, last login, real name). * - * @param auth PlayerAuth + * @param auth The PlayerAuth object to update in the database * - * @return boolean + * @return True upon success, false upon failure */ boolean updateSession(PlayerAuth auth); /** - * Method updatePassword. + * Update the password of the given PlayerAuth object. * - * @param auth PlayerAuth + * @param auth The PlayerAuth whose password should be updated * - * @return boolean + * @return True upon success, false upon failure */ boolean updatePassword(PlayerAuth auth); + /** + * Update the password of the given player. + * + * @param user The user whose password should be updated + * @param password The new password + * + * @return True upon success, false upon failure + */ boolean updatePassword(String user, HashedPassword password); /** - * Method purgeDatabase. + * Purge all records in the database whose last login was longer ago than + * the given time. * - * @param until long + * @param until The minimum last login * - * @return int - */ - int purgeDatabase(long until); - - /** - * Method autoPurgeDatabase. - * - * @param until long - * - * @return List of String + * @return The account names that have been removed */ List autoPurgeDatabase(long until); /** - * Method removeAuth. + * Remove a user record from the database. * - * @param user String + * @param user The user to remove * - * @return boolean + * @return True upon success, false upon failure */ boolean removeAuth(String user); /** - * Method updateQuitLoc. + * Update the quit location of a PlayerAuth. * - * @param auth PlayerAuth + * @param auth The entry whose quit location should be updated * - * @return boolean + * @return True upon success, false upon failure */ boolean updateQuitLoc(PlayerAuth auth); /** - * Method getIps. + * Return all usernames associated with the given IP address. * - * @param ip String + * @param ip The IP address to look up * - * @return int - */ - int getIps(String ip); - - /** - * Method getAllAuthsByName. - * - * @param auth PlayerAuth - * - * @return List of String - */ - List getAllAuthsByName(PlayerAuth auth); - - /** - * Method getAllAuthsByIp. - * - * @param ip String - * - * @return List of String * @throws Exception + * @return Usernames associated with the given IP address */ List getAllAuthsByIp(String ip); /** - * Method getAllAuthsByEmail. + * Return all usernames associated with the given email address. * - * @param email String + * @param email The email address to look up * - * @return List of String * @throws Exception + * @return Users using the given email address */ List getAllAuthsByEmail(String email); /** - * Method updateEmail. + * Update the email of the PlayerAuth in the data source. * - * @param auth PlayerAuth + * @param auth The PlayerAuth whose email should be updated * - * @return boolean + * @return True upon success, false upon failure */ boolean updateEmail(PlayerAuth auth); + /** + * Close the underlying connections to the data source. + */ void close(); void reload(); @@ -205,6 +191,9 @@ public interface DataSource { void updateName(String oldOne, String newOne); boolean updateRealName(String user, String realName); + + boolean updateIp(String user, String ip); + /** * Method getAllAuths. * diff --git a/src/main/java/fr/xephi/authme/datasource/FlatFile.java b/src/main/java/fr/xephi/authme/datasource/FlatFile.java index 6404e1c0..9bab76e1 100644 --- a/src/main/java/fr/xephi/authme/datasource/FlatFile.java +++ b/src/main/java/fr/xephi/authme/datasource/FlatFile.java @@ -278,82 +278,6 @@ public class FlatFile implements DataSource { return true; } - @Override - public int getIps(String ip) { - BufferedReader br = null; - int countIp = 0; - try { - br = new BufferedReader(new FileReader(source)); - String line; - while ((line = br.readLine()) != null) { - String[] args = line.split(":"); - if (args.length > 3 && args[2].equals(ip)) { - countIp++; - } - } - return countIp; - } catch (FileNotFoundException ex) { - ConsoleLogger.showError(ex.getMessage()); - return 0; - } catch (IOException ex) { - ConsoleLogger.showError(ex.getMessage()); - return 0; - } finally { - if (br != null) { - try { - br.close(); - } catch (IOException ignored) { - } - } - } - } - - @Override - public int purgeDatabase(long until) { - BufferedReader br = null; - BufferedWriter bw = null; - ArrayList lines = new ArrayList<>(); - int cleared = 0; - try { - br = new BufferedReader(new FileReader(source)); - String line; - while ((line = br.readLine()) != null) { - String[] args = line.split(":"); - if (args.length >= 4) { - if (Long.parseLong(args[3]) >= until) { - lines.add(line); - continue; - } - } - cleared++; - } - bw = new BufferedWriter(new FileWriter(source)); - for (String l : lines) { - bw.write(l + "\n"); - } - } catch (FileNotFoundException ex) { - ConsoleLogger.showError(ex.getMessage()); - return cleared; - } catch (IOException ex) { - ConsoleLogger.showError(ex.getMessage()); - return cleared; - } finally { - if (br != null) { - try { - br.close(); - } catch (IOException ignored) { - } - } - if (bw != null) { - try { - bw.close(); - } catch (IOException ignored) { - } - } - } - return cleared; - } - @Override public List autoPurgeDatabase(long until) { BufferedReader br = null; @@ -532,36 +456,6 @@ public class FlatFile implements DataSource { return true; } - @Override - public List getAllAuthsByName(PlayerAuth auth) { - BufferedReader br = null; - List countIp = new ArrayList<>(); - try { - br = new BufferedReader(new FileReader(source)); - String line; - while ((line = br.readLine()) != null) { - String[] args = line.split(":"); - if (args.length > 3 && args[2].equals(auth.getIp())) { - countIp.add(args[0]); - } - } - return countIp; - } catch (FileNotFoundException ex) { - ConsoleLogger.showError(ex.getMessage()); - return new ArrayList<>(); - } catch (IOException ex) { - ConsoleLogger.showError(ex.getMessage()); - return new ArrayList<>(); - } finally { - if (br != null) { - try { - br.close(); - } catch (IOException ignored) { - } - } - } - } - @Override public List getAllAuthsByIp(String ip) { BufferedReader br = null; @@ -721,6 +615,11 @@ public class FlatFile implements DataSource { return false; } + @Override + public boolean updateIp(String user, String ip) { + throw new UnsupportedOperationException("Flat file no longer supported"); + } + @Override public List getAllAuths() { BufferedReader br = null; diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 33fd0edd..a429d181 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -562,16 +562,14 @@ public class MySQL implements DataSource { @Override public synchronized boolean updateSession(PlayerAuth auth) { - try (Connection con = getConnection()) { - String sql = "UPDATE " + tableName + " SET " - + col.IP + "=?, " + col.LAST_LOGIN + "=?, " + col.REAL_NAME + "=? WHERE " + col.NAME + "=?;"; - PreparedStatement pst = con.prepareStatement(sql); + String sql = "UPDATE " + tableName + " SET " + + col.IP + "=?, " + col.LAST_LOGIN + "=?, " + col.REAL_NAME + "=? WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { pst.setString(1, auth.getIp()); pst.setTimestamp(2, new Timestamp(auth.getLastLogin())); pst.setString(3, auth.getRealName()); pst.setString(4, auth.getNickname()); pst.executeUpdate(); - pst.close(); return true; } catch (SQLException ex) { logSqlException(ex); @@ -579,20 +577,6 @@ public class MySQL implements DataSource { return false; } - @Override - public synchronized int purgeDatabase(long until) { - int result = 0; - try (Connection con = getConnection()) { - String sql = "DELETE FROM " + tableName + " WHERE " + col.LAST_LOGIN + " autoPurgeDatabase(long until) { List list = new ArrayList<>(); @@ -669,25 +653,6 @@ public class MySQL implements DataSource { return false; } - @Override - public synchronized int getIps(String ip) { - int countIp = 0; - try (Connection con = getConnection()) { - String sql = "SELECT COUNT(*) FROM " + tableName + " WHERE " + col.IP + "=?;"; - PreparedStatement pst = con.prepareStatement(sql); - pst.setString(1, ip); - ResultSet rs = pst.executeQuery(); - while (rs.next()) { - countIp = rs.getInt(1); - } - rs.close(); - pst.close(); - } catch (SQLException ex) { - logSqlException(ex); - } - return countIp; - } - @Override public synchronized boolean updateEmail(PlayerAuth auth) { try (Connection con = getConnection()) { @@ -722,25 +687,6 @@ public class MySQL implements DataSource { } } - @Override - public synchronized List getAllAuthsByName(PlayerAuth auth) { - List result = new ArrayList<>(); - try (Connection con = getConnection()) { - String sql = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.IP + "=?;"; - PreparedStatement pst = con.prepareStatement(sql); - pst.setString(1, auth.getIp()); - ResultSet rs = pst.executeQuery(); - while (rs.next()) { - result.add(rs.getString(col.NAME)); - } - rs.close(); - pst.close(); - } catch (SQLException ex) { - logSqlException(ex); - } - return result; - } - @Override public synchronized List getAllAuthsByIp(String ip) { List result = new ArrayList<>(); @@ -900,6 +846,21 @@ public class MySQL implements DataSource { return false; } + @Override + public boolean updateIp(String user, String ip) { + try (Connection con = getConnection()) { + String sql = "UPDATE " + tableName + " SET " + col.IP + "=? WHERE " + col.NAME + "=?;"; + PreparedStatement pst = con.prepareStatement(sql); + pst.setString(1, ip); + pst.setString(2, user); + pst.executeUpdate(); + return true; + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + @Override public List getAllAuths() { List auths = new ArrayList<>(); diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java index 033bb838..081e386c 100644 --- a/src/main/java/fr/xephi/authme/datasource/SQLite.java +++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java @@ -266,18 +266,6 @@ public class SQLite implements DataSource { return false; } - @Override - public int purgeDatabase(long until) { - String sql = "DELETE FROM " + tableName + " WHERE " + col.LAST_LOGIN + " autoPurgeDatabase(long until) { PreparedStatement pst = null; @@ -336,29 +324,6 @@ public class SQLite implements DataSource { return false; } - @Override - public int getIps(String ip) { - PreparedStatement pst = null; - ResultSet rs = null; - int countIp = 0; - try { - // TODO ljacqu 20151230: Simply fetch COUNT(1) and return that - pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE " + col.IP + "=?;"); - pst.setString(1, ip); - rs = pst.executeQuery(); - while (rs.next()) { - countIp++; - } - return countIp; - } catch (SQLException ex) { - logSqlException(ex); - } finally { - close(rs); - close(pst); - } - return 0; - } - @Override public boolean updateEmail(PlayerAuth auth) { String sql = "UPDATE " + tableName + " SET " + col.EMAIL + "=? WHERE " + col.NAME + "=?;"; @@ -406,37 +371,13 @@ public class SQLite implements DataSource { } } - @Override - public List getAllAuthsByName(PlayerAuth auth) { - PreparedStatement pst = null; - ResultSet rs = null; - List names = new ArrayList<>(); - try { - // TODO ljacqu 20160214: Use SELECT name if only the name is required - pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE " + col.IP + "=?;"); - pst.setString(1, auth.getIp()); - rs = pst.executeQuery(); - while (rs.next()) { - names.add(rs.getString(col.NAME)); - } - return names; - } catch (SQLException ex) { - logSqlException(ex); - - } finally { - close(rs); - close(pst); - } - return new ArrayList<>(); - } - @Override public List getAllAuthsByIp(String ip) { PreparedStatement pst = null; ResultSet rs = null; List countIp = new ArrayList<>(); try { - pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE " + col.IP + "=?;"); + pst = con.prepareStatement("SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.IP + "=?;"); pst.setString(1, ip); rs = pst.executeQuery(); while (rs.next()) { @@ -607,6 +548,20 @@ public class SQLite implements DataSource { return false; } + @Override + public boolean updateIp(String user, String ip) { + String sql = "UPDATE " + tableName + " SET " + col.IP + "=? WHERE " + col.NAME + "=?;"; + try(PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, ip); + pst.setString(2, user); + pst.executeUpdate(); + return true; + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + @Override public List getAllAuths() { List auths = new ArrayList<>(); 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 c7673f70..d5908820 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java @@ -1,6 +1,7 @@ package fr.xephi.authme.process.email; import fr.xephi.authme.AuthMe; +import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; @@ -48,8 +49,13 @@ public class AsyncAddEmail { messages.send(player, MessageKey.EMAIL_ALREADY_USED_ERROR); } else { auth.setEmail(email); - playerCache.updatePlayer(auth); - messages.send(player, MessageKey.EMAIL_ADDED_SUCCESS); + if (dataSource.updateEmail(auth)) { + playerCache.updatePlayer(auth); + messages.send(player, MessageKey.EMAIL_ADDED_SUCCESS); + } else { + ConsoleLogger.showError("Could not save email for player '" + player + "'"); + messages.send(player, MessageKey.ERROR); + } } } else { sendUnloggedMessage(dataSource); diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index 19b75f34..5a4cf425 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -7,12 +7,12 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.AdminPermission; import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.security.RandomString; -import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.output.Messages; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.RegistrationSettings; @@ -23,7 +23,6 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; -import java.util.Date; import java.util.List; /** @@ -143,7 +142,7 @@ public class AsynchronousLogin { if (pAuth.getIp().equals("127.0.0.1") && !pAuth.getIp().equals(ip)) { pAuth.setIp(ip); - database.saveAuth(pAuth); + database.updateIp(pAuth.getNickname(), ip); } String email = pAuth.getEmail(); @@ -226,7 +225,7 @@ public class AsynchronousLogin { return; } - List auths = this.database.getAllAuthsByName(auth); + List auths = this.database.getAllAuthsByIp(auth.getIp()); if (auths.size() < 2) { return; } diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index 29616fe5..ac921510 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -1,19 +1,18 @@ package fr.xephi.authme.process.register; import fr.xephi.authme.AuthMe; -import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; -import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.security.crypts.TwoFactor; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.StringUtils; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -21,11 +20,11 @@ import org.bukkit.entity.Player; */ public class AsyncRegister { - protected final Player player; - protected final String name; - protected final String password; + private final Player player; + private final String name; + private final String password; private final String ip; - private String email = ""; + private final String email; private final AuthMe plugin; private final DataSource database; private final Messages m; @@ -88,7 +87,7 @@ public class AsyncRegister { public void process() { if (preRegisterCheck()) { - if (email != null && !email.isEmpty()) { + if (!StringUtils.isEmpty(email)) { emailRegister(); } else { passwordRegister(); diff --git a/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java b/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java index 065d1961..f878d85a 100644 --- a/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java +++ b/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java @@ -1,6 +1,6 @@ package fr.xephi.authme.util; -import com.maxmind.geoip2.DatabaseReader; +import com.maxmind.geoip.LookupService; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.settings.Settings; @@ -9,18 +9,17 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.InetAddress; import java.net.URL; import java.net.URLConnection; import java.util.concurrent.TimeUnit; import java.util.zip.GZIPInputStream; public class GeoLiteAPI { - - private static final String LICENSE = "[LICENSE] This product includes GeoLite2 data created by MaxMind," + - " available from http://www.maxmind.com"; - private static final String GEOIP_URL = "http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz"; - private static DatabaseReader databaseReader; + private static final String LICENSE = + "[LICENSE] This product uses data from the GeoLite API created by MaxMind, available at http://www.maxmind.com"; + private static final String GEOIP_URL = + "http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz"; + private static LookupService lookupService; private static Thread downloadTask; /** @@ -32,17 +31,17 @@ public class GeoLiteAPI { if (downloadTask != null && downloadTask.isAlive()) { return false; } - if (databaseReader != null) { + if (lookupService != null) { return true; } - final File data = new File(Settings.PLUGIN_FOLDER, "GeoLite2-Country.mmdb"); + final File data = new File(Settings.PLUGIN_FOLDER, "GeoIP.dat"); boolean dataIsOld = (System.currentTimeMillis() - data.lastModified()) > TimeUnit.DAYS.toMillis(30); if (dataIsOld && !data.delete()) { ConsoleLogger.showError("Failed to delete GeoLiteAPI database"); } if (data.exists()) { try { - databaseReader = new DatabaseReader.Builder(data).build(); + lookupService = new LookupService(data); ConsoleLogger.info(LICENSE); return true; } catch (IOException e) { @@ -90,11 +89,7 @@ public class GeoLiteAPI { */ public static String getCountryCode(String ip) { if (!"127.0.0.1".equals(ip) && isDataAvailable()) { - try { - return databaseReader.country(InetAddress.getByName(ip)).getCountry().getIsoCode(); - } catch (Exception e) { - ConsoleLogger.writeStackTrace(e); - } + return lookupService.getCountry(ip).getCode(); } return "--"; } @@ -108,11 +103,7 @@ public class GeoLiteAPI { */ public static String getCountryName(String ip) { if (!"127.0.0.1".equals(ip) && isDataAvailable()) { - try { - return databaseReader.country(InetAddress.getByName(ip)).getCountry().getName(); - } catch (Exception e) { - ConsoleLogger.writeStackTrace(e); - } + return lookupService.getCountry(ip).getName(); } return "N/A"; } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index be7b792f..bf63b364 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -16,7 +16,7 @@ softdepend: commands: authme: description: AuthMe op commands - usage: '/authme reload|register playername password|changepassword playername password|unregister playername|version' + usage: '/authme reload|register playername password|changepassword playername password|unregister playername|version|converter' register: description: Register an account usage: /register @@ -40,9 +40,6 @@ commands: captcha: description: Captcha command usage: /captcha - converter: - description: Converter from different other auth plugins - usage: /converter permissions: authme.admin.*: description: Give access to all admin commands. diff --git a/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java b/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java index f8b0d256..62c2b890 100644 --- a/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java @@ -50,7 +50,7 @@ public class CommandInitializerTest { // It obviously doesn't make sense to test much of the concrete data // that is being initialized; we just want to guarantee with this test // that data is indeed being initialized and we take a few "probes" - assertThat(commands.size(), equalTo(9)); + assertThat(commands.size(), equalTo(8)); assertThat(commandsIncludeLabel(commands, "authme"), equalTo(true)); assertThat(commandsIncludeLabel(commands, "register"), equalTo(true)); assertThat(commandsIncludeLabel(commands, "help"), equalTo(false)); diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/AccountsCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/AccountsCommandTest.java index 4743f699..92bcf4fc 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/AccountsCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/AccountsCommandTest.java @@ -16,7 +16,6 @@ import java.util.List; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.BDDMockito.given; -import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -48,8 +47,8 @@ public class AccountsCommandTest { // given given(sender.getName()).willReturn("Tester"); List arguments = Collections.EMPTY_LIST; - given(dataSource.getAuth("tester")).willReturn(mock(PlayerAuth.class)); - given(dataSource.getAllAuthsByName(any(PlayerAuth.class))).willReturn(Arrays.asList("Toaster", "Pester")); + given(dataSource.getAuth("tester")).willReturn(authWithIp("123.45.67.89")); + given(dataSource.getAllAuthsByIp("123.45.67.89")).willReturn(Arrays.asList("Toaster", "Pester")); // when command.executeCommand(sender, arguments, service); @@ -81,7 +80,7 @@ public class AccountsCommandTest { // given List arguments = Collections.singletonList("SomeUser"); given(dataSource.getAuth("someuser")).willReturn(mock(PlayerAuth.class)); - given(dataSource.getAllAuthsByName(any(PlayerAuth.class))).willReturn(Collections.EMPTY_LIST); + given(dataSource.getAllAuthsByIp(anyString())).willReturn(Collections.EMPTY_LIST); // when command.executeCommand(sender, arguments, service); @@ -96,8 +95,8 @@ public class AccountsCommandTest { public void shouldReturnSingleAccountMessage() { // given List arguments = Collections.singletonList("SomeUser"); - given(dataSource.getAuth("someuser")).willReturn(mock(PlayerAuth.class)); - given(dataSource.getAllAuthsByName(any(PlayerAuth.class))).willReturn(Collections.singletonList("SomeUser")); + given(dataSource.getAuth("someuser")).willReturn(authWithIp("56.78.90.123")); + given(dataSource.getAllAuthsByIp("56.78.90.123")).willReturn(Collections.singletonList("SomeUser")); // when command.executeCommand(sender, arguments, service); @@ -169,4 +168,11 @@ public class AccountsCommandTest { verify(sender, times(expectedCount)).sendMessage(captor.capture()); return captor.getAllValues().toArray(new String[expectedCount]); } + + private static PlayerAuth authWithIp(String ip) { + return PlayerAuth.builder() + .name("Test") + .ip(ip) + .build(); + } } 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 4aea5e57..7cc7c55d 100644 --- a/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java +++ b/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java @@ -1,6 +1,7 @@ package fr.xephi.authme.process.email; import fr.xephi.authme.AuthMe; +import fr.xephi.authme.ConsoleLoggerTestInitializer; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; @@ -35,6 +36,7 @@ public class AsyncAddEmailTest { @BeforeClass public static void setUp() { WrapperMock.createInstance(); + ConsoleLoggerTestInitializer.setupLogger(); } // Clean up the fields to ensure that no test uses elements of another test @@ -56,16 +58,38 @@ public class AsyncAddEmailTest { given(auth.getEmail()).willReturn(null); given(playerCache.getAuth("tester")).willReturn(auth); given(dataSource.isEmailStored("my.mail@example.org")).willReturn(false); + given(dataSource.updateEmail(any(PlayerAuth.class))).willReturn(true); // when process.process(); // then + verify(dataSource).updateEmail(auth); verify(messages).send(player, MessageKey.EMAIL_ADDED_SUCCESS); verify(auth).setEmail("my.mail@example.org"); verify(playerCache).updatePlayer(auth); } + @Test + public void shouldReturnErrorWhenMailCannotBeSaved() { + // given + AsyncAddEmail process = createProcess("my.mail@example.org"); + given(player.getName()).willReturn("testEr"); + given(playerCache.isAuthenticated("tester")).willReturn(true); + 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); + given(dataSource.updateEmail(any(PlayerAuth.class))).willReturn(false); + + // when + process.process(); + + // then + verify(dataSource).updateEmail(auth); + verify(messages).send(player, MessageKey.ERROR); + } + @Test public void shouldNotAddMailIfPlayerAlreadyHasEmail() { // given diff --git a/src/test/java/fr/xephi/authme/settings/NewSettingTest.java b/src/test/java/fr/xephi/authme/settings/NewSettingTest.java index 64a79626..d381604c 100644 --- a/src/test/java/fr/xephi/authme/settings/NewSettingTest.java +++ b/src/test/java/fr/xephi/authme/settings/NewSettingTest.java @@ -5,8 +5,7 @@ import fr.xephi.authme.settings.properties.TestConfiguration; import fr.xephi.authme.settings.properties.TestEnum; import org.bukkit.configuration.file.YamlConfiguration; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; +import org.mockito.internal.stubbing.answers.ReturnsArgumentAt; import java.io.File; @@ -32,10 +31,10 @@ public class NewSettingTest { public void shouldLoadAllConfigs() { // given YamlConfiguration configuration = mock(YamlConfiguration.class); - given(configuration.getString(anyString(), anyString())).willAnswer(withDefaultArgument()); - given(configuration.getBoolean(anyString(), anyBoolean())).willAnswer(withDefaultArgument()); - given(configuration.getDouble(anyString(), anyDouble())).willAnswer(withDefaultArgument()); - given(configuration.getInt(anyString(), anyInt())).willAnswer(withDefaultArgument()); + given(configuration.getString(anyString(), anyString())).willAnswer(new ReturnsArgumentAt(1)); + given(configuration.getBoolean(anyString(), anyBoolean())).willAnswer(new ReturnsArgumentAt(1)); + given(configuration.getDouble(anyString(), anyDouble())).willAnswer(new ReturnsArgumentAt(1)); + given(configuration.getInt(anyString(), anyInt())).willAnswer(new ReturnsArgumentAt(1)); setReturnValue(configuration, TestConfiguration.VERSION_NUMBER, 20); setReturnValue(configuration, TestConfiguration.SKIP_BORING_FEATURES, true); @@ -89,14 +88,4 @@ public class NewSettingTest { setting.getProperty(property).equals(property.getDefaultValue()), equalTo(true)); } - private static Answer withDefaultArgument() { - return new Answer() { - @Override - public T answer(InvocationOnMock invocation) throws Throwable { - // Return the second parameter -> the default - return (T) invocation.getArguments()[1]; - } - }; - } - }