Merge branch 'master' of https://github.com/AuthMe/AuthMeReloaded into 792-registration-date-and-ip

Conflicts:
	src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java
	src/main/java/fr/xephi/authme/datasource/Columns.java
	src/main/java/fr/xephi/authme/datasource/SQLite.java
	src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java
	src/main/java/fr/xephi/authme/service/SessionService.java
	src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java
	src/test/java/fr/xephi/authme/service/SessionServiceTest.java
	src/test/resources/fr/xephi/authme/datasource/sql-initialize.sql
This commit is contained in:
ljacqu 2017-10-15 23:38:01 +02:00
commit 90073ef95d
19 changed files with 536 additions and 97 deletions

View File

@ -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

View File

@ -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) {

View File

@ -51,39 +51,30 @@ 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)
.registrationDate(System.currentTimeMillis())
.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)
.registrationDate(System.currentTimeMillis())
.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)));
}
});
}

View File

@ -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();

View File

@ -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 final String REGISTRATION_DATE;
public final String REGISTRATION_IP;
@ -46,6 +47,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);
REGISTRATION_DATE = settings.getProperty(DatabaseSettings.MYSQL_COL_REGISTER_DATE);
REGISTRATION_IP = settings.getProperty(DatabaseSettings.MYSQL_COL_REGISTER_IP);
}

View File

@ -159,6 +159,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.
*/

View File

@ -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() {
}

View File

@ -248,6 +248,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");
}
@ -575,6 +580,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 + "=?;";

View File

@ -145,7 +145,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");
@ -442,7 +448,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()) {
@ -471,14 +477,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);
}
}
@ -607,14 +651,4 @@ public class SQLite implements DataSource {
}
}
}
private static void close(ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException ex) {
logSqlException(ex);
}
}
}
}

View File

@ -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,32 +174,6 @@ public class AsynchronousJoin implements AsynchronousProcess {
});
}
// TODO #792: lastlogin date might be null (not updating now because of has_session branch changes)
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.getLastIp().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.

View File

@ -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

View File

@ -53,6 +53,7 @@ public class AsynchronousLogout implements AsynchronousProcess {
playerCache.removePlayer(name);
database.setUnlogged(name);
database.revokeSession(name);
syncProcessManager.processSyncPlayerLogout(player);
}
}

View File

@ -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()) {

View 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 PlayerUtils.getPlayerIp(player).equals(auth.getLastIp())
&& 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);
}
}

View File

@ -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 the player's last IP")
public static final Property<String> MYSQL_COL_LAST_IP =
newProperty("DataSource.mySQLColumnIp", "ip");

View File

@ -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);
}

View File

@ -435,4 +435,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));
}
}

View 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)
.lastIp(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(0L)
.lastIp(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())
.lastIp("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)
.lastIp(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;
}
}

View File

@ -18,6 +18,7 @@ CREATE TABLE authme (
isLogged INT DEFAULT '0',
realname VARCHAR(255) NOT NULL DEFAULT 'Player',
salt varchar(255),
hasSession INT NOT NULL DEFAULT '0',
CONSTRAINT table_const_prim PRIMARY KEY (id)
);