From 2021113732cc4d57516a0a9d8c9014ccb788d969 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 10 May 2017 19:45:20 +0200 Subject: [PATCH] #1023 Create LoginSecurity converter --- .../authme/command/CommandInitializer.java | 2 +- .../executable/authme/ConverterCommand.java | 2 + .../converter/LoginSecurityConverter.java | 162 ++++++++++++++++++ .../properties/ConverterSettings.java | 20 +++ src/test/java/fr/xephi/authme/TestHelper.java | 1 + .../converter/LoginSecurityConverterTest.java | 79 +++++++++ .../datasource/converter/LoginSecurity.db | Bin 0 -> 15360 bytes 7 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 src/main/java/fr/xephi/authme/datasource/converter/LoginSecurityConverter.java create mode 100644 src/test/java/fr/xephi/authme/datasource/converter/LoginSecurityConverterTest.java create mode 100644 src/test/resources/fr/xephi/authme/datasource/converter/LoginSecurity.db diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java index 0e8c28ab..1c1240a2 100644 --- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java +++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java @@ -316,7 +316,7 @@ public class CommandInitializer { .description("Converter command") .detailedDescription("Converter command for AuthMeReloaded.") .withArgument("job", "Conversion job: xauth / crazylogin / rakamak / " - + "royalauth / vauth / sqliteToSql / mysqlToSqlite", false) + + "royalauth / vauth / sqliteToSql / mysqlToSqlite / loginsecurity", false) .permission(AdminPermission.CONVERTER) .executableCommand(ConverterCommand.class) .register(); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java index c59c4cb9..ac542c73 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java @@ -6,6 +6,7 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.converter.Converter; import fr.xephi.authme.datasource.converter.CrazyLoginConverter; +import fr.xephi.authme.datasource.converter.LoginSecurityConverter; import fr.xephi.authme.datasource.converter.MySqlToSqlite; import fr.xephi.authme.datasource.converter.RakamakConverter; import fr.xephi.authme.datasource.converter.RoyalAuthConverter; @@ -85,6 +86,7 @@ public class ConverterCommand implements ExecutableCommand { .put("vauth", VAuthConverter.class) .put("sqlitetosql", SqliteToSql.class) .put("mysqltosqlite", MySqlToSqlite.class) + .put("loginsecurity", LoginSecurityConverter.class) .build(); } diff --git a/src/main/java/fr/xephi/authme/datasource/converter/LoginSecurityConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/LoginSecurityConverter.java new file mode 100644 index 00000000..b8eabbe4 --- /dev/null +++ b/src/main/java/fr/xephi/authme/datasource/converter/LoginSecurityConverter.java @@ -0,0 +1,162 @@ +package fr.xephi.authme.datasource.converter; + +import com.google.common.annotations.VisibleForTesting; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.ConverterSettings; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; +import java.io.File; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +import static fr.xephi.authme.util.Utils.logAndSendMessage; + +/** + * Converts data from LoginSecurity to AuthMe. + */ +public class LoginSecurityConverter implements Converter { + + private final File dataFolder; + private final DataSource dataSource; + + private final boolean useSqlite; + private final String mySqlHost; + private final String mySqlDatabase; + private final String mySqlUser; + private final String mySqlPassword; + + @Inject + LoginSecurityConverter(@DataFolder File dataFolder, DataSource dataSource, Settings settings) { + this.dataFolder = dataFolder; + this.dataSource = dataSource; + + useSqlite = settings.getProperty(ConverterSettings.LOGINSECURITY_USE_SQLITE); + mySqlHost = settings.getProperty(ConverterSettings.LOGINSECURITY_MYSQL_HOST); + mySqlDatabase = settings.getProperty(ConverterSettings.LOGINSECURITY_MYSQL_DATABASE); + mySqlUser = settings.getProperty(ConverterSettings.LOGINSECURITY_MYSQL_USER); + mySqlPassword = settings.getProperty(ConverterSettings.LOGINSECURITY_MYSQL_PASSWORD); + } + + @Override + public void execute(CommandSender sender) { + try (Connection connection = createConnectionOrInformSender(sender)) { + if (connection != null) { + performConversion(sender, connection); + } + } catch (SQLException e) { + sender.sendMessage("Failed to convert from SQLite. Please see the log for more info"); + ConsoleLogger.logException("Could not fetch or migrate data:", e); + } + } + + @VisibleForTesting + void performConversion(CommandSender sender, Connection connection) throws SQLException { + try (Statement statement = connection.createStatement()) { + statement.execute( + "SELECT * from ls_players LEFT JOIN ls_locations ON ls_locations.id = ls_players.id"); + try (ResultSet resultSet = statement.getResultSet()) { + migrateData(sender, resultSet); + } + } + } + + private void migrateData(CommandSender sender, ResultSet resultSet) throws SQLException { + List skippedPlayers = new ArrayList<>(); + long successfulSaves = 0; + while (resultSet.next()) { + String name = resultSet.getString("last_name"); + if (dataSource.isAuthAvailable(name)) { + skippedPlayers.add(name); + } else { + PlayerAuth auth = buildAuthFromLoginSecurity(name, resultSet); + dataSource.saveAuth(auth); + ++successfulSaves; + } + } + + logAndSendMessage(sender, "Migrated " + successfulSaves + " accounts successfully from LoginSecurity"); + if (!skippedPlayers.isEmpty()) { + logAndSendMessage(sender, "Skipped conversion for players which were already in AuthMe: " + + String.join(", ", skippedPlayers)); + } + } + + private static PlayerAuth buildAuthFromLoginSecurity(String name, ResultSet resultSet) throws SQLException { + return PlayerAuth.builder() + .name(name) + .realName(name) + .password(resultSet.getString("password"), null) + .ip(resultSet.getString("ip_address")) + .lastLogin(resultSet.getLong("last_login")) + // TODO #792: Register date + .locX(resultSet.getDouble("x")) + .locY(resultSet.getDouble("y")) + .locZ(resultSet.getDouble("z")) + .locWorld(resultSet.getString("world")) + .locYaw(resultSet.getFloat("yaw")) + .locPitch(resultSet.getFloat("pitch")) + .build(); + } + + private Connection createConnectionOrInformSender(CommandSender sender) { + Connection connection; + if (useSqlite) { + File sqliteDatabase = new File(dataFolder.getParentFile(), "LoginSecurity/LoginSecurity.db"); + if (!sqliteDatabase.exists()) { + sender.sendMessage("The file '" + sqliteDatabase.getPath() + "' does not exist"); + return null; + } + connection = createSqliteConnection("plugins/LoginSecurity/LoginSecurity.db"); + } else { + if (mySqlDatabase.isEmpty() || mySqlUser.isEmpty()) { + sender.sendMessage("The LoginSecurity database or username is not configured in AuthMe's config.yml"); + return null; + } + connection = createMySqlConnection(); + } + + if (connection == null) { + sender.sendMessage("Could not connect to LoginSecurity using Sqlite = " + + useSqlite + ", see log for more info"); + return null; + } + return connection; + } + + @VisibleForTesting + Connection createSqliteConnection(String path) { + try { + Class.forName("org.sqlite.JDBC"); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + + try { + return DriverManager.getConnection( + "jdbc:sqlite:" + path, "trump", "donald"); + } catch (SQLException e) { + ConsoleLogger.logException("Could not connect to SQLite database", e); + return null; + } + } + + private Connection createMySqlConnection() { + try { + return DriverManager.getConnection( + "jdbc:mysql://" + mySqlHost + "/" + mySqlDatabase, mySqlUser, mySqlPassword); + } catch (SQLException e) { + ConsoleLogger.logException("Could not connect to SQLite database", e); + return null; + } + } +} diff --git a/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java b/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java index d2b34c9a..c845b3bf 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java @@ -24,6 +24,26 @@ public final class ConverterSettings implements SettingsHolder { public static final Property CRAZYLOGIN_FILE_NAME = newProperty("Converter.CrazyLogin.fileName", "accounts.db"); + @Comment("LoginSecurity: convert from SQLite; if false we use MySQL") + public static final Property LOGINSECURITY_USE_SQLITE = + newProperty("Converter.loginSecurity.useSqlite", true); + + @Comment("LoginSecurity MySQL: database host") + public static final Property LOGINSECURITY_MYSQL_HOST = + newProperty("Converter.loginSecurity.mySql.host", ""); + + @Comment("LoginSecurity MySQL: database name") + public static final Property LOGINSECURITY_MYSQL_DATABASE = + newProperty("Converter.loginSecurity.mySql.database", ""); + + @Comment("LoginSecurity MySQL: database user") + public static final Property LOGINSECURITY_MYSQL_USER = + newProperty("Converter.loginSecurity.mySql.user", ""); + + @Comment("LoginSecurity MySQL: password for database user") + public static final Property LOGINSECURITY_MYSQL_PASSWORD = + newProperty("Converter.loginSecurity.mySql.password", ""); + private ConverterSettings() { } diff --git a/src/test/java/fr/xephi/authme/TestHelper.java b/src/test/java/fr/xephi/authme/TestHelper.java index 5017c476..ee7aaa5e 100644 --- a/src/test/java/fr/xephi/authme/TestHelper.java +++ b/src/test/java/fr/xephi/authme/TestHelper.java @@ -30,6 +30,7 @@ public final class TestHelper { public static final String SOURCES_FOLDER = "src/main/java/"; public static final String TEST_SOURCES_FOLDER = "src/test/java/"; + public static final String TEST_RESOURCES_FOLDER = "src/test/resources/"; public static final String PROJECT_ROOT = "/fr/xephi/authme/"; private TestHelper() { diff --git a/src/test/java/fr/xephi/authme/datasource/converter/LoginSecurityConverterTest.java b/src/test/java/fr/xephi/authme/datasource/converter/LoginSecurityConverterTest.java new file mode 100644 index 00000000..3d19e3d2 --- /dev/null +++ b/src/test/java/fr/xephi/authme/datasource/converter/LoginSecurityConverterTest.java @@ -0,0 +1,79 @@ +package fr.xephi.authme.datasource.converter; + +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.initialization.DataFolder; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.ConverterSettings; +import org.bukkit.command.CommandSender; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; + +import java.io.File; +import java.sql.Connection; +import java.sql.SQLException; + +import static fr.xephi.authme.AuthMeMatchers.equalToHash; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link LoginSecurityConverter}. + */ +@RunWith(DelayedInjectionRunner.class) +public class LoginSecurityConverterTest { + + @InjectDelayed + private LoginSecurityConverter converter; + + @Mock + private DataSource dataSource; + @Mock + private Settings settings; + @DataFolder + private File dataFolder = new File("."); // not used but required for injection + + @BeforeInjecting + public void initMocks() { + TestHelper.setupLogger(); + given(settings.getProperty(ConverterSettings.LOGINSECURITY_USE_SQLITE)).willReturn(true); + } + + @Test + public void shouldConvertFromSqlite() throws SQLException { + // given + Connection connection = converter.createSqliteConnection( + TestHelper.TEST_RESOURCES_FOLDER + TestHelper.PROJECT_ROOT + "datasource/converter/LoginSecurity.db"); + CommandSender sender = mock(CommandSender.class); + + // when + converter.performConversion(sender, connection); + + // then + ArgumentCaptor captor = ArgumentCaptor.forClass(PlayerAuth.class); + verify(dataSource, times(3)).saveAuth(captor.capture()); + assertThat(captor.getAllValues().get(0).getNickname(), equalTo("player1")); + assertThat(captor.getAllValues().get(0).getRealName(), equalTo("Player1")); + assertThat(captor.getAllValues().get(0).getLastLogin(), equalTo(1494242093652L)); + assertThat(captor.getAllValues().get(0).getPassword(), equalToHash("$2a$10$E1Ri7XKeIIBv4qVaiPplgepT7QH9xGFh3hbHfcmCjq7hiW.UBTiGK")); + assertThat(captor.getAllValues().get(0).getIp(), equalTo("127.0.0.1")); + + assertThat(captor.getAllValues().get(1).getNickname(), equalTo("player2")); + assertThat(captor.getAllValues().get(1).getLastLogin(), equalTo(1494242174589L)); + assertThat(captor.getAllValues().get(1).getIp(), equalTo("127.4.5.6")); + + assertThat(captor.getAllValues().get(2).getRealName(), equalTo("Player3")); + assertThat(captor.getAllValues().get(2).getPassword(), equalToHash("$2a$10$WFui8KSXMLDOVXKFpCLyPukPi4M82w1cv/rNojsAnwJjba3pp8sba")); + } + +} diff --git a/src/test/resources/fr/xephi/authme/datasource/converter/LoginSecurity.db b/src/test/resources/fr/xephi/authme/datasource/converter/LoginSecurity.db new file mode 100644 index 0000000000000000000000000000000000000000..4c10efc77af44f85433148bb5a78048de1caaa7e GIT binary patch literal 15360 zcmeHO%WoS+7~l28NfQV;aEPiLn5r#_!A`Qfe%{E#X+n}FO`N6$q1AYIVo$T)-R!QD z7>Pq$g-RSi;=upF1qs2WHx391^}>xq?;ueLsR(fZ2{B`Pon1dt6Qxbs?#Ld`&V1ka z+ut{j-QUctUt1y$hFgYJLJpL;9**a^cOc|AuAhE~=+_OsG!b$y=r8X{cS`niv0nzl zv=o5HOy)K45BRH=?4eVWAJ^$7cTGz5!X#fVX!bS)l;EnC)M+h;UkBXuLZL!$U6H z*pgctg5N<-#l&svt7iITX??py=_Kx#H7#S8sI-l>4Sv}cZ@CyzXHl33Sx@d`y|Nb! z(wKJoAOr{m2)u*{u>KFgHyn5bexg4?2oMMmINJz}fG{`oc37Gt`;h3nSa%GIV4Hvc z*YExKGw&eo+Z|m_5ToepX2U_Y=FQQ-~o6box7Xr=^Ktj_>UVr ztmj38aGKh!_qnc(1O=~dk55WRijK7kfN~CmDGn_NmbC^ z5pwm#r6@FsqZB&|HpE)oaR^+iGlJ*;=ktLkusT5CBoG)3{lNkLAqOh_!_f9g)ELxp zMi96?7Jt^~y_v-I%|1#o|M2EV^?2oW4`B9i4SNJ6WKxNICN`PRtJDgt#?V$i9TO%K z88xklqLRTid!)J#A|fp3?W; zy_H9jX{PNws+#fTx6BM0uTbyY^O^BKU^!7Vct0AGN?zDPnS3lAPa=wjil}$gOe}#i zYBD2b6fDGRXb5#Pvceik-B`p63)8#to$H9Knpy#yxzx4!%>I?RqEyV!Zz-jjTRW*D z*&N@P&XFsN42|!9%hltR8vlR5fuF$v`09+b+`xDQ{MIjai{r~hm5?Bx*@azz1>i>-YJ}S3C-@0sVo1NxV zp&{mS?3K214YBHNYX)6d@cmo;!n4QS!Ue*P1u!07R+|?rSVj~YrbDgUZdcQxY3&yV zYHMC~SIbR~bI%+2vngaAD%)Ry7bI8PlXsM=4>(uXo9S1s9d~fsXfuHxWhZAaDd@HH z4_*_Xg}1PUbp_k(EQwoW*G@^$qaiBRFkKg-?aZ-=;y{{JHdS=5*jqgV@!81J?n1P~ z4^`Ly0pe($kts* zE29zrT;`ucn_TWrh5qcLDsZPX_5|W;d-9G__0G2qU2mpe`I%3`o_V=xiuW1WG{?JT SbT|H!W_iyaz{n{Mmj3~WN