mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2024-11-22 10:15:18 +01:00
* Introduce hasSession field in datasource That makes isLogged more consistent as it will be '1' only when the player is online. * Fixes * Fix unit testing * Update config doc * Create SessionService * Create test for SessionService, avoid DB operations if sessions are disabled * Cleanup: remove outdated warning for session timeout = 0 - Remove outdated warning - Encapsulate session enabled check in SessionService * Fix failing SessionServiceTest, add data source integration tests for session methods
This commit is contained in:
parent
1487fc0d9e
commit
22e95493de
@ -1,5 +1,5 @@
|
||||
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
|
||||
<!-- File auto-generated on Mon Oct 09 10:19:07 CEST 2017. See docs/config/config.tpl.md -->
|
||||
<!-- File auto-generated on Tue Oct 10 13:51:56 CEST 2017. See docs/config/config.tpl.md -->
|
||||
|
||||
## AuthMe Configuration
|
||||
The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder,
|
||||
@ -41,6 +41,8 @@ DataSource:
|
||||
mySQLColumnEmail: 'email'
|
||||
# Column for storing if a player is logged in or not
|
||||
mySQLColumnLogged: 'isLogged'
|
||||
# Column for storing if a player has a valid session or not
|
||||
mySQLColumnHasSession: 'hasSession'
|
||||
# Column for storing players ips
|
||||
mySQLColumnIp: 'ip'
|
||||
# Column for storing players lastlogins
|
||||
@ -545,4 +547,4 @@ To change settings on a running server, save your changes to config.yml and use
|
||||
|
||||
---
|
||||
|
||||
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Mon Oct 09 10:19:07 CEST 2017
|
||||
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Tue Oct 10 13:51:56 CEST 2017
|
||||
|
@ -30,7 +30,6 @@ import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.service.MigrationService;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.EmailSettings;
|
||||
import fr.xephi.authme.settings.properties.PluginSettings;
|
||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||
import fr.xephi.authme.settings.properties.SecuritySettings;
|
||||
import fr.xephi.authme.task.CleanupTask;
|
||||
@ -264,12 +263,6 @@ public class AuthMe extends JavaPlugin {
|
||||
ConsoleLogger.warning("WARNING!!! By disabling ForceSingleSession, your server protection is inadequate!");
|
||||
}
|
||||
|
||||
// Session timeout disabled
|
||||
if (settings.getProperty(PluginSettings.SESSIONS_TIMEOUT) == 0
|
||||
&& settings.getProperty(PluginSettings.SESSIONS_ENABLED)) {
|
||||
ConsoleLogger.warning("WARNING!!! You set session timeout to 0, this may cause security issues!");
|
||||
}
|
||||
|
||||
// Use TLS property only affects port 25
|
||||
if (!settings.getProperty(EmailSettings.PORT25_USE_TLS)
|
||||
&& settings.getProperty(EmailSettings.SMTP_PORT) != 25) {
|
||||
|
@ -51,38 +51,29 @@ public class RegisterAdminCommand implements ExecutableCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
bukkitService.runTaskOptionallyAsync(new Runnable() {
|
||||
bukkitService.runTaskOptionallyAsync(() -> {
|
||||
if (dataSource.isAuthAvailable(playerNameLowerCase)) {
|
||||
commonService.send(sender, MessageKey.NAME_ALREADY_REGISTERED);
|
||||
return;
|
||||
}
|
||||
HashedPassword hashedPassword = passwordSecurity.computeHash(playerPass, playerNameLowerCase);
|
||||
PlayerAuth auth = PlayerAuth.builder()
|
||||
.name(playerNameLowerCase)
|
||||
.realName(playerName)
|
||||
.password(hashedPassword)
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (dataSource.isAuthAvailable(playerNameLowerCase)) {
|
||||
commonService.send(sender, MessageKey.NAME_ALREADY_REGISTERED);
|
||||
return;
|
||||
}
|
||||
HashedPassword hashedPassword = passwordSecurity.computeHash(playerPass, playerNameLowerCase);
|
||||
PlayerAuth auth = PlayerAuth.builder()
|
||||
.name(playerNameLowerCase)
|
||||
.realName(playerName)
|
||||
.password(hashedPassword)
|
||||
.build();
|
||||
if (!dataSource.saveAuth(auth)) {
|
||||
commonService.send(sender, MessageKey.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dataSource.saveAuth(auth)) {
|
||||
commonService.send(sender, MessageKey.ERROR);
|
||||
return;
|
||||
}
|
||||
dataSource.setUnlogged(playerNameLowerCase);
|
||||
|
||||
commonService.send(sender, MessageKey.REGISTER_SUCCESS);
|
||||
ConsoleLogger.info(sender.getName() + " registered " + playerName);
|
||||
final Player player = bukkitService.getPlayerExact(playerName);
|
||||
if (player != null) {
|
||||
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
player.kickPlayer(commonService.retrieveSingleMessage(MessageKey.KICK_FOR_ADMIN_REGISTER));
|
||||
}
|
||||
});
|
||||
}
|
||||
commonService.send(sender, MessageKey.REGISTER_SUCCESS);
|
||||
ConsoleLogger.info(sender.getName() + " registered " + playerName);
|
||||
final Player player = bukkitService.getPlayerExact(playerName);
|
||||
if (player != null) {
|
||||
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() ->
|
||||
player.kickPlayer(commonService.retrieveSingleMessage(MessageKey.KICK_FOR_ADMIN_REGISTER)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -208,6 +208,21 @@ public class CacheDataSource implements DataSource {
|
||||
source.setUnlogged(user.toLowerCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSession(final String user) {
|
||||
return source.hasSession(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantSession(final String user) {
|
||||
source.grantSession(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revokeSession(final String user) {
|
||||
source.revokeSession(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeLogged() {
|
||||
source.purgeLogged();
|
||||
|
@ -26,6 +26,7 @@ public final class Columns {
|
||||
public final String EMAIL;
|
||||
public final String ID;
|
||||
public final String IS_LOGGED;
|
||||
public final String HAS_SESSION;
|
||||
|
||||
public Columns(Settings settings) {
|
||||
NAME = settings.getProperty(DatabaseSettings.MYSQL_COL_NAME);
|
||||
@ -44,6 +45,7 @@ public final class Columns {
|
||||
EMAIL = settings.getProperty(DatabaseSettings.MYSQL_COL_EMAIL);
|
||||
ID = settings.getProperty(DatabaseSettings.MYSQL_COL_ID);
|
||||
IS_LOGGED = settings.getProperty(DatabaseSettings.MYSQL_COL_ISLOGGED);
|
||||
HAS_SESSION = settings.getProperty(DatabaseSettings.MYSQL_COL_HASSESSION);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -161,6 +161,29 @@ public interface DataSource extends Reloadable {
|
||||
*/
|
||||
void setUnlogged(String user);
|
||||
|
||||
/**
|
||||
* Query the datasource whether the player has an active session or not.
|
||||
* Warning: this value won't expire, you have also to check the user's last login timestamp.
|
||||
*
|
||||
* @param user The name of the player to verify
|
||||
* @return True if the user has a valid session, false otherwise
|
||||
*/
|
||||
boolean hasSession(String user);
|
||||
|
||||
/**
|
||||
* Mark the user's hasSession value to true.
|
||||
*
|
||||
* @param user The name of the player to change
|
||||
*/
|
||||
void grantSession(String user);
|
||||
|
||||
/**
|
||||
* Mark the user's hasSession value to false.
|
||||
*
|
||||
* @param user The name of the player to change
|
||||
*/
|
||||
void revokeSession(String user);
|
||||
|
||||
/**
|
||||
* Set all players who are marked as logged in as NOT logged in.
|
||||
*/
|
||||
|
@ -329,6 +329,19 @@ public class FlatFile implements DataSource {
|
||||
public void setUnlogged(String user) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSession(String user) {
|
||||
throw new UnsupportedOperationException("Flat file no longer supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantSession(String user) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revokeSession(String user) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeLogged() {
|
||||
}
|
||||
|
@ -237,6 +237,11 @@ public class MySQL implements DataSource {
|
||||
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN "
|
||||
+ col.IS_LOGGED + " SMALLINT NOT NULL DEFAULT '0' AFTER " + col.EMAIL);
|
||||
}
|
||||
|
||||
if (isColumnMissing(md, col.HAS_SESSION)) {
|
||||
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN "
|
||||
+ col.HAS_SESSION + " SMALLINT NOT NULL DEFAULT '0' AFTER " + col.IS_LOGGED);
|
||||
}
|
||||
}
|
||||
ConsoleLogger.info("MySQL setup finished");
|
||||
}
|
||||
@ -565,6 +570,44 @@ public class MySQL implements DataSource {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSession(String user) {
|
||||
String sql = "SELECT " + col.HAS_SESSION + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
return rs.next() && (rs.getInt(col.HAS_SESSION) == 1);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantSession(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.HAS_SESSION + "=? WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 1);
|
||||
pst.setString(2, user.toLowerCase());
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revokeSession(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.HAS_SESSION + "=? WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 0);
|
||||
pst.setString(2, user.toLowerCase());
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeLogged() {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE " + col.IS_LOGGED + "=?;";
|
||||
|
@ -134,7 +134,13 @@ public class SQLite implements DataSource {
|
||||
}
|
||||
|
||||
if (isColumnMissing(md, col.IS_LOGGED)) {
|
||||
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.IS_LOGGED + " INT DEFAULT '0';");
|
||||
st.executeUpdate("ALTER TABLE " + tableName
|
||||
+ " ADD COLUMN " + col.IS_LOGGED + " INT NOT NULL DEFAULT '0';");
|
||||
}
|
||||
|
||||
if (isColumnMissing(md, col.HAS_SESSION)) {
|
||||
st.executeUpdate("ALTER TABLE " + tableName
|
||||
+ " ADD COLUMN " + col.HAS_SESSION + " INT NOT NULL DEFAULT '0';");
|
||||
}
|
||||
}
|
||||
ConsoleLogger.info("SQLite Setup finished");
|
||||
@ -426,7 +432,7 @@ public class SQLite implements DataSource {
|
||||
|
||||
@Override
|
||||
public boolean isLogged(String user) {
|
||||
String sql = "SELECT * FROM " + tableName + " WHERE LOWER(" + col.NAME + ")=?;";
|
||||
String sql = "SELECT " + col.IS_LOGGED + " FROM " + tableName + " WHERE LOWER(" + col.NAME + ")=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
@ -455,14 +461,52 @@ public class SQLite implements DataSource {
|
||||
@Override
|
||||
public void setUnlogged(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE LOWER(" + col.NAME + ")=?;";
|
||||
if (user != null) {
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 0);
|
||||
pst.setString(2, user);
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 0);
|
||||
pst.setString(2, user);
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSession(String user) {
|
||||
String sql = "SELECT " + col.HAS_SESSION + " FROM " + tableName + " WHERE LOWER(" + col.NAME + ")=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return rs.getInt(col.HAS_SESSION) == 1;
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantSession(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.HAS_SESSION + "=? WHERE LOWER(" + col.NAME + ")=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 1);
|
||||
pst.setString(2, user);
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revokeSession(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.HAS_SESSION + "=? WHERE LOWER(" + col.NAME + ")=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 0);
|
||||
pst.setString(2, user);
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -574,17 +618,6 @@ public class SQLite implements DataSource {
|
||||
return authBuilder.build();
|
||||
}
|
||||
|
||||
|
||||
private static void close(Statement st) {
|
||||
if (st != null) {
|
||||
try {
|
||||
st.close();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void close(Connection con) {
|
||||
if (con != null) {
|
||||
try {
|
||||
@ -594,14 +627,4 @@ public class SQLite implements DataSource {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void close(ResultSet rs) {
|
||||
if (rs != null) {
|
||||
try {
|
||||
rs.close();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
package fr.xephi.authme.process.join;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.data.auth.PlayerCache;
|
||||
import fr.xephi.authme.data.limbo.LimboService;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.events.ProtectInventoryEvent;
|
||||
import fr.xephi.authme.events.RestoreSessionEvent;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.permission.PlayerStatePermission;
|
||||
import fr.xephi.authme.process.AsynchronousProcess;
|
||||
@ -14,11 +11,11 @@ import fr.xephi.authme.process.login.AsynchronousLogin;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.service.CommonService;
|
||||
import fr.xephi.authme.service.PluginHookService;
|
||||
import fr.xephi.authme.service.SessionService;
|
||||
import fr.xephi.authme.service.ValidationService;
|
||||
import fr.xephi.authme.settings.WelcomeMessageConfiguration;
|
||||
import fr.xephi.authme.settings.commandconfig.CommandManager;
|
||||
import fr.xephi.authme.settings.properties.HooksSettings;
|
||||
import fr.xephi.authme.settings.properties.PluginSettings;
|
||||
import fr.xephi.authme.settings.properties.RegistrationSettings;
|
||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||
import fr.xephi.authme.util.PlayerUtils;
|
||||
@ -48,9 +45,6 @@ public class AsynchronousJoin implements AsynchronousProcess {
|
||||
@Inject
|
||||
private CommonService service;
|
||||
|
||||
@Inject
|
||||
private PlayerCache playerCache;
|
||||
|
||||
@Inject
|
||||
private LimboService limboService;
|
||||
|
||||
@ -72,6 +66,9 @@ public class AsynchronousJoin implements AsynchronousProcess {
|
||||
@Inject
|
||||
private WelcomeMessageConfiguration welcomeMessageConfiguration;
|
||||
|
||||
@Inject
|
||||
private SessionService sessionService;
|
||||
|
||||
AsynchronousJoin() {
|
||||
}
|
||||
|
||||
@ -121,7 +118,7 @@ public class AsynchronousJoin implements AsynchronousProcess {
|
||||
}
|
||||
|
||||
// Session logic
|
||||
if (canResumeSession(player)) {
|
||||
if (sessionService.canResumeSession(player)) {
|
||||
service.send(player, MessageKey.SESSION_RECONNECTION);
|
||||
// Run commands
|
||||
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(
|
||||
@ -177,30 +174,6 @@ public class AsynchronousJoin implements AsynchronousProcess {
|
||||
});
|
||||
}
|
||||
|
||||
private boolean canResumeSession(Player player) {
|
||||
final String name = player.getName();
|
||||
if (database.isLogged(name)) {
|
||||
database.setUnlogged(name);
|
||||
playerCache.removePlayer(name);
|
||||
if(service.getProperty(PluginSettings.SESSIONS_ENABLED)) {
|
||||
PlayerAuth auth = database.getAuth(name);
|
||||
if (auth != null) {
|
||||
long timeSinceLastLogin = System.currentTimeMillis() - auth.getLastLogin();
|
||||
if(timeSinceLastLogin < 0
|
||||
|| timeSinceLastLogin > (service.getProperty(PluginSettings.SESSIONS_TIMEOUT) * 60 * 1000)
|
||||
|| !auth.getIp().equals(PlayerUtils.getPlayerIp(player))) {
|
||||
service.send(player, MessageKey.SESSION_EXPIRED);
|
||||
} else {
|
||||
RestoreSessionEvent event = bukkitService.createAndCallEvent(
|
||||
isAsync -> new RestoreSessionEvent(player, isAsync));
|
||||
return !event.isCancelled();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the maximum number of accounts has been exceeded for the given IP address (according to
|
||||
* settings and permissions). If this is the case, the player is kicked.
|
||||
|
@ -20,6 +20,7 @@ import fr.xephi.authme.process.SyncProcessManager;
|
||||
import fr.xephi.authme.security.PasswordSecurity;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.service.CommonService;
|
||||
import fr.xephi.authme.service.SessionService;
|
||||
import fr.xephi.authme.settings.properties.DatabaseSettings;
|
||||
import fr.xephi.authme.settings.properties.EmailSettings;
|
||||
import fr.xephi.authme.settings.properties.HooksSettings;
|
||||
@ -69,6 +70,9 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
||||
@Inject
|
||||
private EmailService emailService;
|
||||
|
||||
@Inject
|
||||
private SessionService sessionService;
|
||||
|
||||
AsynchronousLogin() {
|
||||
}
|
||||
|
||||
@ -238,9 +242,10 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
||||
|
||||
ConsoleLogger.fine(player.getName() + " logged in!");
|
||||
|
||||
// makes player isLoggedin via API
|
||||
// makes player loggedin
|
||||
playerCache.updatePlayer(auth);
|
||||
dataSource.setLogged(name);
|
||||
sessionService.grantSession(name);
|
||||
|
||||
// As the scheduling executes the Task most likely after the current
|
||||
// task, we schedule it in the end
|
||||
|
@ -53,6 +53,7 @@ public class AsynchronousLogout implements AsynchronousProcess {
|
||||
|
||||
playerCache.removePlayer(name);
|
||||
database.setUnlogged(name);
|
||||
database.revokeSession(name);
|
||||
syncProcessManager.processSyncPlayerLogout(player);
|
||||
}
|
||||
}
|
||||
|
@ -82,8 +82,11 @@ public class AsynchronousQuit implements AsynchronousProcess {
|
||||
playerCache.removePlayer(name);
|
||||
|
||||
//always update the database when the player quit the game (if sessions are disabled)
|
||||
if(!wasLoggedIn || !service.getProperty(PluginSettings.SESSIONS_ENABLED)) {
|
||||
if (wasLoggedIn) {
|
||||
database.setUnlogged(name);
|
||||
if (!service.getProperty(PluginSettings.SESSIONS_ENABLED)) {
|
||||
database.revokeSession(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.isEnabled()) {
|
||||
|
86
src/main/java/fr/xephi/authme/service/SessionService.java
Normal file
86
src/main/java/fr/xephi/authme/service/SessionService.java
Normal file
@ -0,0 +1,86 @@
|
||||
package fr.xephi.authme.service;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.events.RestoreSessionEvent;
|
||||
import fr.xephi.authme.initialization.Reloadable;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.settings.properties.PluginSettings;
|
||||
import fr.xephi.authme.util.PlayerUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Handles the user sessions.
|
||||
*/
|
||||
public class SessionService implements Reloadable {
|
||||
|
||||
private final CommonService service;
|
||||
private final BukkitService bukkitService;
|
||||
private final DataSource database;
|
||||
|
||||
private boolean isEnabled;
|
||||
|
||||
@Inject
|
||||
SessionService(CommonService service, BukkitService bukkitService, DataSource database) {
|
||||
this.service = service;
|
||||
this.bukkitService = bukkitService;
|
||||
this.database = database;
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the player has a session he can resume.
|
||||
*
|
||||
* @param player the player to check
|
||||
* @return true if there is a current session, false otherwise
|
||||
*/
|
||||
public boolean canResumeSession(Player player) {
|
||||
final String name = player.getName();
|
||||
if (isEnabled && database.hasSession(name)) {
|
||||
database.setUnlogged(name);
|
||||
database.revokeSession(name);
|
||||
PlayerAuth auth = database.getAuth(name);
|
||||
if (hasValidSessionData(auth, player)) {
|
||||
RestoreSessionEvent event = bukkitService.createAndCallEvent(
|
||||
isAsync -> new RestoreSessionEvent(player, isAsync));
|
||||
return !event.isCancelled();
|
||||
} else {
|
||||
service.send(player, MessageKey.SESSION_EXPIRED);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given Player has a current session by comparing its properties
|
||||
* with the given PlayerAuth's.
|
||||
*
|
||||
* @param auth the player auth
|
||||
* @param player the associated player
|
||||
* @return true if the player may resume his login session, false otherwise
|
||||
*/
|
||||
private boolean hasValidSessionData(PlayerAuth auth, Player player) {
|
||||
if (auth == null) {
|
||||
ConsoleLogger.warning("No PlayerAuth in database for '" + player.getName() + "' during session check");
|
||||
return false;
|
||||
}
|
||||
long timeSinceLastLogin = System.currentTimeMillis() - auth.getLastLogin();
|
||||
return auth.getIp().equals(PlayerUtils.getPlayerIp(player))
|
||||
&& timeSinceLastLogin > 0
|
||||
&& timeSinceLastLogin < service.getProperty(PluginSettings.SESSIONS_TIMEOUT) * 60 * 1000;
|
||||
}
|
||||
|
||||
public void grantSession(String name) {
|
||||
if (isEnabled) {
|
||||
database.grantSession(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
this.isEnabled = service.getProperty(PluginSettings.SESSIONS_ENABLED);
|
||||
}
|
||||
}
|
@ -75,6 +75,10 @@ public final class DatabaseSettings implements SettingsHolder {
|
||||
public static final Property<String> MYSQL_COL_ISLOGGED =
|
||||
newProperty("DataSource.mySQLColumnLogged", "isLogged");
|
||||
|
||||
@Comment("Column for storing if a player has a valid session or not")
|
||||
public static final Property<String> MYSQL_COL_HASSESSION =
|
||||
newProperty("DataSource.mySQLColumnHasSession", "hasSession");
|
||||
|
||||
@Comment("Column for storing players ips")
|
||||
public static final Property<String> MYSQL_COL_IP =
|
||||
newProperty("DataSource.mySQLColumnIp", "ip");
|
||||
|
@ -144,7 +144,6 @@ public class RegisterAdminCommandTest {
|
||||
ArgumentCaptor<PlayerAuth> captor = ArgumentCaptor.forClass(PlayerAuth.class);
|
||||
verify(dataSource).saveAuth(captor.capture());
|
||||
assertAuthHasInfo(captor.getValue(), user, hashedPassword);
|
||||
verify(dataSource).setUnlogged(user);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -174,7 +173,6 @@ public class RegisterAdminCommandTest {
|
||||
ArgumentCaptor<PlayerAuth> captor = ArgumentCaptor.forClass(PlayerAuth.class);
|
||||
verify(dataSource).saveAuth(captor.capture());
|
||||
assertAuthHasInfo(captor.getValue(), user, hashedPassword);
|
||||
verify(dataSource).setUnlogged(user);
|
||||
verify(player).kickPlayer(kickForAdminRegister);
|
||||
}
|
||||
|
||||
|
@ -416,4 +416,36 @@ public abstract class AbstractDataSourceIntegrationTest {
|
||||
// then
|
||||
assertThat(loggedPlayersWithEmptyMail, contains("Bobby"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGrantAndRetrieveSessionFlag() {
|
||||
// given
|
||||
DataSource dataSource = getDataSource();
|
||||
|
||||
// when
|
||||
dataSource.grantSession("bobby");
|
||||
dataSource.grantSession("doesNotExist");
|
||||
|
||||
// then
|
||||
assertThat(dataSource.hasSession("bobby"), equalTo(true));
|
||||
assertThat(dataSource.hasSession("user"), equalTo(false));
|
||||
assertThat(dataSource.hasSession("bogus"), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRevokeSession() {
|
||||
// given
|
||||
DataSource dataSource = getDataSource();
|
||||
dataSource.grantSession("bobby");
|
||||
dataSource.grantSession("user");
|
||||
|
||||
// when
|
||||
dataSource.revokeSession("bobby");
|
||||
dataSource.revokeSession("userNotInDatabase");
|
||||
|
||||
// then
|
||||
assertThat(dataSource.hasSession("bobby"), equalTo(false));
|
||||
assertThat(dataSource.hasSession("user"), equalTo(true));
|
||||
assertThat(dataSource.hasSession("nonExistentName"), equalTo(false));
|
||||
}
|
||||
}
|
||||
|
222
src/test/java/fr/xephi/authme/service/SessionServiceTest.java
Normal file
222
src/test/java/fr/xephi/authme/service/SessionServiceTest.java
Normal file
@ -0,0 +1,222 @@
|
||||
package fr.xephi.authme.service;
|
||||
|
||||
import ch.jalu.injector.testing.BeforeInjecting;
|
||||
import ch.jalu.injector.testing.DelayedInjectionRunner;
|
||||
import ch.jalu.injector.testing.InjectDelayed;
|
||||
import fr.xephi.authme.TestHelper;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.events.RestoreSessionEvent;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.settings.properties.PluginSettings;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
/**
|
||||
* Test for {@link SessionService}.
|
||||
*/
|
||||
@RunWith(DelayedInjectionRunner.class)
|
||||
public class SessionServiceTest {
|
||||
|
||||
@InjectDelayed
|
||||
private SessionService sessionService;
|
||||
|
||||
@Mock
|
||||
private DataSource dataSource;
|
||||
@Mock
|
||||
private CommonService commonService;
|
||||
@Mock
|
||||
private BukkitService bukkitService;
|
||||
|
||||
@BeforeClass
|
||||
public static void initLogger() {
|
||||
TestHelper.setupLogger();
|
||||
}
|
||||
|
||||
@BeforeInjecting
|
||||
public void setUpEnabledProperty() {
|
||||
given(commonService.getProperty(PluginSettings.SESSIONS_ENABLED)).willReturn(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCheckSessionsEnabledSetting() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
given(commonService.getProperty(PluginSettings.SESSIONS_ENABLED)).willReturn(false);
|
||||
sessionService.reload();
|
||||
|
||||
// when
|
||||
boolean result = sessionService.canResumeSession(player);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(false));
|
||||
verifyZeroInteractions(dataSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCheckIfUserHasSession() {
|
||||
// given
|
||||
String name = "Bobby";
|
||||
Player player = mock(Player.class);
|
||||
given(player.getName()).willReturn(name);
|
||||
given(dataSource.hasSession(name)).willReturn(false);
|
||||
|
||||
// when
|
||||
boolean result = sessionService.canResumeSession(player);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(false));
|
||||
verify(commonService, only()).getProperty(PluginSettings.SESSIONS_ENABLED);
|
||||
verify(dataSource, only()).hasSession(name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCheckLastLoginDate() {
|
||||
// given
|
||||
String name = "Bobby";
|
||||
String ip = "127.3.12.15";
|
||||
Player player = mockPlayerWithNameAndIp(name, ip);
|
||||
given(commonService.getProperty(PluginSettings.SESSIONS_TIMEOUT)).willReturn(8);
|
||||
given(dataSource.hasSession(name)).willReturn(true);
|
||||
PlayerAuth auth = PlayerAuth.builder()
|
||||
.name(name)
|
||||
.lastLogin(System.currentTimeMillis() - 10 * 60 * 1000)
|
||||
.ip(ip).build();
|
||||
given(dataSource.getAuth(name)).willReturn(auth);
|
||||
|
||||
// when
|
||||
boolean result = sessionService.canResumeSession(player);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(false));
|
||||
verify(commonService).getProperty(PluginSettings.SESSIONS_ENABLED);
|
||||
verify(commonService).send(player, MessageKey.SESSION_EXPIRED);
|
||||
verify(dataSource).hasSession(name);
|
||||
verify(dataSource).setUnlogged(name);
|
||||
verify(dataSource).revokeSession(name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRefuseSessionForAuthWithZeroLastLoginTimestamp() {
|
||||
// given
|
||||
String name = "Bobby";
|
||||
String ip = "127.3.12.15";
|
||||
Player player = mockPlayerWithNameAndIp(name, ip);
|
||||
given(commonService.getProperty(PluginSettings.SESSIONS_TIMEOUT)).willReturn(8);
|
||||
given(dataSource.hasSession(name)).willReturn(true);
|
||||
PlayerAuth auth = PlayerAuth.builder()
|
||||
.name(name)
|
||||
.lastLogin(0)
|
||||
.ip(ip).build();
|
||||
given(dataSource.getAuth(name)).willReturn(auth);
|
||||
|
||||
// when
|
||||
boolean result = sessionService.canResumeSession(player);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(false));
|
||||
verify(commonService).getProperty(PluginSettings.SESSIONS_ENABLED);
|
||||
verify(commonService).send(player, MessageKey.SESSION_EXPIRED);
|
||||
verify(dataSource).hasSession(name);
|
||||
verify(dataSource).setUnlogged(name);
|
||||
verify(dataSource).revokeSession(name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCheckLastLoginIp() {
|
||||
// given
|
||||
String name = "Bobby";
|
||||
String ip = "127.3.12.15";
|
||||
Player player = mockPlayerWithNameAndIp(name, ip);
|
||||
given(dataSource.hasSession(name)).willReturn(true);
|
||||
PlayerAuth auth = PlayerAuth.builder()
|
||||
.name(name)
|
||||
.lastLogin(System.currentTimeMillis())
|
||||
.ip("8.8.8.8").build();
|
||||
given(dataSource.getAuth(name)).willReturn(auth);
|
||||
|
||||
// when
|
||||
boolean result = sessionService.canResumeSession(player);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(false));
|
||||
verify(commonService).getProperty(PluginSettings.SESSIONS_ENABLED);
|
||||
verify(commonService).send(player, MessageKey.SESSION_EXPIRED);
|
||||
verify(dataSource).hasSession(name);
|
||||
verify(dataSource).setUnlogged(name);
|
||||
verify(dataSource).revokeSession(name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEmitEventForValidSession() {
|
||||
// given
|
||||
String name = "Bobby";
|
||||
String ip = "127.3.12.15";
|
||||
Player player = mockPlayerWithNameAndIp(name, ip);
|
||||
given(commonService.getProperty(PluginSettings.SESSIONS_TIMEOUT)).willReturn(8);
|
||||
given(dataSource.hasSession(name)).willReturn(true);
|
||||
PlayerAuth auth = PlayerAuth.builder()
|
||||
.name(name)
|
||||
.lastLogin(System.currentTimeMillis() - 7 * 60 * 1000)
|
||||
.ip(ip).build();
|
||||
given(dataSource.getAuth(name)).willReturn(auth);
|
||||
RestoreSessionEvent event = spy(new RestoreSessionEvent(player, false));
|
||||
given(bukkitService.createAndCallEvent(any(Function.class))).willReturn(event);
|
||||
|
||||
// when
|
||||
boolean result = sessionService.canResumeSession(player);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(true));
|
||||
verify(commonService).getProperty(PluginSettings.SESSIONS_ENABLED);
|
||||
verify(commonService).getProperty(PluginSettings.SESSIONS_TIMEOUT);
|
||||
verifyNoMoreInteractions(commonService);
|
||||
verify(dataSource).hasSession(name);
|
||||
verify(dataSource).setUnlogged(name);
|
||||
verify(dataSource).revokeSession(name);
|
||||
verify(event).isCancelled();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHandleNullPlayerAuth() {
|
||||
// given
|
||||
String name = "Bobby";
|
||||
Player player = mockPlayerWithNameAndIp(name, "127.3.12.15");
|
||||
given(dataSource.hasSession(name)).willReturn(true);
|
||||
given(dataSource.getAuth(name)).willReturn(null);
|
||||
|
||||
// when
|
||||
boolean result = sessionService.canResumeSession(player);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(false));
|
||||
verify(commonService).getProperty(PluginSettings.SESSIONS_ENABLED);
|
||||
verify(dataSource).hasSession(name);
|
||||
verify(dataSource).setUnlogged(name);
|
||||
verify(dataSource).revokeSession(name);
|
||||
verify(dataSource).getAuth(name);
|
||||
}
|
||||
|
||||
private static Player mockPlayerWithNameAndIp(String name, String ip) {
|
||||
Player player = mock(Player.class);
|
||||
given(player.getName()).willReturn(name);
|
||||
TestHelper.mockPlayerIp(player, ip);
|
||||
return player;
|
||||
}
|
||||
}
|
@ -13,10 +13,12 @@ CREATE TABLE authme (
|
||||
yaw FLOAT,
|
||||
pitch FLOAT,
|
||||
email VARCHAR(255) DEFAULT 'your@email.com',
|
||||
isLogged INT DEFAULT '0', realname VARCHAR(255) NOT NULL DEFAULT 'Player',
|
||||
isLogged INT DEFAULT '0',
|
||||
realname VARCHAR(255) NOT NULL DEFAULT 'Player',
|
||||
salt varchar(255),
|
||||
recoverycode VARCHAR(20),
|
||||
recoveryexpiration BIGINT,
|
||||
hasSession INT NOT NULL DEFAULT '0',
|
||||
CONSTRAINT table_const_prim PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user