diff --git a/.checkstyle.xml b/.checkstyle.xml index d12ab0d40..eac59ec94 100644 --- a/.checkstyle.xml +++ b/.checkstyle.xml @@ -6,7 +6,7 @@ - + @@ -32,6 +32,7 @@ + diff --git a/.codeclimate.yml b/.codeclimate.yml index 84c6151de..536899863 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -37,7 +37,6 @@ exclude_patterns: - 'src/main/java/fr/xephi/authme/mail/OAuth2Provider.java' - 'src/main/java/fr/xephi/authme/mail/OAuth2SaslClient.java' - 'src/main/java/fr/xephi/authme/mail/OAuth2SaslClientFactory.java' - - 'src/main/java/fr/xephi/authme/security/crypts/BCryptService.java' - 'src/main/java/fr/xephi/authme/security/crypts/PhpBB.java' - 'src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java' - 'src/main/java/fr/xephi/authme/security/crypts/Wordpress.java' diff --git a/docs/hash_algorithms.md b/docs/hash_algorithms.md index 658319545..f22948cee 100644 --- a/docs/hash_algorithms.md +++ b/docs/hash_algorithms.md @@ -1,5 +1,5 @@ - + ## Hash Algorithms AuthMe supports the following hash algorithms for storing your passwords safely. @@ -8,8 +8,9 @@ AuthMe supports the following hash algorithms for storing your passwords safely. Algorithm | Recommendation | Hash length | ASCII | | Salt type | Length | Separate? --------- | -------------- | ----------- | ----- | --- | --------- | ------ | --------- ARGON2 | Recommended | 96 | | | Text | 16 | -BCRYPT | Recommended | 60 | | | Text | | +BCRYPT | Recommended | 60 | | | Text | 22 | BCRYPT2Y | Recommended | 60 | | | Text | 22 | +CMW | Do not use | 32 | | | None | | CRAZYCRYPT1 | Do not use | 128 | | | Username | | IPB3 | Acceptable | 32 | | | Text | 5 | Y IPB4 | Does not work | 60 | | | Text | 22 | Y @@ -27,10 +28,10 @@ SHA256 | Recommended | 86 | | | Text | 16 | SMF | Do not use | 40 | | | Username | | Y TWO_FACTOR | Does not work | 16 | | | None | | WBB3 | Acceptable | 40 | | | Text | 40 | Y -WBB4 | Recommended | 60 | | | Text | 8 | +WBB4 | Recommended | 60 | | | Text | 22 | WORDPRESS | Acceptable | 34 | | | Text | 9 | XAUTH | Recommended | 140 | | | Text | 12 | -XFBCRYPT | | 60 | | | | | +XFBCRYPT | Recommended | 60 | | | Text | 22 | CUSTOM | | | | | | | | @@ -79,4 +80,4 @@ or bad. --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Fri Dec 01 19:16:17 CET 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Sep 02 20:38:48 CEST 2018 diff --git a/docs/permission_nodes.md b/docs/permission_nodes.md index 643a327b0..3f0df2206 100644 --- a/docs/permission_nodes.md +++ b/docs/permission_nodes.md @@ -32,6 +32,7 @@ The following are the permission nodes that are currently supported by the lates - **authme.admin.updatemessages** – Permission to use the update messages command. - **authme.allowchatbeforelogin** – Permission to send chat messages before being logged in. - **authme.allowmultipleaccounts** – Permission to be able to register multiple accounts. +- **authme.bypassbungeesend** – Permission node to bypass BungeeCord server teleportation. - **authme.bypassantibot** – Permission node to bypass AntiBot protection. - **authme.bypasscountrycheck** – Permission to bypass the GeoIp country code check. - **authme.bypassforcesurvival** – Permission for users to bypass force-survival mode. diff --git a/docs/translations.md b/docs/translations.md index c8ace37b1..aac58a65f 100644 --- a/docs/translations.md +++ b/docs/translations.md @@ -1,5 +1,5 @@ - + # AuthMe Translations The following translations are available in AuthMe. Set `messagesLanguage` to the language code @@ -13,7 +13,7 @@ Code | Language | Translated |   [cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 80% | bar [de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 80% | bar [eo](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eo.yml) | Esperanto | 80% | bar -[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 92% | bar +[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 100% | bar [et](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_et.yml) | Estonian | 80% | bar [eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 42% | bar [fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 45% | bar @@ -28,9 +28,9 @@ Code | Language | Translated |   [pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 100% | bar [pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 100% | bar [ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 80% | bar -[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 92% | bar +[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 93% | bar [sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 80% | bar -[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 76% | bar +[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 100% | bar [uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 63% | bar [vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 77% | bar [zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 89% | bar @@ -41,4 +41,4 @@ Code | Language | Translated |   --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Mon Jun 25 21:53:35 CEST 2018 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Thu Aug 02 21:12:10 CEST 2018 diff --git a/pom.xml b/pom.xml index d2213d370..4a836e44e 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ Xephi, sgdc3, DNx5, timvisee, games647, ljacqu, Gnat008 - 1.13-pre7-R0.1-SNAPSHOT + 1.13-R0.1-SNAPSHOT @@ -145,7 +145,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.8.0 ${jdk.version} ${jdk.version} @@ -173,7 +173,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.21.0 + 2.22.0 @@ -295,12 +295,32 @@ javax.inject fr.xephi.authme.libs.javax.inject + + at.favre.lib + fr.xephi.authme.libs.at.favre.lib + org.bstats fr.xephi.authme.libs.org.bstats + + org.postgresql + fr.xephi.authme.libs.org.postgresql + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + @@ -313,6 +333,11 @@ maven-deploy-plugin 2.8.2 + + org.apache.maven.plugins + maven-site-plugin + 3.7.1 + org.eluder.coveralls coveralls-maven-plugin @@ -400,7 +425,7 @@ com.google.guava guava - 25.1-jre + 26.0-jre true @@ -478,7 +503,7 @@ com.warrenstrange googleauth - 1.1.5 + 1.2.0 true @@ -518,7 +543,7 @@ ch.jalu configme - 0.4.1 + 1.0.1 true @@ -682,7 +707,7 @@ com.onarandombox.multiversecore Multiverse-Core - 2.6.0-SNAPSHOT + 2.6.0 jar provided @@ -759,6 +784,14 @@ + + + at.favre.lib + bcrypt + 0.5.0 + true + + de.luricos.bukkit @@ -808,6 +841,12 @@ true + + org.postgresql + postgresql + 42.2.4 + + @@ -828,7 +867,7 @@ org.mockito mockito-core test - 2.19.0 + 2.21.0 hamcrest-core diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 6a2b3583c..b206fed7a 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -198,7 +198,7 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.setLogFile(new File(getDataFolder(), LOG_FILENAME)); // Check java version - if(!SystemUtils.isJavaVersionAtLeast(1.8f)) { + if (!SystemUtils.isJavaVersionAtLeast(1.8f)) { throw new IllegalStateException("You need Java 1.8 or above to run this plugin!"); } diff --git a/src/main/java/fr/xephi/authme/api/v3/AuthMeApi.java b/src/main/java/fr/xephi/authme/api/v3/AuthMeApi.java index 575c61ab1..7dcf137c3 100644 --- a/src/main/java/fr/xephi/authme/api/v3/AuthMeApi.java +++ b/src/main/java/fr/xephi/authme/api/v3/AuthMeApi.java @@ -12,13 +12,11 @@ import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.service.GeoIpService; import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.util.PlayerUtils; - import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; import javax.inject.Inject; - import java.time.Instant; import java.util.ArrayList; import java.util.Date; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java index ff41ddbac..82b109cc3 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java @@ -1,8 +1,8 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.command.ExecutableCommand; -import fr.xephi.authme.task.purge.PurgeService; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.task.purge.PurgeService; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java index 44c303843..1e72c0587 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java @@ -1,10 +1,10 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.service.AntiBotService; import fr.xephi.authme.command.CommandMapper; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.command.FoundCommandResult; import fr.xephi.authme.command.help.HelpProvider; +import fr.xephi.authme.service.AntiBotService; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java index 8be10c5f2..7338c8686 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java @@ -17,8 +17,8 @@ import java.util.Map; import java.util.Optional; import java.util.function.Function; -import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation; import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.applyToLimboPlayersMap; +import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation; /** * Shows the data stored in LimboPlayers and the equivalent properties on online players. diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ShowEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ShowEmailCommand.java index f09321e6a..9e2049875 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/ShowEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/ShowEmailCommand.java @@ -27,7 +27,7 @@ public class ShowEmailCommand extends PlayerCommand { public void runCommand(Player player, List arguments) { PlayerAuth auth = playerCache.getAuth(player.getName()); if (auth != null && !Utils.isEmailEmpty(auth.getEmail())) { - if(commonService.getProperty(SecuritySettings.USE_EMAIL_MASKING)){ + if (commonService.getProperty(SecuritySettings.USE_EMAIL_MASKING)){ commonService.send(player, MessageKey.EMAIL_SHOW, emailMask(auth.getEmail())); } else { commonService.send(player, MessageKey.EMAIL_SHOW, auth.getEmail()); diff --git a/src/main/java/fr/xephi/authme/data/QuickCommandsProtectionManager.java b/src/main/java/fr/xephi/authme/data/QuickCommandsProtectionManager.java index 335944c82..7414e21c8 100644 --- a/src/main/java/fr/xephi/authme/data/QuickCommandsProtectionManager.java +++ b/src/main/java/fr/xephi/authme/data/QuickCommandsProtectionManager.java @@ -48,7 +48,7 @@ public class QuickCommandsProtectionManager implements SettingsDependent, HasCle * @param player the player to process */ public void processJoin(Player player) { - if(shouldSavePlayer(player)) { + if (shouldSavePlayer(player)) { setJoin(player.getName()); } } diff --git a/src/main/java/fr/xephi/authme/data/TempbanManager.java b/src/main/java/fr/xephi/authme/data/TempbanManager.java index e6b685aca..fd2a0a3ed 100644 --- a/src/main/java/fr/xephi/authme/data/TempbanManager.java +++ b/src/main/java/fr/xephi/authme/data/TempbanManager.java @@ -104,7 +104,7 @@ public class TempbanManager implements SettingsDependent, HasCleanup { expires.setTime(newTime); bukkitService.scheduleSyncDelayedTask(() -> { - if(customCommand.isEmpty()) { + if (customCommand.isEmpty()) { bukkitService.banIp(ip, reason, expires, "AuthMe"); player.kickPlayer(reason); } else { diff --git a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java index 165560b1e..ec57cd57f 100644 --- a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java @@ -71,6 +71,11 @@ public class CacheDataSource implements DataSource { source.reload(); } + @Override + public boolean isCached() { + return true; + } + @Override public boolean isAuthAvailable(String user) { return getAuth(user) != null; diff --git a/src/main/java/fr/xephi/authme/datasource/DataSource.java b/src/main/java/fr/xephi/authme/datasource/DataSource.java index 6ce28852f..3152eb17e 100644 --- a/src/main/java/fr/xephi/authme/datasource/DataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/DataSource.java @@ -14,6 +14,15 @@ import java.util.Set; */ public interface DataSource extends Reloadable { + /** + * Return whether the data source is cached and needs to send plugin messaging updates. + * + * @return true if the data source is cached. + */ + default boolean isCached() { + return false; + } + /** * Return whether there is a record for the given username. * diff --git a/src/main/java/fr/xephi/authme/datasource/DataSourceType.java b/src/main/java/fr/xephi/authme/datasource/DataSourceType.java index 093e88ed9..14d3d23b5 100644 --- a/src/main/java/fr/xephi/authme/datasource/DataSourceType.java +++ b/src/main/java/fr/xephi/authme/datasource/DataSourceType.java @@ -7,6 +7,8 @@ public enum DataSourceType { MYSQL, + POSTGRESQL, + SQLITE, @Deprecated diff --git a/src/main/java/fr/xephi/authme/datasource/PostgreSqlDataSource.java b/src/main/java/fr/xephi/authme/datasource/PostgreSqlDataSource.java new file mode 100644 index 000000000..eaf869b8a --- /dev/null +++ b/src/main/java/fr/xephi/authme/datasource/PostgreSqlDataSource.java @@ -0,0 +1,455 @@ +package fr.xephi.authme.datasource; + +import com.google.common.annotations.VisibleForTesting; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.columnshandler.AuthMeColumnsHandler; +import fr.xephi.authme.datasource.mysqlextensions.MySqlExtension; +import fr.xephi.authme.datasource.mysqlextensions.MySqlExtensionsFactory; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.DatabaseSettings; +import fr.xephi.authme.settings.properties.HooksSettings; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static fr.xephi.authme.datasource.SqlDataSourceUtils.getNullableLong; +import static fr.xephi.authme.datasource.SqlDataSourceUtils.logSqlException; + +/** + * PostgreSQL data source. + */ +public class PostgreSqlDataSource extends AbstractSqlDataSource { + + private String host; + private String port; + private String username; + private String password; + private String database; + private String tableName; + private int poolSize; + private int maxLifetime; + private List columnOthers; + private Columns col; + private MySqlExtension sqlExtension; + private HikariDataSource ds; + + public PostgreSqlDataSource(Settings settings, MySqlExtensionsFactory extensionsFactory) throws SQLException { + setParameters(settings, extensionsFactory); + + // Set the connection arguments (and check if connection is ok) + try { + this.setConnectionArguments(); + } catch (RuntimeException e) { + if (e instanceof IllegalArgumentException) { + ConsoleLogger.warning("Invalid database arguments! Please check your configuration!"); + ConsoleLogger.warning("If this error persists, please report it to the developer!"); + } + if (e instanceof PoolInitializationException) { + ConsoleLogger.warning("Can't initialize database connection! Please check your configuration!"); + ConsoleLogger.warning("If this error persists, please report it to the developer!"); + } + ConsoleLogger.warning("Can't use the Hikari Connection Pool! Please, report this error to the developer!"); + throw e; + } + + // Initialize the database + try { + checkTablesAndColumns(); + } catch (SQLException e) { + closeConnection(); + ConsoleLogger.logException("Can't initialize the PostgreSQL database:", e); + ConsoleLogger.warning("Please check your database settings in the config.yml file!"); + throw e; + } + } + + @VisibleForTesting + PostgreSqlDataSource(Settings settings, HikariDataSource hikariDataSource, + MySqlExtensionsFactory extensionsFactory) { + ds = hikariDataSource; + setParameters(settings, extensionsFactory); + } + + /** + * Retrieves various settings. + * + * @param settings the settings to read properties from + * @param extensionsFactory factory to create the MySQL extension + */ + private void setParameters(Settings settings, MySqlExtensionsFactory extensionsFactory) { + this.host = settings.getProperty(DatabaseSettings.MYSQL_HOST); + this.port = settings.getProperty(DatabaseSettings.MYSQL_PORT); + this.username = settings.getProperty(DatabaseSettings.MYSQL_USERNAME); + this.password = settings.getProperty(DatabaseSettings.MYSQL_PASSWORD); + this.database = settings.getProperty(DatabaseSettings.MYSQL_DATABASE); + this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE); + this.columnOthers = settings.getProperty(HooksSettings.MYSQL_OTHER_USERNAME_COLS); + this.col = new Columns(settings); + this.columnsHandler = AuthMeColumnsHandler.createForMySql(this::getConnection, settings); + this.sqlExtension = extensionsFactory.buildExtension(col); + this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE); + this.maxLifetime = settings.getProperty(DatabaseSettings.MYSQL_CONNECTION_MAX_LIFETIME); + } + + /** + * Sets up the connection arguments to the database. + */ + private void setConnectionArguments() { + ds = new HikariDataSource(); + ds.setPoolName("AuthMePostgreSQLPool"); + + // Pool Settings + ds.setMaximumPoolSize(poolSize); + ds.setMaxLifetime(maxLifetime * 1000); + + // Database URL + ds.setDriverClassName("org.postgresql.Driver"); + ds.setJdbcUrl("jdbc:postgresql://" + this.host + ":" + this.port + "/" + this.database); + + // Auth + ds.setUsername(this.username); + ds.setPassword(this.password); + + // Random stuff + ds.addDataSourceProperty("reWriteBatchedInserts", "true"); + + // Caching + ds.addDataSourceProperty("cachePrepStmts", "true"); + ds.addDataSourceProperty("preparedStatementCacheQueries", "275"); + + ConsoleLogger.info("Connection arguments loaded, Hikari ConnectionPool ready!"); + } + + @Override + public void reload() { + if (ds != null) { + ds.close(); + } + setConnectionArguments(); + ConsoleLogger.info("Hikari ConnectionPool arguments reloaded!"); + } + + private Connection getConnection() throws SQLException { + return ds.getConnection(); + } + + /** + * Creates the table or any of its required columns if they don't exist. + */ + private void checkTablesAndColumns() throws SQLException { + try (Connection con = getConnection(); Statement st = con.createStatement()) { + // Create table with ID column if it doesn't exist + String sql = "CREATE TABLE IF NOT EXISTS " + tableName + " (" + + col.ID + " BIGSERIAL," + + "PRIMARY KEY (" + col.ID + ")" + + ");"; + st.executeUpdate(sql); + + DatabaseMetaData md = con.getMetaData(); + if (isColumnMissing(md, col.NAME)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + col.NAME + " VARCHAR(255) NOT NULL UNIQUE;"); + } + + if (isColumnMissing(md, col.REAL_NAME)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + col.REAL_NAME + " VARCHAR(255) NOT NULL;"); + } + + if (isColumnMissing(md, col.PASSWORD)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + col.PASSWORD + " VARCHAR(255) NOT NULL;"); + } + + if (!col.SALT.isEmpty() && isColumnMissing(md, col.SALT)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.SALT + " VARCHAR(255);"); + } + + if (isColumnMissing(md, col.LAST_IP)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + col.LAST_IP + " VARCHAR(40);"); + } else { + MySqlMigrater.migrateLastIpColumn(st, md, tableName, col); + } + + if (isColumnMissing(md, col.LAST_LOGIN)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + col.LAST_LOGIN + " BIGINT;"); + } else { + MySqlMigrater.migrateLastLoginColumn(st, md, tableName, col); + } + + if (isColumnMissing(md, col.REGISTRATION_DATE)) { + MySqlMigrater.addRegistrationDateColumn(st, tableName, col); + } + + if (isColumnMissing(md, col.REGISTRATION_IP)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + col.REGISTRATION_IP + " VARCHAR(40);"); + } + + if (isColumnMissing(md, col.LASTLOC_X)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.LASTLOC_X + " DOUBLE PRECISION NOT NULL DEFAULT '0.0' , ADD " + + col.LASTLOC_Y + " DOUBLE PRECISION NOT NULL DEFAULT '0.0' , ADD " + + col.LASTLOC_Z + " DOUBLE PRECISION NOT NULL DEFAULT '0.0';"); + } + + if (isColumnMissing(md, col.LASTLOC_WORLD)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.LASTLOC_WORLD + " VARCHAR(255) NOT NULL DEFAULT 'world';"); + } + + if (isColumnMissing(md, col.LASTLOC_YAW)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.LASTLOC_YAW + " FLOAT;"); + } + + if (isColumnMissing(md, col.LASTLOC_PITCH)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.LASTLOC_PITCH + " FLOAT;"); + } + + if (isColumnMissing(md, col.EMAIL)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.EMAIL + " VARCHAR(255);"); + } + + if (isColumnMissing(md, col.IS_LOGGED)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.IS_LOGGED + " SMALLINT NOT NULL DEFAULT '0';"); + } + + if (isColumnMissing(md, col.HAS_SESSION)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.HAS_SESSION + " SMALLINT NOT NULL DEFAULT '0';"); + } + + if (isColumnMissing(md, col.TOTP_KEY)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + col.TOTP_KEY + " VARCHAR(16);"); + } + } + ConsoleLogger.info("PostgreSQL setup finished"); + } + + private boolean isColumnMissing(DatabaseMetaData metaData, String columnName) throws SQLException { + try (ResultSet rs = metaData.getColumns(null, null, tableName, columnName.toLowerCase())) { + return !rs.next(); + } + } + + @Override + public PlayerAuth getAuth(String user) { + String sql = "SELECT * FROM " + tableName + " WHERE " + col.NAME + "=?;"; + PlayerAuth auth; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, user.toLowerCase()); + try (ResultSet rs = pst.executeQuery()) { + if (rs.next()) { + int id = rs.getInt(col.ID); + auth = buildAuthFromResultSet(rs); + sqlExtension.extendAuth(auth, id, con); + return auth; + } + } + } catch (SQLException ex) { + logSqlException(ex); + } + return null; + } + + @Override + public boolean saveAuth(PlayerAuth auth) { + super.saveAuth(auth); + + try (Connection con = getConnection()) { + if (!columnOthers.isEmpty()) { + for (String column : columnOthers) { + try (PreparedStatement pst = con.prepareStatement( + "UPDATE " + tableName + " SET " + column + "=? WHERE " + col.NAME + "=?;")) { + pst.setString(1, auth.getRealName()); + pst.setString(2, auth.getNickname()); + pst.executeUpdate(); + } + } + } + + sqlExtension.saveAuth(auth, con); + return true; + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + + @Override + public Set getRecordsToPurge(long until) { + Set list = new HashSet<>(); + String select = "SELECT " + col.NAME + " FROM " + tableName + " WHERE GREATEST(" + + " COALESCE(" + col.LAST_LOGIN + ", 0)," + + " COALESCE(" + col.REGISTRATION_DATE + ", 0)" + + ") < ?;"; + try (Connection con = getConnection(); + PreparedStatement selectPst = con.prepareStatement(select)) { + selectPst.setLong(1, until); + try (ResultSet rs = selectPst.executeQuery()) { + while (rs.next()) { + list.add(rs.getString(col.NAME)); + } + } + } catch (SQLException ex) { + logSqlException(ex); + } + + return list; + } + + @Override + public boolean removeAuth(String user) { + user = user.toLowerCase(); + String sql = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + sqlExtension.removeAuth(user, con); + pst.setString(1, user.toLowerCase()); + pst.executeUpdate(); + return true; + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + + @Override + public void closeConnection() { + if (ds != null && !ds.isClosed()) { + ds.close(); + } + } + + @Override + public void purgeRecords(Collection toPurge) { + String sql = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + for (String name : toPurge) { + pst.setString(1, name.toLowerCase()); + pst.executeUpdate(); + } + } catch (SQLException ex) { + logSqlException(ex); + } + } + + @Override + public DataSourceType getType() { + return DataSourceType.POSTGRESQL; + } + + @Override + public List getAllAuths() { + List auths = new ArrayList<>(); + try (Connection con = getConnection(); Statement st = con.createStatement()) { + try (ResultSet rs = st.executeQuery("SELECT * FROM " + tableName)) { + while (rs.next()) { + PlayerAuth auth = buildAuthFromResultSet(rs); + sqlExtension.extendAuth(auth, rs.getInt(col.ID), con); + auths.add(auth); + } + } + } catch (SQLException ex) { + logSqlException(ex); + } + return auths; + } + + @Override + public List getLoggedPlayersWithEmptyMail() { + List players = new ArrayList<>(); + String sql = "SELECT " + col.REAL_NAME + " FROM " + tableName + " WHERE " + col.IS_LOGGED + " = 1" + + " AND (" + col.EMAIL + " = 'your@email.com' OR " + col.EMAIL + " IS NULL);"; + try (Connection con = getConnection(); + Statement st = con.createStatement(); + ResultSet rs = st.executeQuery(sql)) { + while (rs.next()) { + players.add(rs.getString(1)); + } + } catch (SQLException ex) { + logSqlException(ex); + } + return players; + } + + @Override + public List getRecentlyLoggedInPlayers() { + List players = new ArrayList<>(); + String sql = "SELECT * FROM " + tableName + " ORDER BY " + col.LAST_LOGIN + " DESC LIMIT 10;"; + try (Connection con = getConnection(); + Statement st = con.createStatement(); + ResultSet rs = st.executeQuery(sql)) { + while (rs.next()) { + players.add(buildAuthFromResultSet(rs)); + } + } catch (SQLException e) { + logSqlException(e); + } + return players; + } + + @Override + public boolean setTotpKey(String user, String totpKey) { + String sql = "UPDATE " + tableName + " SET " + col.TOTP_KEY + " = ? WHERE " + col.NAME + " = ?"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, totpKey); + pst.setString(2, user.toLowerCase()); + pst.executeUpdate(); + return true; + } catch (SQLException e) { + logSqlException(e); + } + return false; + } + + /** + * Creates a {@link PlayerAuth} object with the data from the provided result set. + * + * @param row the result set to read from + * + * @return generated player auth object with the data from the result set + * + * @throws SQLException . + */ + private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException { + String salt = col.SALT.isEmpty() ? null : row.getString(col.SALT); + int group = col.GROUP.isEmpty() ? -1 : row.getInt(col.GROUP); + return PlayerAuth.builder() + .name(row.getString(col.NAME)) + .realName(row.getString(col.REAL_NAME)) + .password(row.getString(col.PASSWORD), salt) + .totpKey(row.getString(col.TOTP_KEY)) + .lastLogin(getNullableLong(row, col.LAST_LOGIN)) + .lastIp(row.getString(col.LAST_IP)) + .email(row.getString(col.EMAIL)) + .registrationDate(row.getLong(col.REGISTRATION_DATE)) + .registrationIp(row.getString(col.REGISTRATION_IP)) + .groupId(group) + .locWorld(row.getString(col.LASTLOC_WORLD)) + .locX(row.getDouble(col.LASTLOC_X)) + .locY(row.getDouble(col.LASTLOC_Y)) + .locZ(row.getDouble(col.LASTLOC_Z)) + .locYaw(row.getFloat(col.LASTLOC_YAW)) + .locPitch(row.getFloat(col.LASTLOC_PITCH)) + .build(); + } +} diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java index ff72a8018..93b7f48a6 100644 --- a/src/main/java/fr/xephi/authme/datasource/SQLite.java +++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java @@ -182,7 +182,11 @@ public class SQLite extends AbstractSqlDataSource { ConsoleLogger.info("SQLite Setup finished"); } - protected void migrateIfNeeded() throws SQLException { + /** + * Migrates the database if necessary. See {@link SqLiteMigrater} for details. + */ + @VisibleForTesting + void migrateIfNeeded() throws SQLException { DatabaseMetaData metaData = con.getMetaData(); if (SqLiteMigrater.isMigrationRequired(metaData, tableName, col)) { new SqLiteMigrater(settings, dataFolder).performMigration(this); diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtension.java b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtension.java index 84ab4c953..8d2a1ee94 100644 --- a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtension.java +++ b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtension.java @@ -35,7 +35,14 @@ class XfBcryptExtension extends MySqlExtension { updateXenforoTablesOnSave(auth, authId.getAsInt(), con); } } - + + /** + * Updates the xenforo tables after a player auth has been saved. + * + * @param auth the player auth which was saved + * @param id the account id + * @param con connection to the database + */ private void updateXenforoTablesOnSave(PlayerAuth auth, int id, Connection con) throws SQLException { // Insert player password, salt in xf_user_authenticate String sql = "INSERT INTO " + xfPrefix + "user_authenticate (user_id, scheme_class, data) VALUES (?,?,?)"; diff --git a/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java b/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java index 4a29e755d..f88bd5985 100644 --- a/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java +++ b/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java @@ -7,6 +7,7 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSourceType; import fr.xephi.authme.datasource.FlatFile; import fr.xephi.authme.datasource.MySQL; +import fr.xephi.authme.datasource.PostgreSqlDataSource; import fr.xephi.authme.datasource.SQLite; import fr.xephi.authme.datasource.converter.ForceFlatToSqlite; import fr.xephi.authme.datasource.mysqlextensions.MySqlExtensionsFactory; @@ -71,6 +72,9 @@ public class DataSourceProvider implements Provider { case MYSQL: dataSource = new MySQL(settings, mySqlExtensionsFactory); break; + case POSTGRESQL: + dataSource = new PostgreSqlDataSource(settings, mySqlExtensionsFactory); + break; case SQLITE: dataSource = new SQLite(settings, dataFolder); break; diff --git a/src/main/java/fr/xephi/authme/listener/EntityListener.java b/src/main/java/fr/xephi/authme/listener/EntityListener.java index ff6af1c65..7c8281861 100644 --- a/src/main/java/fr/xephi/authme/listener/EntityListener.java +++ b/src/main/java/fr/xephi/authme/listener/EntityListener.java @@ -1,7 +1,6 @@ package fr.xephi.authme.listener; import fr.xephi.authme.ConsoleLogger; - import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; diff --git a/src/main/java/fr/xephi/authme/listener/PlayerListener.java b/src/main/java/fr/xephi/authme/listener/PlayerListener.java index 0fbc1f902..3ddbb12ca 100644 --- a/src/main/java/fr/xephi/authme/listener/PlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/PlayerListener.java @@ -23,6 +23,7 @@ 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; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.entity.HumanEntity; @@ -226,6 +227,15 @@ public class PlayerListener implements Listener { public void onPlayerJoin(PlayerJoinEvent event) { final Player player = event.getPlayer(); + if (unresolvedPlayerHostname.remove(player.getName())) { + try { + runOnJoinChecks(JoiningPlayer.fromPlayerObject(player), PlayerUtils.getPlayerIp(player)); + } catch (FailedVerificationException e) { + player.kickPlayer(messages.retrieveSingle(player, e.getReason(), e.getArgs())); + return; + } + } + if (!PlayerListener19Spigot.isPlayerSpawnLocationEventCalled()) { teleportationService.teleportOnJoin(player); } @@ -266,15 +276,6 @@ public class PlayerListener implements Listener { return; } - // getAddress() sometimes returning null if not yet resolved - // skip it and let PlayerLoginEvent to handle it - if (event.getAddress() == null) { - unresolvedPlayerHostname.add(event.getName()); - return; - } else { - unresolvedPlayerHostname.remove(event.getName()); - } - final String name = event.getName(); if (validationService.isUnrestricted(name)) { @@ -292,6 +293,15 @@ public class PlayerListener implements Listener { ConsoleLogger.logException("Unable to load the permission data of user " + name, e); } + // getAddress() sometimes returning null if not yet resolved + // skip it and let PlayerLoginEvent to handle it + if (event.getAddress() == null) { + unresolvedPlayerHostname.add(event.getName()); + return; + } else { + unresolvedPlayerHostname.remove(event.getName()); + } + try { runOnJoinChecks(JoiningPlayer.fromName(name), event.getAddress().getHostAddress()); } catch (FailedVerificationException e) { @@ -320,9 +330,16 @@ public class PlayerListener implements Listener { } - if (!isAsyncPlayerPreLoginEventCalled || !settings.getProperty(PluginSettings.USE_ASYNC_PRE_LOGIN_EVENT) - || unresolvedPlayerHostname.remove(name)) { + if (event.getAddress() == null) { // Address still null + unresolvedPlayerHostname.add(name); + return; + } else { + unresolvedPlayerHostname.remove(name); + } + + if (!isAsyncPlayerPreLoginEventCalled || !settings.getProperty(PluginSettings.USE_ASYNC_PRE_LOGIN_EVENT)) { try { + // Player.getAddress() can be null at this event, use event.getAddress() runOnJoinChecks(JoiningPlayer.fromPlayerObject(player), event.getAddress().getHostAddress()); } catch (FailedVerificationException e) { event.setKickMessage(messages.retrieveSingle(player, e.getReason(), e.getArgs())); diff --git a/src/main/java/fr/xephi/authme/listener/ServerListener.java b/src/main/java/fr/xephi/authme/listener/ServerListener.java index 4aceaf6fe..8bebf3613 100644 --- a/src/main/java/fr/xephi/authme/listener/ServerListener.java +++ b/src/main/java/fr/xephi/authme/listener/ServerListener.java @@ -1,15 +1,16 @@ package fr.xephi.authme.listener; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.listener.protocollib.ProtocolLibService; import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.settings.SpawnLoader; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.event.server.PluginEnableEvent; + import javax.inject.Inject; /** diff --git a/src/main/java/fr/xephi/authme/listener/protocollib/ProtocolLibService.java b/src/main/java/fr/xephi/authme/listener/protocollib/ProtocolLibService.java index 43d6fed47..c6acd03ef 100644 --- a/src/main/java/fr/xephi/authme/listener/protocollib/ProtocolLibService.java +++ b/src/main/java/fr/xephi/authme/listener/protocollib/ProtocolLibService.java @@ -79,6 +79,9 @@ public class ProtocolLibService implements SettingsDependent { this.isEnabled = true; } + /** + * Stops all features based on ProtocolLib. + */ public void disable() { isEnabled = false; diff --git a/src/main/java/fr/xephi/authme/mail/OAuth2SaslClient.java b/src/main/java/fr/xephi/authme/mail/OAuth2SaslClient.java index f6e7a5f33..1f9b8c50c 100644 --- a/src/main/java/fr/xephi/authme/mail/OAuth2SaslClient.java +++ b/src/main/java/fr/xephi/authme/mail/OAuth2SaslClient.java @@ -14,13 +14,13 @@ */ package fr.xephi.authme.mail; -import java.io.IOException; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.sasl.SaslClient; import javax.security.sasl.SaslException; +import java.io.IOException; /** * An OAuth2 implementation of SaslClient. @@ -95,4 +95,4 @@ class OAuth2SaslClient implements SaslClient { public void dispose() throws SaslException { } -} \ No newline at end of file +} diff --git a/src/main/java/fr/xephi/authme/mail/OAuth2SaslClientFactory.java b/src/main/java/fr/xephi/authme/mail/OAuth2SaslClientFactory.java index 09a448059..a3ec879e9 100644 --- a/src/main/java/fr/xephi/authme/mail/OAuth2SaslClientFactory.java +++ b/src/main/java/fr/xephi/authme/mail/OAuth2SaslClientFactory.java @@ -15,12 +15,11 @@ */ package fr.xephi.authme.mail; -import java.util.Map; -import java.util.logging.Logger; - import javax.security.auth.callback.CallbackHandler; import javax.security.sasl.SaslClient; import javax.security.sasl.SaslClientFactory; +import java.util.Map; +import java.util.logging.Logger; /** * A SaslClientFactory that returns instances of OAuth2SaslClient. diff --git a/src/main/java/fr/xephi/authme/message/updater/JarMessageSource.java b/src/main/java/fr/xephi/authme/message/updater/JarMessageSource.java index 34c95a6ce..f1996f236 100644 --- a/src/main/java/fr/xephi/authme/message/updater/JarMessageSource.java +++ b/src/main/java/fr/xephi/authme/message/updater/JarMessageSource.java @@ -39,7 +39,7 @@ public class JarMessageSource { } private static String getString(String path, PropertyReader reader) { - return reader == null ? null : reader.getTypedObject(path, String.class); + return reader == null ? null : reader.getString(path); } private static MessageMigraterPropertyReader loadJarFile(String jarPath) { diff --git a/src/main/java/fr/xephi/authme/message/updater/MessageKeyConfigurationData.java b/src/main/java/fr/xephi/authme/message/updater/MessageKeyConfigurationData.java new file mode 100644 index 000000000..50f948f4b --- /dev/null +++ b/src/main/java/fr/xephi/authme/message/updater/MessageKeyConfigurationData.java @@ -0,0 +1,43 @@ +package fr.xephi.authme.message.updater; + +import ch.jalu.configme.configurationdata.ConfigurationDataImpl; +import ch.jalu.configme.properties.Property; +import ch.jalu.configme.resource.PropertyReader; +import fr.xephi.authme.message.MessageKey; + +import java.util.List; +import java.util.Map; + +public class MessageKeyConfigurationData extends ConfigurationDataImpl { + + /** + * Constructor. + * + * @param propertyListBuilder property list builder for message key properties + * @param allComments registered comments + */ + public MessageKeyConfigurationData(MessageUpdater.MessageKeyPropertyListBuilder propertyListBuilder, + Map> allComments) { + super(propertyListBuilder.getAllProperties(), allComments); + } + + @Override + public void initializeValues(PropertyReader reader) { + getAllMessageProperties().stream() + .filter(prop -> prop.isPresent(reader)) + .forEach(prop -> setValue(prop, prop.determineValue(reader))); + } + + @SuppressWarnings("unchecked") + public List> getAllMessageProperties() { + return (List) getProperties(); + } + + public String getMessage(MessageKey messageKey) { + return getValue(new MessageUpdater.MessageKeyProperty(messageKey)); + } + + public void setMessage(MessageKey messageKey, String message) { + setValue(new MessageUpdater.MessageKeyProperty(messageKey), message); + } +} diff --git a/src/main/java/fr/xephi/authme/message/updater/MessageMigraterPropertyReader.java b/src/main/java/fr/xephi/authme/message/updater/MessageMigraterPropertyReader.java index ab9f8d3b8..a41b53409 100644 --- a/src/main/java/fr/xephi/authme/message/updater/MessageMigraterPropertyReader.java +++ b/src/main/java/fr/xephi/authme/message/updater/MessageMigraterPropertyReader.java @@ -12,20 +12,19 @@ import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Objects; +import java.util.Set; /** * Implementation of {@link PropertyReader} which can read a file or a stream with * a specified charset. */ -public final class MessageMigraterPropertyReader implements PropertyReader { +final class MessageMigraterPropertyReader implements PropertyReader { - public static final Charset CHARSET = StandardCharsets.UTF_8; + private static final Charset CHARSET = StandardCharsets.UTF_8; private Map root; - /** See same field in {@link ch.jalu.configme.resource.YamlFileReader} for details. */ - private boolean hasObjectAsRoot = false; private MessageMigraterPropertyReader(Map valuesMap) { root = valuesMap; @@ -38,14 +37,11 @@ public final class MessageMigraterPropertyReader implements PropertyReader { * @return the created property reader */ public static MessageMigraterPropertyReader loadFromFile(File file) { - Map valuesMap; try (InputStream is = new FileInputStream(file)) { - valuesMap = readStreamToMap(is); + return loadFromStream(is); } catch (IOException e) { throw new IllegalStateException("Error while reading file '" + file + "'", e); } - - return new MessageMigraterPropertyReader(valuesMap); } public static MessageMigraterPropertyReader loadFromStream(InputStream inputStream) { @@ -53,10 +49,20 @@ public final class MessageMigraterPropertyReader implements PropertyReader { return new MessageMigraterPropertyReader(valuesMap); } + @Override + public boolean contains(String path) { + return getObject(path) != null; + } + + @Override + public Set getKeys(boolean b) { + throw new UnsupportedOperationException(); + } + @Override public Object getObject(String path) { if (path.isEmpty()) { - return hasObjectAsRoot ? root.get("") : root; + return root.get(""); } Object node = root; String[] keys = path.split("\\."); @@ -70,66 +76,29 @@ public final class MessageMigraterPropertyReader implements PropertyReader { } @Override - public T getTypedObject(String path, Class clazz) { - Object value = getObject(path); - if (clazz.isInstance(value)) { - return clazz.cast(value); - } - return null; + public String getString(String path) { + Object o = getObject(path); + return o instanceof String ? (String) o : null; } @Override - public void set(String path, Object value) { - Objects.requireNonNull(path); - - if (path.isEmpty()) { - root.clear(); - root.put("", value); - hasObjectAsRoot = true; - } else if (hasObjectAsRoot) { - throw new ConfigMeException("The root path is a bean property; you cannot set values to any subpath. " - + "Modify the bean at the root or set a new one instead."); - } else { - setValueInChildPath(path, value); - } - } - - /** - * Sets the value at the given path. This method is used when the root is a map and not a specific object. - * - * @param path the path to set the value at - * @param value the value to set - */ - @SuppressWarnings("unchecked") - private void setValueInChildPath(String path, Object value) { - Map node = root; - String[] keys = path.split("\\."); - for (int i = 0; i < keys.length - 1; ++i) { - Object child = node.get(keys[i]); - if (child instanceof Map) { - node = (Map) child; - } else { // child is null or some other value - replace with map - Map newEntry = new HashMap<>(); - node.put(keys[i], newEntry); - if (value == null) { - // For consistency, replace whatever value/null here with an empty map, - // but if the value is null our work here is done. - return; - } - node = newEntry; - } - } - // node now contains the parent map (existing or newly created) - if (value == null) { - node.remove(keys[keys.length - 1]); - } else { - node.put(keys[keys.length - 1], value); - } + public Integer getInt(String path) { + throw new UnsupportedOperationException(); } @Override - public void reload() { - throw new UnsupportedOperationException("Reload not supported by this implementation"); + public Double getDouble(String path) { + throw new UnsupportedOperationException(); + } + + @Override + public Boolean getBoolean(String path) { + throw new UnsupportedOperationException(); + } + + @Override + public List getList(String path) { + throw new UnsupportedOperationException(); } private static Map readStreamToMap(InputStream inputStream) { diff --git a/src/main/java/fr/xephi/authme/message/updater/MessageUpdater.java b/src/main/java/fr/xephi/authme/message/updater/MessageUpdater.java index 433f0b17b..895764f2b 100644 --- a/src/main/java/fr/xephi/authme/message/updater/MessageUpdater.java +++ b/src/main/java/fr/xephi/authme/message/updater/MessageUpdater.java @@ -1,10 +1,10 @@ package fr.xephi.authme.message.updater; -import ch.jalu.configme.SettingsManager; import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.configurationdata.PropertyListBuilder; import ch.jalu.configme.properties.Property; import ch.jalu.configme.properties.StringProperty; +import ch.jalu.configme.resource.PropertyReader; import ch.jalu.configme.resource.PropertyResource; import com.google.common.collect.ImmutableMap; import com.google.common.io.Files; @@ -20,21 +20,15 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; + +import static java.util.Collections.singletonList; /** * Migrates the used messages file to a complete, up-to-date version when necessary. */ public class MessageUpdater { - /** - * Configuration data object for all message keys incl. comments associated to sections. - */ - private static final ConfigurationData CONFIGURATION_DATA = buildConfigurationData(); - - public static ConfigurationData getConfigurationData() { - return CONFIGURATION_DATA; - } - /** * Applies any necessary migrations to the user's messages file and saves it if it has been modified. * @@ -57,52 +51,57 @@ public class MessageUpdater { */ private boolean migrateAndSave(File userFile, JarMessageSource jarMessageSource) { // YamlConfiguration escapes all special characters when saving, making the file hard to use, so use ConfigMe + MessageKeyConfigurationData configurationData = createConfigurationData(); PropertyResource userResource = new MigraterYamlFileResource(userFile); + PropertyReader reader = userResource.createReader(); + configurationData.initializeValues(reader); + // Step 1: Migrate any old keys in the file to the new paths - boolean movedOldKeys = migrateOldKeys(userResource); + boolean movedOldKeys = migrateOldKeys(reader, configurationData); // Step 2: Perform newer migrations - boolean movedNewerKeys = migrateKeys(userResource); + boolean movedNewerKeys = migrateKeys(reader, configurationData); // Step 3: Take any missing messages from the message files shipped in the AuthMe JAR - boolean addedMissingKeys = addMissingKeys(jarMessageSource, userResource); + boolean addedMissingKeys = addMissingKeys(jarMessageSource, configurationData); if (movedOldKeys || movedNewerKeys || addedMissingKeys) { backupMessagesFile(userFile); - SettingsManager settingsManager = new SettingsManager(userResource, null, CONFIGURATION_DATA); - settingsManager.save(); + userResource.exportProperties(configurationData); ConsoleLogger.debug("Successfully saved {0}", userFile); return true; } return false; } - private boolean migrateKeys(PropertyResource userResource) { - return moveIfApplicable(userResource, "misc.two_factor_create", MessageKey.TWO_FACTOR_CREATE.getKey()); + private boolean migrateKeys(PropertyReader propertyReader, MessageKeyConfigurationData configurationData) { + return moveIfApplicable(propertyReader, configurationData, + "misc.two_factor_create", MessageKey.TWO_FACTOR_CREATE); } - private static boolean moveIfApplicable(PropertyResource resource, String oldPath, String newPath) { - if (resource.getString(newPath) == null && resource.getString(oldPath) != null) { - resource.setValue(newPath, resource.getString(oldPath)); + private static boolean moveIfApplicable(PropertyReader reader, MessageKeyConfigurationData configurationData, + String oldPath, MessageKey messageKey) { + if (configurationData.getMessage(messageKey) == null && reader.getString(oldPath) != null) { + configurationData.setMessage(messageKey, reader.getString(oldPath)); return true; } return false; } - private boolean migrateOldKeys(PropertyResource userResource) { - boolean hasChange = OldMessageKeysMigrater.migrateOldPaths(userResource); + private boolean migrateOldKeys(PropertyReader propertyReader, MessageKeyConfigurationData configurationData) { + boolean hasChange = OldMessageKeysMigrater.migrateOldPaths(propertyReader, configurationData); if (hasChange) { ConsoleLogger.info("Old keys have been moved to the new ones in your messages_xx.yml file"); } return hasChange; } - private boolean addMissingKeys(JarMessageSource jarMessageSource, PropertyResource userResource) { + private boolean addMissingKeys(JarMessageSource jarMessageSource, MessageKeyConfigurationData configurationData) { List addedKeys = new ArrayList<>(); - for (Property property : CONFIGURATION_DATA.getProperties()) { + for (Property property : configurationData.getAllMessageProperties()) { final String key = property.getPath(); - if (userResource.getString(key) == null) { - userResource.setValue(key, jarMessageSource.getMessageFromJar(property)); + if (configurationData.getValue(property) == null) { + configurationData.setValue(property, jarMessageSource.getMessageFromJar(property)); addedKeys.add(key); } } @@ -129,40 +128,68 @@ public class MessageUpdater { * * @return the configuration data to export with */ - private static ConfigurationData buildConfigurationData() { - Map comments = ImmutableMap.builder() - .put("registration", new String[]{"Registration"}) - .put("password", new String[]{"Password errors on registration"}) - .put("login", new String[]{"Login"}) - .put("error", new String[]{"Errors"}) - .put("antibot", new String[]{"AntiBot"}) - .put("unregister", new String[]{"Unregister"}) - .put("misc", new String[]{"Other messages"}) - .put("session", new String[]{"Session messages"}) - .put("on_join_validation", new String[]{"Error messages when joining"}) - .put("email", new String[]{"Email"}) - .put("recovery", new String[]{"Password recovery by email"}) - .put("captcha", new String[]{"Captcha"}) - .put("verification", new String[]{"Verification code"}) - .put("time", new String[]{"Time units"}) - .put("two_factor", new String[]{"Two-factor authentication"}) + public static MessageKeyConfigurationData createConfigurationData() { + Map comments = ImmutableMap.builder() + .put("registration", "Registration") + .put("password", "Password errors on registration") + .put("login", "Login") + .put("error", "Errors") + .put("antibot", "AntiBot") + .put("unregister", "Unregister") + .put("misc", "Other messages") + .put("session", "Session messages") + .put("on_join_validation", "Error messages when joining") + .put("email", "Email") + .put("recovery", "Password recovery by email") + .put("captcha", "Captcha") + .put("verification", "Verification code") + .put("time", "Time units") + .put("two_factor", "Two-factor authentication") .build(); Set addedKeys = new HashSet<>(); - PropertyListBuilder builder = new PropertyListBuilder(); + MessageKeyPropertyListBuilder builder = new MessageKeyPropertyListBuilder(); // Add one key per section based on the comments map above so that the order is clear for (String path : comments.keySet()) { MessageKey key = Arrays.stream(MessageKey.values()).filter(p -> p.getKey().startsWith(path + ".")) .findFirst().orElseThrow(() -> new IllegalStateException(path)); - builder.add(new StringProperty(key.getKey(), "")); + builder.addMessageKey(key); addedKeys.add(key.getKey()); } // Add all remaining keys to the property list builder Arrays.stream(MessageKey.values()) .filter(key -> !addedKeys.contains(key.getKey())) - .forEach(key -> builder.add(new StringProperty(key.getKey(), ""))); + .forEach(builder::addMessageKey); - return new ConfigurationData(builder.create(), comments); + // Create ConfigurationData instance + Map> commentsMap = comments.entrySet().stream() + .collect(Collectors.toMap(e -> e.getKey(), e -> singletonList(e.getValue()))); + return new MessageKeyConfigurationData(builder, commentsMap); } + static final class MessageKeyProperty extends StringProperty { + + MessageKeyProperty(MessageKey messageKey) { + super(messageKey.getKey(), ""); + } + + @Override + protected String getFromReader(PropertyReader reader) { + return reader.getString(getPath()); + } + } + + static final class MessageKeyPropertyListBuilder { + + private PropertyListBuilder propertyListBuilder = new PropertyListBuilder(); + + void addMessageKey(MessageKey key) { + propertyListBuilder.add(new MessageKeyProperty(key)); + } + + @SuppressWarnings("unchecked") + List getAllProperties() { + return (List) propertyListBuilder.create(); + } + } } diff --git a/src/main/java/fr/xephi/authme/message/updater/MigraterYamlFileResource.java b/src/main/java/fr/xephi/authme/message/updater/MigraterYamlFileResource.java index 69b580ae1..c8b6755bf 100644 --- a/src/main/java/fr/xephi/authme/message/updater/MigraterYamlFileResource.java +++ b/src/main/java/fr/xephi/authme/message/updater/MigraterYamlFileResource.java @@ -1,41 +1,30 @@ package fr.xephi.authme.message.updater; -import ch.jalu.configme.beanmapper.leafproperties.LeafPropertiesGenerator; -import ch.jalu.configme.configurationdata.ConfigurationData; -import ch.jalu.configme.exception.ConfigMeException; -import ch.jalu.configme.properties.Property; -import ch.jalu.configme.resource.PropertyPathTraverser; +import ch.jalu.configme.resource.PropertyReader; import ch.jalu.configme.resource.YamlFileResource; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.util.List; - -import static fr.xephi.authme.message.updater.MessageMigraterPropertyReader.CHARSET; /** - * Extension of {@link YamlFileResource} to fine-tune the export style - * and to be able to specify the character encoding. + * Extension of {@link YamlFileResource} to fine-tune the export style. */ public class MigraterYamlFileResource extends YamlFileResource { - private static final String INDENTATION = " "; - - private final File file; private Yaml singleQuoteYaml; public MigraterYamlFileResource(File file) { - super(file, MessageMigraterPropertyReader.loadFromFile(file), new LeafPropertiesGenerator()); - this.file = file; + super(file); } @Override - protected Yaml getSingleQuoteYaml() { + public PropertyReader createReader() { + return MessageMigraterPropertyReader.loadFromFile(getFile()); + } + + @Override + protected Yaml createNewYaml() { if (singleQuoteYaml == null) { DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); @@ -47,57 +36,4 @@ public class MigraterYamlFileResource extends YamlFileResource { } return singleQuoteYaml; } - - @Override - public void exportProperties(ConfigurationData configurationData) { - try (FileOutputStream fos = new FileOutputStream(file); - OutputStreamWriter writer = new OutputStreamWriter(fos, CHARSET)) { - PropertyPathTraverser pathTraverser = new PropertyPathTraverser(configurationData); - for (Property property : convertPropertiesToExportableTypes(configurationData.getProperties())) { - - List pathElements = pathTraverser.getPathElements(property); - for (PropertyPathTraverser.PathElement pathElement : pathElements) { - writeComments(writer, pathElement.indentationLevel, pathElement.comments); - writer.append("\n") - .append(indent(pathElement.indentationLevel)) - .append(pathElement.name) - .append(":"); - } - - writer.append(" ") - .append(toYaml(property, pathElements.get(pathElements.size() - 1).indentationLevel)); - } - writer.flush(); - writer.close(); - } catch (IOException e) { - throw new ConfigMeException("Could not save config to '" + file.getPath() + "'", e); - } finally { - singleQuoteYaml = null; - } - } - - private void writeComments(Writer writer, int indentation, String[] comments) throws IOException { - if (comments.length == 0) { - return; - } - String commentStart = "\n" + indent(indentation) + "# "; - for (String comment : comments) { - writer.append(commentStart).append(comment); - } - } - - private String toYaml(Property property, int indent) { - Object value = property.getValue(this); - String representation = transformValue(property, value); - String[] lines = representation.split("\\n"); - return String.join("\n" + indent(indent), lines); - } - - private static String indent(int level) { - String result = ""; - for (int i = 0; i < level; i++) { - result += INDENTATION; - } - return result; - } } diff --git a/src/main/java/fr/xephi/authme/message/updater/OldMessageKeysMigrater.java b/src/main/java/fr/xephi/authme/message/updater/OldMessageKeysMigrater.java index c4e0c3143..739a449d3 100644 --- a/src/main/java/fr/xephi/authme/message/updater/OldMessageKeysMigrater.java +++ b/src/main/java/fr/xephi/authme/message/updater/OldMessageKeysMigrater.java @@ -1,6 +1,6 @@ package fr.xephi.authme.message.updater; -import ch.jalu.configme.resource.PropertyResource; +import ch.jalu.configme.resource.PropertyReader; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import fr.xephi.authme.message.MessageKey; @@ -16,7 +16,6 @@ import static com.google.common.collect.ImmutableMap.of; */ final class OldMessageKeysMigrater { - @VisibleForTesting static final Map KEYS_TO_OLD_PATH = ImmutableMap.builder() .put(MessageKey.LOGIN_SUCCESS, "login") @@ -130,23 +129,26 @@ final class OldMessageKeysMigrater { /** * Migrates any existing old key paths to their new paths if no text has been defined for the new key. * - * @param resource the resource to modify and read from + * @param reader the property reader to get values from + * @param configurationData the configuration data to write to * @return true if at least one message could be migrated, false otherwise */ - static boolean migrateOldPaths(PropertyResource resource) { + static boolean migrateOldPaths(PropertyReader reader, MessageKeyConfigurationData configurationData) { boolean wasPropertyMoved = false; for (Map.Entry migrationEntry : KEYS_TO_OLD_PATH.entrySet()) { - wasPropertyMoved |= moveIfApplicable(resource, migrationEntry.getKey(), migrationEntry.getValue()); + wasPropertyMoved |= moveIfApplicable(reader, configurationData, + migrationEntry.getKey(), migrationEntry.getValue()); } return wasPropertyMoved; } - private static boolean moveIfApplicable(PropertyResource resource, MessageKey messageKey, String oldPath) { - if (resource.getString(messageKey.getKey()) == null) { - String textAtOldPath = resource.getString(oldPath); + private static boolean moveIfApplicable(PropertyReader reader, MessageKeyConfigurationData configurationData, + MessageKey messageKey, String oldPath) { + if (configurationData.getMessage(messageKey) == null) { + String textAtOldPath = reader.getString(oldPath); if (textAtOldPath != null) { textAtOldPath = replaceOldPlaceholders(messageKey, textAtOldPath); - resource.setValue(messageKey.getKey(), textAtOldPath); + configurationData.setMessage(messageKey, textAtOldPath); return true; } } diff --git a/src/main/java/fr/xephi/authme/output/Log4JFilter.java b/src/main/java/fr/xephi/authme/output/Log4JFilter.java index 1d044e184..1ebf5141f 100644 --- a/src/main/java/fr/xephi/authme/output/Log4JFilter.java +++ b/src/main/java/fr/xephi/authme/output/Log4JFilter.java @@ -48,7 +48,7 @@ public class Log4JFilter extends AbstractFilter { @Override public Result filter(LogEvent event) { Message candidate = null; - if(event != null) { + if (event != null) { candidate = event.getMessage(); } return validateMessage(candidate); @@ -67,7 +67,7 @@ public class Log4JFilter extends AbstractFilter { @Override public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) { String candidate = null; - if(msg != null) { + if (msg != null) { candidate = msg.toString(); } return validateMessage(candidate); diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index ec1fff9bb..61cd31f9b 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -321,7 +321,7 @@ public class PermissionsManager implements Reloadable { * @param groupName The group name. * * @return True if the player is in the specified group, false otherwise. - * False is also returned if groups aren't supported by the used permissions system. + * False is also returned if groups aren't supported by the used permissions system. */ public boolean isInGroup(OfflinePlayer player, String groupName) { return isEnabled() && handler.isInGroup(player, groupName); @@ -334,7 +334,7 @@ public class PermissionsManager implements Reloadable { * @param groupName The name of the group. * * @return True if succeed, false otherwise. - * False is also returned if this feature isn't supported for the current permissions system. + * False is also returned if this feature isn't supported for the current permissions system. */ public boolean addGroup(OfflinePlayer player, String groupName) { if (!isEnabled() || StringUtils.isEmpty(groupName)) { @@ -350,7 +350,7 @@ public class PermissionsManager implements Reloadable { * @param groupNames The name of the groups to add. * * @return True if at least one group was added, false otherwise. - * False is also returned if this feature isn't supported for the current permissions system. + * False is also returned if this feature isn't supported for the current permissions system. */ public boolean addGroups(OfflinePlayer player, Collection groupNames) { // If no permissions system is used, return false @@ -377,7 +377,7 @@ public class PermissionsManager implements Reloadable { * @param groupName The name of the group. * * @return True if succeed, false otherwise. - * False is also returned if this feature isn't supported for the current permissions system. + * False is also returned if this feature isn't supported for the current permissions system. */ public boolean removeGroup(OfflinePlayer player, String groupName) { return isEnabled() && handler.removeFromGroup(player, groupName); @@ -390,7 +390,7 @@ public class PermissionsManager implements Reloadable { * @param groupNames The name of the groups to remove. * * @return True if at least one group was removed, false otherwise. - * False is also returned if this feature isn't supported for the current permissions system. + * False is also returned if this feature isn't supported for the current permissions system. */ public boolean removeGroups(OfflinePlayer player, Collection groupNames) { // If no permissions system is used, return false @@ -418,7 +418,7 @@ public class PermissionsManager implements Reloadable { * @param groupName The name of the group. * * @return True if succeed, false otherwise. - * False is also returned if this feature isn't supported for the current permissions system. + * False is also returned if this feature isn't supported for the current permissions system. */ public boolean setGroup(OfflinePlayer player, String groupName) { return isEnabled() && handler.setGroup(player, groupName); @@ -432,7 +432,7 @@ public class PermissionsManager implements Reloadable { * @param player The player to remove all groups from. * * @return True if succeed, false otherwise. - * False will also be returned if this feature isn't supported for the used permissions system. + * False will also be returned if this feature isn't supported for the used permissions system. */ public boolean removeAllGroups(OfflinePlayer player) { // If no permissions system is used, return false diff --git a/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java b/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java index 667b55d56..8feb0a78c 100644 --- a/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java +++ b/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java @@ -11,6 +11,11 @@ public enum PlayerStatePermission implements PermissionNode { */ BYPASS_ANTIBOT("authme.bypassantibot", DefaultPermission.OP_ONLY), + /** + * Permission node to bypass BungeeCord server teleportation. + */ + BYPASS_BUNGEE_SEND("authme.bypassbungeesend", DefaultPermission.OP_ONLY), + /** * Permission for users to bypass force-survival mode. */ diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java index 26a5da9e7..df141c6cd 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java @@ -35,16 +35,17 @@ public class AsyncChangeEmail implements AsynchronousProcess { @Inject private BungeeSender bungeeSender; - + @Inject private BukkitService bukkitService; - AsyncChangeEmail() { } + AsyncChangeEmail() { + } /** * Handles the request to change the player's email address. * - * @param player the player to change the email for + * @param player the player to change the email for * @param oldEmail provided old email * @param newEmail provided new email */ @@ -70,6 +71,14 @@ public class AsyncChangeEmail implements AsynchronousProcess { } } + /** + * Saves the new email value into the database and informs services. + * + * @param auth the player auth object + * @param player the player object + * @param oldEmail the old email value + * @param newEmail the new email value + */ private void saveNewEmail(PlayerAuth auth, Player player, String oldEmail, String newEmail) { EmailChangedEvent event = bukkitService.createAndCallEvent(isAsync -> new EmailChangedEvent(player, oldEmail, newEmail, isAsync)); diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java index c9aee5d33..29bbe720d 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java @@ -6,6 +6,8 @@ import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.LoginEvent; import fr.xephi.authme.events.RestoreInventoryEvent; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.CommonService; @@ -52,6 +54,9 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { @Inject private JoinMessageService joinMessageService; + @Inject + private PermissionsManager permissionsManager; + ProcessSyncPlayerLogin() { } @@ -106,7 +111,9 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { } commandManager.runCommandsOnLogin(player, authsWithSameIp); - // Send Bungee stuff. The service will check if it is enabled or not. - bungeeSender.connectPlayerOnLogin(player); + if (!permissionsManager.hasPermission(player, PlayerStatePermission.BYPASS_BUNGEE_SEND)) { + // Send Bungee stuff. The service will check if it is enabled or not. + bungeeSender.connectPlayerOnLogin(player); + } } } 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 7f52befe2..608267f00 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -66,7 +66,7 @@ public class AsyncRegister implements AsynchronousProcess { * Checks if the player is able to register, in that case the {@link AuthMeAsyncPreRegisterEvent} is invoked. * * @param variant the registration type variant. - * @param player the player which is trying to register. + * @param player the player which is trying to register. * * @return true if the checks are successful and the event hasn't marked the action as denied, false otherwise. */ diff --git a/src/main/java/fr/xephi/authme/security/HashUtils.java b/src/main/java/fr/xephi/authme/security/HashUtils.java index 642081c6d..e80880784 100644 --- a/src/main/java/fr/xephi/authme/security/HashUtils.java +++ b/src/main/java/fr/xephi/authme/security/HashUtils.java @@ -70,13 +70,13 @@ public final class HashUtils { /** * Return whether the given hash starts like a BCrypt hash. Checking with this method - * beforehand prevents the BcryptService from throwing certain exceptions. + * beforehand prevents the BCryptHasher from throwing certain exceptions. * * @param hash The salt to verify * @return True if the salt is valid, false otherwise */ public static boolean isValidBcryptHash(String hash) { - return hash.length() > 3 && hash.substring(0, 2).equals("$2"); + return hash.length() == 60 && hash.substring(0, 2).equals("$2"); } /** diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java b/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java index 8b454c799..5b75c89af 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java @@ -1,57 +1,23 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.security.HashUtils; -import fr.xephi.authme.security.crypts.description.HasSalt; -import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.SaltType; -import fr.xephi.authme.security.crypts.description.Usage; +import at.favre.lib.crypto.bcrypt.BCrypt.Version; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.HooksSettings; -import fr.xephi.authme.util.ExceptionUtils; import javax.inject.Inject; -@Recommendation(Usage.RECOMMENDED) // provided the salt length is >= 8 -@HasSalt(value = SaltType.TEXT) // length depends on the bcryptLog2Rounds setting -public class BCrypt implements EncryptionMethod { - - private final int bCryptLog2Rounds; +/** + * BCrypt hash algorithm with configurable cost factor. + */ +public class BCrypt extends BCryptBasedHash { @Inject public BCrypt(Settings settings) { - bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND); + super(createHasher(settings)); } - @Override - public String computeHash(String password, String salt, String name) { - return BCryptService.hashpw(password, salt); + private static BCryptHasher createHasher(Settings settings) { + int bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND); + return new BCryptHasher(Version.VERSION_2A, bCryptLog2Rounds); } - - @Override - public HashedPassword computeHash(String password, String name) { - String salt = generateSalt(); - return new HashedPassword(BCryptService.hashpw(password, salt), null); - } - - @Override - public boolean comparePassword(String password, HashedPassword hash, String name) { - try { - return HashUtils.isValidBcryptHash(hash.getHash()) && BCryptService.checkpw(password, hash.getHash()); - } catch (IllegalArgumentException e) { - ConsoleLogger.warning("Bcrypt checkpw() returned " + ExceptionUtils.formatException(e)); - } - return false; - } - - @Override - public String generateSalt() { - return BCryptService.gensalt(bCryptLog2Rounds); - } - - @Override - public boolean hasSeparateSalt() { - return false; - } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java b/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java index a22a68906..2558fa98a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java @@ -1,36 +1,20 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.crypts.description.Recommendation; -import fr.xephi.authme.security.crypts.description.Usage; +import at.favre.lib.crypto.bcrypt.BCrypt; -import static fr.xephi.authme.security.HashUtils.isEqual; +import javax.inject.Inject; -@Recommendation(Usage.RECOMMENDED) -public class BCrypt2y extends HexSaltedMethod { +/** + * Hash for BCrypt in the $2y$ variant. Uses a fixed cost factor of 10. + */ +public class BCrypt2y extends BCryptBasedHash { - @Override - public String computeHash(String password, String salt, String name) { - if (salt.length() == 22) { - salt = "$2y$10$" + salt; - } - return BCryptService.hashpw(password, salt); + @Inject + public BCrypt2y() { + this(10); } - @Override - public boolean comparePassword(String password, HashedPassword encrypted, String unusedName) { - String hash = encrypted.getHash(); - if (hash.length() != 60) { - return false; - } - // The salt is the first 29 characters of the hash - - String salt = hash.substring(0, 29); - return isEqual(hash, computeHash(password, salt, null)); + public BCrypt2y(int cost) { + super(new BCryptHasher(BCrypt.Version.VERSION_2Y, cost)); } - - @Override - public int getSaltLength() { - return 22; - } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCryptBasedHash.java b/src/main/java/fr/xephi/authme/security/crypts/BCryptBasedHash.java new file mode 100644 index 000000000..919c9bf2f --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/BCryptBasedHash.java @@ -0,0 +1,48 @@ +package fr.xephi.authme.security.crypts; + +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.Usage; + +import static fr.xephi.authme.security.crypts.BCryptHasher.SALT_LENGTH_ENCODED; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Abstract parent for BCrypt-based hash algorithms. + */ +@Recommendation(Usage.RECOMMENDED) +@HasSalt(value = SaltType.TEXT, length = SALT_LENGTH_ENCODED) +public abstract class BCryptBasedHash implements EncryptionMethod { + + private final BCryptHasher bCryptHasher; + + public BCryptBasedHash(BCryptHasher bCryptHasher) { + this.bCryptHasher = bCryptHasher; + } + + @Override + public HashedPassword computeHash(String password, String name) { + return bCryptHasher.hash(password); + } + + @Override + public String computeHash(String password, String salt, String name) { + return bCryptHasher.hashWithRawSalt(password, salt.getBytes(UTF_8)); + } + + @Override + public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { + return BCryptHasher.comparePassword(password, hashedPassword.getHash()); + } + + @Override + public String generateSalt() { + return BCryptHasher.generateSalt(); + } + + @Override + public boolean hasSeparateSalt() { + return false; + } +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCryptHasher.java b/src/main/java/fr/xephi/authme/security/crypts/BCryptHasher.java new file mode 100644 index 000000000..ffa1064dc --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/BCryptHasher.java @@ -0,0 +1,78 @@ +package fr.xephi.authme.security.crypts; + +import at.favre.lib.crypto.bcrypt.BCrypt; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.util.RandomStringUtils; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Wraps a {@link BCrypt.Hasher} instance and provides methods suitable for use in AuthMe. + */ +public class BCryptHasher { + + /** Number of bytes in a BCrypt salt (not encoded). */ + public static final int BYTES_IN_SALT = 16; + /** Number of characters of the salt in its radix64-encoded form. */ + public static final int SALT_LENGTH_ENCODED = 22; + + private final BCrypt.Hasher hasher; + private final int costFactor; + + /** + * Constructor. + * + * @param version the BCrypt version the instance should generate + * @param costFactor the log2 cost factor to use + */ + public BCryptHasher(BCrypt.Version version, int costFactor) { + this.hasher = BCrypt.with(version); + this.costFactor = costFactor; + } + + public HashedPassword hash(String password) { + byte[] hash = hasher.hash(costFactor, password.getBytes(UTF_8)); + return new HashedPassword(new String(hash, UTF_8)); + } + + public String hashWithRawSalt(String password, byte[] rawSalt) { + byte[] hash = hasher.hash(costFactor, rawSalt, password.getBytes(UTF_8)); + return new String(hash, UTF_8); + } + + /** + * Verifies that the given password is correct for the provided BCrypt hash. + * + * @param password the password to check with + * @param hash the hash to check against + * @return true if the password matches the hash, false otherwise + */ + public static boolean comparePassword(String password, String hash) { + if (HashUtils.isValidBcryptHash(hash)) { + BCrypt.Result result = BCrypt.verifyer().verify(password.getBytes(UTF_8), hash.getBytes(UTF_8)); + return result.verified; + } + return false; + } + + /** + * Generates a salt for usage in BCrypt. The returned salt is not yet encoded. + *

+ * Internally, the BCrypt library in {@link BCrypt.Hasher#hash(int, byte[])} uses the following: + * {@code Bytes.random(16, secureRandom).encodeUtf8();} + *

+ * Because our {@link EncryptionMethod} interface works with {@code String} types we need to make sure that the + * generated bytes in the salt are suitable for conversion into a String, such that calling String#getBytes will + * yield the same number of bytes again. Thus, we are forced to limit the range of characters we use. Ideally we'd + * only have to pass the salt in its encoded form so that we could make use of the entire "spectrum" of values, + * which proves difficult to achieve with the underlying BCrypt library. However, the salt needs to be generated + * manually only for testing purposes; production code should always hash passwords using + * {@link EncryptionMethod#computeHash(String, String)}, which internally may represent salts in more suitable + * formats. + * + * @return the salt for a BCrypt hash + */ + public static String generateSalt() { + return RandomStringUtils.generateLowerUpper(BYTES_IN_SALT); + } +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCryptService.java b/src/main/java/fr/xephi/authme/security/crypts/BCryptService.java deleted file mode 100644 index 6b82305c7..000000000 --- a/src/main/java/fr/xephi/authme/security/crypts/BCryptService.java +++ /dev/null @@ -1,779 +0,0 @@ -package fr.xephi.authme.security.crypts; - -// Copyright (c) 2006 Damien Miller -// -// Permission to use, copy, modify, and distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -import java.io.UnsupportedEncodingException; - -import java.security.SecureRandom; - -/** - * BCrypt implements OpenBSD-style Blowfish password hashing using - * the scheme described in "A Future-Adaptable Password Scheme" by - * Niels Provos and David Mazieres. - *

- * This password hashing system tries to thwart off-line password - * cracking using a computationally-intensive hashing algorithm, - * based on Bruce Schneier's Blowfish cipher. The work factor of - * the algorithm is parameterised, so it can be increased as - * computers get faster. - *

- * Usage is really simple. To hash a password for the first time, - * call the hashpw method with a random salt, like this: - *

- * - * String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
- *
- *

- * To check whether a plaintext password matches one that has been - * hashed previously, use the checkpw method: - *

- * - * if (BCrypt.checkpw(candidate_password, stored_hash))
- *     System.out.println("It matches");
- * else
- *     System.out.println("It does not match");
- *
- *

- * The gensalt() method takes an optional parameter (log_rounds) - * that determines the computational complexity of the hashing: - *

- * - * String strong_salt = BCrypt.gensalt(10)
- * String stronger_salt = BCrypt.gensalt(12)
- *
- *

- * The amount of work increases exponentially (2**log_rounds), so - * each increment is twice as much work. The default log_rounds is - * 10, and the valid range is 4 to 30. - * - * @author Damien Miller - * @version 0.4 - */ -public class BCryptService { - // BCrypt parameters - private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10; - private static final int BCRYPT_SALT_LEN = 16; - - // Blowfish parameters - private static final int BLOWFISH_NUM_ROUNDS = 16; - - // Initial contents of key schedule - private static final int P_orig[] = { - 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, - 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, - 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, - 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, - 0x9216d5d9, 0x8979fb1b - }; - private static final int S_orig[] = { - 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, - 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, - 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, - 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, - 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, - 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, - 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, - 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, - 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, - 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, - 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, - 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, - 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, - 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, - 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, - 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, - 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, - 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, - 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, - 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, - 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, - 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, - 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, - 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, - 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, - 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, - 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, - 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, - 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, - 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, - 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, - 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, - 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, - 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, - 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, - 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, - 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, - 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, - 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, - 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, - 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, - 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, - 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, - 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, - 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, - 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, - 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, - 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, - 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, - 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, - 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, - 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, - 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, - 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, - 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, - 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, - 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, - 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, - 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, - 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, - 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, - 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, - 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, - 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, - 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, - 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, - 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, - 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, - 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, - 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, - 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, - 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, - 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, - 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, - 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, - 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, - 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, - 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, - 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, - 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, - 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, - 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, - 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, - 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, - 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, - 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, - 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, - 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, - 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, - 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, - 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, - 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, - 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, - 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, - 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, - 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, - 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, - 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, - 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, - 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, - 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, - 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, - 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, - 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, - 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, - 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, - 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, - 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, - 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, - 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, - 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, - 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, - 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, - 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, - 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, - 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, - 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, - 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, - 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, - 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, - 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, - 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, - 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, - 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, - 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, - 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, - 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, - 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, - 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, - 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, - 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, - 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, - 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, - 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, - 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, - 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, - 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, - 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, - 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, - 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, - 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, - 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, - 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, - 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, - 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, - 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, - 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, - 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, - 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, - 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, - 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, - 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, - 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, - 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, - 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, - 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, - 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, - 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, - 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, - 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, - 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, - 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, - 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, - 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, - 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, - 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, - 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, - 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, - 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, - 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, - 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, - 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, - 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, - 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, - 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, - 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, - 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, - 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, - 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, - 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, - 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, - 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, - 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, - 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, - 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, - 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, - 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, - 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, - 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, - 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, - 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, - 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, - 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, - 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, - 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, - 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, - 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, - 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, - 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, - 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, - 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, - 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, - 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, - 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, - 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, - 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, - 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, - 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, - 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, - 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, - 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, - 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, - 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, - 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, - 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, - 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, - 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, - 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, - 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, - 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, - 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, - 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, - 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, - 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, - 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, - 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, - 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, - 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, - 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, - 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, - 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, - 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, - 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, - 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, - 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, - 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, - 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, - 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, - 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, - 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, - 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, - 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, - 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, - 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, - 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, - 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, - 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, - 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, - 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, - 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, - 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, - 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, - 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, - 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, - 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, - 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 - }; - - // bcrypt IV: "OrpheanBeholderScryDoubt". The C implementation calls - // this "ciphertext", but it is really plaintext or an IV. We keep - // the name to make code comparison easier. - static private final int bf_crypt_ciphertext[] = { - 0x4f727068, 0x65616e42, 0x65686f6c, - 0x64657253, 0x63727944, 0x6f756274 - }; - - // Table for Base64 encoding - static private final char base64_code[] = { - '.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', - 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', - 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9' - }; - - // Table for Base64 decoding - static private final byte index_64[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, 0, 1, 54, 55, - 56, 57, 58, 59, 60, 61, 62, 63, -1, -1, - -1, -1, -1, -1, -1, 2, 3, 4, 5, 6, - 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - -1, -1, -1, -1, -1, -1, 28, 29, 30, - 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, - 51, 52, 53, -1, -1, -1, -1, -1 - }; - - // Expanded Blowfish key - private int P[]; - private int S[]; - - /** - * Encode a byte array using bcrypt's slightly-modified base64 - * encoding scheme. Note that this is *not* compatible with - * the standard MIME-base64 encoding. - * - * @param d the byte array to encode - * @param len the number of bytes to encode - * @return base64-encoded string - * @exception IllegalArgumentException if the length is invalid - */ - private static String encode_base64(byte d[], int len) - throws IllegalArgumentException { - int off = 0; - StringBuilder rs = new StringBuilder(); - int c1, c2; - - if (len <= 0 || len > d.length) - throw new IllegalArgumentException ("Invalid len"); - - while (off < len) { - c1 = d[off++] & 0xff; - rs.append(base64_code[(c1 >> 2) & 0x3f]); - c1 = (c1 & 0x03) << 4; - if (off >= len) { - rs.append(base64_code[c1 & 0x3f]); - break; - } - c2 = d[off++] & 0xff; - c1 |= (c2 >> 4) & 0x0f; - rs.append(base64_code[c1 & 0x3f]); - c1 = (c2 & 0x0f) << 2; - if (off >= len) { - rs.append(base64_code[c1 & 0x3f]); - break; - } - c2 = d[off++] & 0xff; - c1 |= (c2 >> 6) & 0x03; - rs.append(base64_code[c1 & 0x3f]); - rs.append(base64_code[c2 & 0x3f]); - } - return rs.toString(); - } - - /** - * Look up the 3 bits base64-encoded by the specified character, - * range-checking againt conversion table - * @param x the base64-encoded value - * @return the decoded value of x - */ - private static byte char64(char x) { - if ((int)x < 0 || (int)x > index_64.length) - return -1; - return index_64[(int)x]; - } - - /** - * Decode a string encoded using bcrypt's base64 scheme to a - * byte array. Note that this is *not* compatible with - * the standard MIME-base64 encoding. - * @param s the string to decode - * @param maxolen the maximum number of bytes to decode - * @return an array containing the decoded bytes - * @throws IllegalArgumentException if maxolen is invalid - */ - private static byte[] decode_base64(String s, int maxolen) - throws IllegalArgumentException { - StringBuilder rs = new StringBuilder(); - int off = 0, slen = s.length(), olen = 0; - byte ret[]; - byte c1, c2, c3, c4, o; - - if (maxolen <= 0) - throw new IllegalArgumentException ("Invalid maxolen"); - - while (off < slen - 1 && olen < maxolen) { - c1 = char64(s.charAt(off++)); - c2 = char64(s.charAt(off++)); - if (c1 == -1 || c2 == -1) - break; - o = (byte)(c1 << 2); - o |= (c2 & 0x30) >> 4; - rs.append((char)o); - if (++olen >= maxolen || off >= slen) - break; - c3 = char64(s.charAt(off++)); - if (c3 == -1) - break; - o = (byte)((c2 & 0x0f) << 4); - o |= (c3 & 0x3c) >> 2; - rs.append((char)o); - if (++olen >= maxolen || off >= slen) - break; - c4 = char64(s.charAt(off++)); - o = (byte)((c3 & 0x03) << 6); - o |= c4; - rs.append((char)o); - ++olen; - } - - ret = new byte[olen]; - for (off = 0; off < olen; off++) - ret[off] = (byte)rs.charAt(off); - return ret; - } - - /** - * Blowfish encipher a single 64-bit block encoded as - * two 32-bit halves - * @param lr an array containing the two 32-bit half blocks - * @param off the position in the array of the blocks - */ - private void encipher(int lr[], int off) { - int i, n, l = lr[off], r = lr[off + 1]; - - l ^= P[0]; - for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) { - // Feistel substitution on left word - n = S[(l >> 24) & 0xff]; - n += S[0x100 | ((l >> 16) & 0xff)]; - n ^= S[0x200 | ((l >> 8) & 0xff)]; - n += S[0x300 | (l & 0xff)]; - r ^= n ^ P[++i]; - - // Feistel substitution on right word - n = S[(r >> 24) & 0xff]; - n += S[0x100 | ((r >> 16) & 0xff)]; - n ^= S[0x200 | ((r >> 8) & 0xff)]; - n += S[0x300 | (r & 0xff)]; - l ^= n ^ P[++i]; - } - lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1]; - lr[off + 1] = l; - } - - /** - * Cycically extract a word of key material - * @param data the string to extract the data from - * @param offp a "pointer" (as a one-entry array) to the - * current offset into data - * @return the next word of material from data - */ - private static int streamtoword(byte data[], int offp[]) { - int i; - int word = 0; - int off = offp[0]; - - for (i = 0; i < 4; i++) { - word = (word << 8) | (data[off] & 0xff); - off = (off + 1) % data.length; - } - - offp[0] = off; - return word; - } - - /** - * Initialise the Blowfish key schedule - */ - private void init_key() { - P = P_orig.clone(); - S = S_orig.clone(); - } - - /** - * Key the Blowfish cipher - * @param key an array containing the key - */ - private void key(byte key[]) { - int i; - int koffp[] = { 0 }; - int lr[] = { 0, 0 }; - int plen = P.length, slen = S.length; - - for (i = 0; i < plen; i++) - P[i] = P[i] ^ streamtoword(key, koffp); - - for (i = 0; i < plen; i += 2) { - encipher(lr, 0); - P[i] = lr[0]; - P[i + 1] = lr[1]; - } - - for (i = 0; i < slen; i += 2) { - encipher(lr, 0); - S[i] = lr[0]; - S[i + 1] = lr[1]; - } - } - - /** - * Perform the "enhanced key schedule" step described by - * Provos and Mazieres in "A Future-Adaptable Password Scheme" - * http://www.openbsd.org/papers/bcrypt-paper.ps - * @param data salt information - * @param key password information - */ - private void ekskey(byte data[], byte key[]) { - int i; - int koffp[] = { 0 }, doffp[] = { 0 }; - int lr[] = { 0, 0 }; - int plen = P.length, slen = S.length; - - for (i = 0; i < plen; i++) - P[i] = P[i] ^ streamtoword(key, koffp); - - for (i = 0; i < plen; i += 2) { - lr[0] ^= streamtoword(data, doffp); - lr[1] ^= streamtoword(data, doffp); - encipher(lr, 0); - P[i] = lr[0]; - P[i + 1] = lr[1]; - } - - for (i = 0; i < slen; i += 2) { - lr[0] ^= streamtoword(data, doffp); - lr[1] ^= streamtoword(data, doffp); - encipher(lr, 0); - S[i] = lr[0]; - S[i + 1] = lr[1]; - } - } - - /** - * Perform the central password hashing step in the - * bcrypt scheme - * @param password the password to hash - * @param salt the binary salt to hash with the password - * @param log_rounds the binary logarithm of the number - * of rounds of hashing to apply - * @param cdata the plaintext to encrypt - * @return an array containing the binary hashed password - */ - public byte[] crypt_raw(byte password[], byte salt[], int log_rounds, - int cdata[]) { - int rounds, i, j; - int clen = cdata.length; - byte ret[]; - - if (log_rounds < 4 || log_rounds > 30) - throw new IllegalArgumentException ("Bad number of rounds"); - rounds = 1 << log_rounds; - if (salt.length != BCRYPT_SALT_LEN) - throw new IllegalArgumentException ("Bad salt length"); - - init_key(); - ekskey(salt, password); - for (i = 0; i != rounds; i++) { - key(password); - key(salt); - } - - for (i = 0; i < 64; i++) { - for (j = 0; j < (clen >> 1); j++) - encipher(cdata, j << 1); - } - - ret = new byte[clen * 4]; - for (i = 0, j = 0; i < clen; i++) { - ret[j++] = (byte)((cdata[i] >> 24) & 0xff); - ret[j++] = (byte)((cdata[i] >> 16) & 0xff); - ret[j++] = (byte)((cdata[i] >> 8) & 0xff); - ret[j++] = (byte)(cdata[i] & 0xff); - } - return ret; - } - - /** - * Hash a password using the OpenBSD bcrypt scheme - * @param password the password to hash - * @param salt the salt to hash with (perhaps generated - * using BCrypt.gensalt) - * @return the hashed password - */ - public static String hashpw(String password, String salt) { - BCryptService B; - String real_salt; - byte passwordb[], saltb[], hashed[]; - char minor = (char)0; - int rounds, off; - StringBuilder rs = new StringBuilder(); - - if (salt.charAt(0) != '$' || salt.charAt(1) != '2') - throw new IllegalArgumentException ("Invalid salt version"); - if (salt.charAt(2) == '$') - off = 3; - else { - minor = salt.charAt(2); - // Note ljacqu 20160118: Added check to also allow minor version 'y' - // cf. https://security.stackexchange.com/questions/20541/insecure-versions-of-crypt-hashes - if ((minor != 'a' && minor != 'y') || salt.charAt(3) != '$') - throw new IllegalArgumentException ("Invalid salt revision"); - off = 4; - } - - // Extract number of rounds - if (salt.charAt(off + 2) > '$') - throw new IllegalArgumentException ("Missing salt rounds"); - rounds = Integer.parseInt(salt.substring(off, off + 2)); - - real_salt = salt.substring(off + 3, off + 25); - try { - passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8"); - } catch (UnsupportedEncodingException uee) { - throw new AssertionError("UTF-8 is not supported"); - } - - saltb = decode_base64(real_salt, BCRYPT_SALT_LEN); - - B = new BCryptService(); - hashed = B.crypt_raw(passwordb, saltb, rounds, bf_crypt_ciphertext.clone()); - - rs.append("$2"); - if (minor >= 'a') - rs.append(minor); - rs.append("$"); - if (rounds < 10) - rs.append("0"); - if (rounds > 30) { - throw new IllegalArgumentException( - "rounds exceeds maximum (30)"); - } - rs.append(Integer.toString(rounds)); - rs.append("$"); - rs.append(encode_base64(saltb, saltb.length)); - rs.append(encode_base64(hashed, - bf_crypt_ciphertext.length * 4 - 1)); - return rs.toString(); - } - - /** - * Generate a salt for use with the BCrypt.hashpw() method - * @param log_rounds the log2 of the number of rounds of - * hashing to apply - the work factor therefore increases as - * 2**log_rounds. - * @param random an instance of SecureRandom to use - * @return an encoded salt value - */ - public static String gensalt(int log_rounds, SecureRandom random) { - StringBuilder rs = new StringBuilder(); - byte rnd[] = new byte[BCRYPT_SALT_LEN]; - - random.nextBytes(rnd); - - rs.append("$2a$"); - if (log_rounds < 10) - rs.append("0"); - if (log_rounds > 30) { - throw new IllegalArgumentException( - "log_rounds exceeds maximum (30)"); - } - rs.append(Integer.toString(log_rounds)); - rs.append("$"); - rs.append(encode_base64(rnd, rnd.length)); - return rs.toString(); - } - - /** - * Generate a salt for use with the BCrypt.hashpw() method - * @param log_rounds the log2 of the number of rounds of - * hashing to apply - the work factor therefore increases as - * 2**log_rounds. - * @return an encoded salt value - */ - public static String gensalt(int log_rounds) { - return gensalt(log_rounds, new SecureRandom()); - } - - /** - * Generate a salt for use with the BCrypt.hashpw() method, - * selecting a reasonable default for the number of hashing - * rounds to apply - * @return an encoded salt value - */ - public static String gensalt() { - return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS); - } - - /** - * Check that a plaintext password matches a previously hashed - * one - * @param plaintext the plaintext password to verify - * @param hashed the previously-hashed password - * @return true if the passwords match, false otherwise - */ - public static boolean checkpw(String plaintext, String hashed) { - byte hashed_bytes[]; - byte try_bytes[]; - try { - String try_pw = hashpw(plaintext, hashed); - hashed_bytes = hashed.getBytes("UTF-8"); - try_bytes = try_pw.getBytes("UTF-8"); - } catch (UnsupportedEncodingException uee) { - return false; - } - if (hashed_bytes.length != try_bytes.length) - return false; - byte ret = 0; - for (int i = 0; i < try_bytes.length; i++) - ret |= hashed_bytes[i] ^ try_bytes[i]; - return ret == 0; - } -} diff --git a/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java index 5ac8caf8b..81e767722 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java @@ -1,10 +1,10 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; +import fr.xephi.authme.util.RandomStringUtils; /** * Common type for encryption methods which use a random String of hexadecimal characters diff --git a/src/main/java/fr/xephi/authme/security/crypts/Ipb3.java b/src/main/java/fr/xephi/authme/security/crypts/Ipb3.java index 15dcd189b..b29df4fc5 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Ipb3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Ipb3.java @@ -1,10 +1,10 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; +import fr.xephi.authme.util.RandomStringUtils; import static fr.xephi.authme.security.HashUtils.md5; diff --git a/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java b/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java index c7bfcd65b..e176d0aab 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java @@ -1,45 +1,58 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.security.HashUtils; +import at.favre.lib.crypto.bcrypt.BCrypt; +import at.favre.lib.crypto.bcrypt.IllegalBCryptFormatException; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; -import fr.xephi.authme.util.ExceptionUtils; import fr.xephi.authme.util.RandomStringUtils; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * Implementation for Ipb4 (Invision Power Board 4). *

* The hash uses standard BCrypt with 13 as log2 number of rounds. Additionally, - * Ipb4 requires that the salt be stored additionally in the column "members_pass_hash" - * (even though BCrypt hashes already have the salt in the result). + * Ipb4 requires that the salt be stored in the column "members_pass_hash" as well + * (even though BCrypt hashes already contain the salt within themselves). */ @Recommendation(Usage.DOES_NOT_WORK) -@HasSalt(value = SaltType.TEXT, length = 22) +@HasSalt(value = SaltType.TEXT, length = BCryptHasher.SALT_LENGTH_ENCODED) public class Ipb4 implements EncryptionMethod { + private BCryptHasher bCryptHasher = new BCryptHasher(BCrypt.Version.VERSION_2A, 13); + @Override public String computeHash(String password, String salt, String name) { - return BCryptService.hashpw(password, "$2a$13$" + salt); + // Since the radix64-encoded salt is necessary to be stored separately as well, the incoming salt here is + // radix64-encoded (see #generateSalt()). This means we first need to decode it before passing into the + // bcrypt hasher... We cheat by inserting the encoded salt into a dummy bcrypt hash so that we can parse it + // with the BCrypt utilities. + // This method (with specific salt) is only used for testing purposes, so this approach should be OK. + + String dummyHash = "$2a$10$" + salt + "3Cfb5GnwvKhJ20r.hMjmcNkIT9.Uh9K"; + try { + BCrypt.HashData parseResult = BCrypt.Version.VERSION_2A.parser.parse(dummyHash.getBytes(UTF_8)); + return bCryptHasher.hashWithRawSalt(password, parseResult.rawSalt); + } catch (IllegalBCryptFormatException |IllegalArgumentException e) { + throw new IllegalStateException("Cannot parse hash with salt '" + salt + "'", e); + } } @Override public HashedPassword computeHash(String password, String name) { - String salt = generateSalt(); - return new HashedPassword(computeHash(password, salt, name), salt); + HashedPassword hash = bCryptHasher.hash(password); + + // 7 chars prefix, then 22 chars which is the encoded salt, which we need again + String salt = hash.getHash().substring(7, 29); + return new HashedPassword(hash.getHash(), salt); } @Override - public boolean comparePassword(String password, HashedPassword hash, String name) { - try { - return HashUtils.isValidBcryptHash(hash.getHash()) && BCryptService.checkpw(password, hash.getHash()); - } catch (IllegalArgumentException e) { - ConsoleLogger.warning("Bcrypt checkpw() returned " + ExceptionUtils.formatException(e)); - } - return false; + public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { + return BCryptHasher.comparePassword(password, hashedPassword.getHash()); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/MyBB.java b/src/main/java/fr/xephi/authme/security/crypts/MyBB.java index 3f0a477ab..3ff0ee5e0 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MyBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MyBB.java @@ -1,15 +1,16 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; +import fr.xephi.authme.util.RandomStringUtils; import static fr.xephi.authme.security.HashUtils.md5; @Recommendation(Usage.ACCEPTABLE) @HasSalt(value = SaltType.TEXT, length = 8) +@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) public class MyBB extends SeparateSaltMethod { @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java b/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java index 2d641706c..c4e27e9d7 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java @@ -11,6 +11,7 @@ import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import static fr.xephi.authme.security.HashUtils.isEqual; +import static fr.xephi.authme.security.crypts.BCryptHasher.SALT_LENGTH_ENCODED; /** * Encryption method compatible with phpBB3. @@ -21,15 +22,14 @@ import static fr.xephi.authme.security.HashUtils.isEqual; * as well as plain MD5. */ @Recommendation(Usage.ACCEPTABLE) -@HasSalt(value = SaltType.TEXT, length = 22) +@HasSalt(value = SaltType.TEXT, length = SALT_LENGTH_ENCODED) public class PhpBB implements EncryptionMethod { private final BCrypt2y bCrypt2y = new BCrypt2y(); @Override public HashedPassword computeHash(String password, String name) { - String salt = generateSalt(); - return new HashedPassword(BCryptService.hashpw(password, salt)); + return bCrypt2y.computeHash(password, name); } @Override @@ -52,7 +52,8 @@ public class PhpBB implements EncryptionMethod { @Override public String generateSalt() { // Salt length 22, as seen in https://github.com/phpbb/phpbb/blob/master/phpBB/phpbb/passwords/driver/bcrypt.php - return BCryptService.gensalt(10); + // Ours generates 16 chars because the salt must not yet be encoded. + return BCryptHasher.generateSalt(); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/PhpFusion.java b/src/main/java/fr/xephi/authme/security/crypts/PhpFusion.java index 5a49ed4ce..b97441171 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PhpFusion.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PhpFusion.java @@ -1,10 +1,10 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.security.crypts.description.AsciiRestricted; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; +import fr.xephi.authme.util.RandomStringUtils; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; diff --git a/src/main/java/fr/xephi/authme/security/crypts/Salted2Md5.java b/src/main/java/fr/xephi/authme/security/crypts/Salted2Md5.java index 6d708b3ce..3d2166a75 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Salted2Md5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Salted2Md5.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.RandomStringUtils; import javax.inject.Inject; diff --git a/src/main/java/fr/xephi/authme/security/crypts/SaltedSha512.java b/src/main/java/fr/xephi/authme/security/crypts/SaltedSha512.java index b5660d658..b5578b93b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SaltedSha512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SaltedSha512.java @@ -1,9 +1,9 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; +import fr.xephi.authme.util.RandomStringUtils; @Recommendation(Usage.RECOMMENDED) public class SaltedSha512 extends SeparateSaltMethod { diff --git a/src/main/java/fr/xephi/authme/security/crypts/Wbb3.java b/src/main/java/fr/xephi/authme/security/crypts/Wbb3.java index 0a042b489..8e26463bb 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Wbb3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Wbb3.java @@ -1,10 +1,10 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; +import fr.xephi.authme.util.RandomStringUtils; import static fr.xephi.authme.security.HashUtils.sha1; diff --git a/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java b/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java index f396c5d84..9eeec4426 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java @@ -1,41 +1,72 @@ package fr.xephi.authme.security.crypts; +import at.favre.lib.crypto.bcrypt.BCrypt; +import at.favre.lib.crypto.bcrypt.IllegalBCryptFormatException; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; +import java.security.SecureRandom; + import static fr.xephi.authme.security.HashUtils.isEqual; -import static fr.xephi.authme.security.crypts.BCryptService.hashpw; +import static fr.xephi.authme.security.crypts.BCryptHasher.BYTES_IN_SALT; +import static fr.xephi.authme.security.crypts.BCryptHasher.SALT_LENGTH_ENCODED; +import static java.nio.charset.StandardCharsets.UTF_8; @Recommendation(Usage.RECOMMENDED) -public class Wbb4 extends HexSaltedMethod { +@HasSalt(value = SaltType.TEXT, length = SALT_LENGTH_ENCODED) +public class Wbb4 implements EncryptionMethod { + + private BCryptHasher bCryptHasher = new BCryptHasher(BCrypt.Version.VERSION_2A, 8); + private SecureRandom random = new SecureRandom(); + + @Override + public HashedPassword computeHash(String password, String name) { + byte[] salt = new byte[BYTES_IN_SALT]; + random.nextBytes(salt); + + String hash = hashInternal(password, salt); + return new HashedPassword(hash); + } @Override public String computeHash(String password, String salt, String name) { - return hashpw(hashpw(password, salt), salt); + return hashInternal(password, salt.getBytes(UTF_8)); } @Override public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { - if (hashedPassword.getHash().length() != 60) { - return false; + try { + BCrypt.HashData hashData = BCrypt.Version.VERSION_2A.parser.parse(hashedPassword.getHash().getBytes(UTF_8)); + byte[] salt = hashData.rawSalt; + String computedHash = hashInternal(password, salt); + return isEqual(hashedPassword.getHash(), computedHash); + } catch (IllegalBCryptFormatException | IllegalArgumentException e) { + ConsoleLogger.logException("Invalid WBB4 hash:", e); } - String salt = hashedPassword.getHash().substring(0, 29); - return isEqual(hashedPassword.getHash(), computeHash(password, salt, name)); + return false; + } + + /** + * Hashes the given password with the provided salt twice: hash(hash(password, salt), salt). + * + * @param password the password to hash + * @param rawSalt the salt to use + * @return WBB4-compatible hash + */ + private String hashInternal(String password, byte[] rawSalt) { + return bCryptHasher.hashWithRawSalt(bCryptHasher.hashWithRawSalt(password, rawSalt), rawSalt); } @Override public String generateSalt() { - return BCryptService.gensalt(8); + return BCryptHasher.generateSalt(); } - /** - * Note that {@link #generateSalt()} is overridden for this class. - * - * @return The salt length - */ @Override - public int getSaltLength() { - return 8; + public boolean hasSeparateSalt() { + return false; } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java b/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java index 846807e6c..749b5f57f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java @@ -1,45 +1,17 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.security.HashUtils; -import fr.xephi.authme.util.ExceptionUtils; +import at.favre.lib.crypto.bcrypt.BCrypt; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class XfBCrypt implements EncryptionMethod { +public class XfBCrypt extends BCryptBasedHash { + public static final String SCHEME_CLASS = "XenForo_Authentication_Core12"; private static final Pattern HASH_PATTERN = Pattern.compile("\"hash\";s.*\"(.*)?\""); - @Override - public String generateSalt() { - return BCryptService.gensalt(); - } - - @Override - public String computeHash(String password, String salt, String name) { - return BCryptService.hashpw(password, salt); - } - - @Override - public HashedPassword computeHash(String password, String name) { - String salt = generateSalt(); - return new HashedPassword(BCryptService.hashpw(password, salt), null); - } - - @Override - public boolean comparePassword(String password, HashedPassword hash, String salt) { - try { - return HashUtils.isValidBcryptHash(hash.getHash()) && BCryptService.checkpw(password, hash.getHash()); - } catch (IllegalArgumentException e) { - ConsoleLogger.warning("XfBCrypt checkpw() returned " + ExceptionUtils.formatException(e)); - } - return false; - } - - @Override - public boolean hasSeparateSalt() { - return false; + XfBCrypt() { + super(new BCryptHasher(BCrypt.Version.VERSION_2A, 10)); } /** diff --git a/src/main/java/fr/xephi/authme/service/BackupService.java b/src/main/java/fr/xephi/authme/service/BackupService.java index b85002eaa..6ae65a061 100644 --- a/src/main/java/fr/xephi/authme/service/BackupService.java +++ b/src/main/java/fr/xephi/authme/service/BackupService.java @@ -97,6 +97,11 @@ public class BackupService { return false; } + /** + * Performs a backup for the MySQL data source. + * + * @return true if successful, false otherwise + */ private boolean performMySqlBackup() { FileUtils.createDirectory(backupFolder); File sqlBackupFile = constructBackupFile("sql"); diff --git a/src/main/java/fr/xephi/authme/service/GeoIpService.java b/src/main/java/fr/xephi/authme/service/GeoIpService.java index be2746878..804b82a15 100644 --- a/src/main/java/fr/xephi/authme/service/GeoIpService.java +++ b/src/main/java/fr/xephi/authme/service/GeoIpService.java @@ -13,12 +13,12 @@ import com.maxmind.db.Reader.FileMode; import com.maxmind.db.cache.CHMCache; import com.maxmind.db.model.Country; import com.maxmind.db.model.CountryResponse; - import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.util.FileUtils; import fr.xephi.authme.util.InternetProtocolUtils; +import javax.inject.Inject; import java.io.BufferedInputStream; import java.io.File; import java.io.FileNotFoundException; @@ -41,8 +41,6 @@ import java.util.Objects; import java.util.Optional; import java.util.zip.GZIPInputStream; -import javax.inject.Inject; - public class GeoIpService { private static final String LICENSE = @@ -228,8 +226,8 @@ public class GeoIpService { HashCode actualHash = function.hashBytes(Files.readAllBytes(file)); HashCode expectedHash = HashCode.fromString(expectedChecksum); if (!Objects.equals(actualHash, expectedHash)) { - throw new IOException("GEO IP Checksum verification failed. " + - "Expected: " + expectedChecksum + "Actual:" + actualHash); + throw new IOException("GEO IP Checksum verification failed. " + + "Expected: " + expectedChecksum + "Actual:" + actualHash); } } @@ -268,9 +266,13 @@ public class GeoIpService { * Get the country code of the given IP address. * * @param ip textual IP address to lookup. - * @return two-character ISO 3166-1 alpha code for the country or "--" if it cannot be fetched. + * @return two-character ISO 3166-1 alpha code for the country, "LOCALHOST" for local addresses + * or "--" if it cannot be fetched. */ public String getCountryCode(String ip) { + if (InternetProtocolUtils.isLocalAddress(ip)) { + return "LOCALHOST"; + } return getCountry(ip).map(Country::getIsoCode).orElse("--"); } @@ -278,9 +280,12 @@ public class GeoIpService { * Get the country name of the given IP address. * * @param ip textual IP address to lookup. - * @return The name of the country or "N/A" if it cannot be fetched. + * @return The name of the country, "LocalHost" for local addresses, or "N/A" if it cannot be fetched. */ public String getCountryName(String ip) { + if (InternetProtocolUtils.isLocalAddress(ip)) { + return "LocalHost"; + } return getCountry(ip).map(Country::getName).orElse("N/A"); } @@ -297,7 +302,7 @@ public class GeoIpService { * */ private Optional getCountry(String ip) { - if (ip == null || ip.isEmpty() || InternetProtocolUtils.isLocalAddress(ip) || !isDataAvailable()) { + if (ip == null || ip.isEmpty() || !isDataAvailable()) { return Optional.empty(); } diff --git a/src/main/java/fr/xephi/authme/service/PluginHookService.java b/src/main/java/fr/xephi/authme/service/PluginHookService.java index 0a204aa0e..ab344fac3 100644 --- a/src/main/java/fr/xephi/authme/service/PluginHookService.java +++ b/src/main/java/fr/xephi/authme/service/PluginHookService.java @@ -69,7 +69,7 @@ public class PluginHookService { */ public File getCmiDataFolder() { Plugin plugin = pluginManager.getPlugin("CMI"); - if(plugin == null) { + if (plugin == null) { return null; } return plugin.getDataFolder(); diff --git a/src/main/java/fr/xephi/authme/service/ValidationService.java b/src/main/java/fr/xephi/authme/service/ValidationService.java index 840b9c4c9..2583cdf77 100644 --- a/src/main/java/fr/xephi/authme/service/ValidationService.java +++ b/src/main/java/fr/xephi/authme/service/ValidationService.java @@ -23,7 +23,6 @@ import org.bukkit.entity.Player; import javax.annotation.PostConstruct; import javax.inject.Inject; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; @@ -45,7 +44,6 @@ public class ValidationService implements Reloadable { private GeoIpService geoIpService; private Pattern passwordRegex; - private Set unrestrictedNames; private Multimap restrictedNames; ValidationService() { @@ -55,8 +53,6 @@ public class ValidationService implements Reloadable { @Override public void reload() { passwordRegex = Utils.safePatternCompile(settings.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX)); - // Use Set for more efficient contains() lookup - unrestrictedNames = new HashSet<>(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)); restrictedNames = settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS) ? loadNameRestrictions(settings.getProperty(RestrictionSettings.RESTRICTED_USERS)) : HashMultimap.create(); @@ -140,7 +136,7 @@ public class ValidationService implements Reloadable { * @return true if unrestricted, false otherwise */ public boolean isUnrestricted(String name) { - return unrestrictedNames.contains(name.toLowerCase()); + return settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES).contains(name.toLowerCase()); } /** @@ -157,16 +153,16 @@ public class ValidationService implements Reloadable { String ip = PlayerUtils.getPlayerIp(player); String domain = player.getAddress().getHostName(); - for(String restriction : restrictions) { - if(restriction.startsWith("regex:")) { + for (String restriction : restrictions) { + if (restriction.startsWith("regex:")) { restriction = restriction.replace("regex:", ""); } else { - restriction = restriction.replaceAll("\\*","(.*)"); + restriction = restriction.replace("*", "(.*)"); } - if(ip.matches(restriction)) { + if (ip.matches(restriction)) { return true; } - if(domain.matches(restriction)) { + if (domain.matches(restriction)) { return true; } } @@ -208,7 +204,7 @@ public class ValidationService implements Reloadable { * @param configuredRestrictions the restriction rules to convert to a map * @return map of allowed IPs/domain names by player name */ - private Multimap loadNameRestrictions(List configuredRestrictions) { + private Multimap loadNameRestrictions(Set configuredRestrictions) { Multimap restrictions = HashMultimap.create(); for (String restriction : configuredRestrictions) { if (isInsideString(';', restriction)) { diff --git a/src/main/java/fr/xephi/authme/service/bungeecord/BungeeReceiver.java b/src/main/java/fr/xephi/authme/service/bungeecord/BungeeReceiver.java index 0cc112aa9..c2b36588f 100644 --- a/src/main/java/fr/xephi/authme/service/bungeecord/BungeeReceiver.java +++ b/src/main/java/fr/xephi/authme/service/bungeecord/BungeeReceiver.java @@ -15,6 +15,7 @@ import org.bukkit.plugin.messaging.Messenger; import org.bukkit.plugin.messaging.PluginMessageListener; import javax.inject.Inject; +import java.util.Optional; public class BungeeReceiver implements PluginMessageListener, SettingsDependent { @@ -26,8 +27,8 @@ public class BungeeReceiver implements PluginMessageListener, SettingsDependent private boolean isEnabled; @Inject - BungeeReceiver(AuthMe plugin, BukkitService bukkitService, Management management, DataSource dataSource, - Settings settings) { + BungeeReceiver(final AuthMe plugin, final BukkitService bukkitService, final Management management, + final DataSource dataSource, final Settings settings) { this.plugin = plugin; this.bukkitService = bukkitService; this.management = management; @@ -36,57 +37,106 @@ public class BungeeReceiver implements PluginMessageListener, SettingsDependent } @Override - public void reload(Settings settings) { + public void reload(final Settings settings) { this.isEnabled = settings.getProperty(HooksSettings.BUNGEECORD); if (this.isEnabled) { - Messenger messenger = plugin.getServer().getMessenger(); + final Messenger messenger = plugin.getServer().getMessenger(); if (!messenger.isIncomingChannelRegistered(plugin, "BungeeCord")) { messenger.registerIncomingPluginChannel(plugin, "BungeeCord", this); } } } + private void handleBroadcast(final ByteArrayDataInput in) { + // Read data byte array + final short dataLength = in.readShort(); + final byte[] dataBytes = new byte[dataLength]; + in.readFully(dataBytes); + final ByteArrayDataInput dataIn = ByteStreams.newDataInput(dataBytes); + + // Parse type + final Optional type = MessageType.fromId(dataIn.readUTF()); + if (!type.isPresent()) { + ConsoleLogger.debug("Received unsupported forwarded bungeecord message type! ({0})", type); + return; + } + + // Parse argument + final String argument; + try { + argument = dataIn.readUTF(); + } catch (IllegalStateException e) { + ConsoleLogger.warning("Received invalid forwarded plugin message of type " + type.get().name() + ": argument is missing!"); + return; + } + + // Handle type + switch (type.get()) { + case UNREGISTER: + dataSource.invalidateCache(argument); + break; + case REFRESH_PASSWORD: + case REFRESH_QUITLOC: + case REFRESH_EMAIL: + case REFRESH: + dataSource.refreshCache(argument); + break; + default: + } + } + + private void handle(final ByteArrayDataInput in) { + // Parse type + final Optional type = MessageType.fromId(in.readUTF()); + if (!type.isPresent()) { + ConsoleLogger.debug("Received unsupported bungeecord message type! ({0})", type); + return; + } + + // Parse argument + final String argument; + try { + argument = in.readUTF(); + } catch (IllegalStateException e) { + ConsoleLogger.warning("Received invalid plugin message of type " + type.get().name() + + ": argument is missing!"); + return; + } + + // Handle type + switch (type.get()) { + case PERFORM_LOGIN: + performLogin(argument); + break; + default: + } + } + @Override - public void onPluginMessageReceived(String channel, Player player, byte[] data) { + public void onPluginMessageReceived(final String channel, final Player player, final byte[] data) { if (!isEnabled) { return; } - ByteArrayDataInput in = ByteStreams.newDataInput(data); - String subchannel = in.readUTF(); - if (!"AuthMe".equals(subchannel)) { - return; - } + final ByteArrayDataInput in = ByteStreams.newDataInput(data); - String type = in.readUTF(); - String name = in.readUTF(); - switch (type) { - case MessageType.UNREGISTER: - dataSource.invalidateCache(name); - break; - case MessageType.REFRESH_PASSWORD: - case MessageType.REFRESH_QUITLOC: - case MessageType.REFRESH_EMAIL: - case MessageType.REFRESH: - dataSource.refreshCache(name); - break; - case MessageType.BUNGEE_LOGIN: - handleBungeeLogin(name); - break; - default: - ConsoleLogger.debug("Received unsupported bungeecord message type! ({0})", type); + // Check subchannel + final String subChannel = in.readUTF(); + if ("AuthMe.v2.Broadcast".equals(subChannel)) { + handleBroadcast(in); + } else if ("AuthMe.v2".equals(subChannel)) { + handle(in); } } - private void handleBungeeLogin(String name) { + private void performLogin(final String name) { Player player = bukkitService.getPlayerExact(name); if (player != null && player.isOnline()) { management.forceLogin(player); ConsoleLogger.info("The user " + player.getName() + " has been automatically logged in, " - + "as requested by the AuthMeBungee integration."); + + "as requested via plugin messaging."); } - } } diff --git a/src/main/java/fr/xephi/authme/service/bungeecord/BungeeSender.java b/src/main/java/fr/xephi/authme/service/bungeecord/BungeeSender.java index 84fda6956..5e3508abf 100644 --- a/src/main/java/fr/xephi/authme/service/bungeecord/BungeeSender.java +++ b/src/main/java/fr/xephi/authme/service/bungeecord/BungeeSender.java @@ -4,6 +4,7 @@ import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.SettingsDependent; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; @@ -17,6 +18,7 @@ public class BungeeSender implements SettingsDependent { private final AuthMe plugin; private final BukkitService bukkitService; + private final DataSource dataSource; private boolean isEnabled; private String destinationServerOnLogin; @@ -25,19 +27,21 @@ public class BungeeSender implements SettingsDependent { * Constructor. */ @Inject - BungeeSender(AuthMe plugin, BukkitService bukkitService, Settings settings) { + BungeeSender(final AuthMe plugin, final BukkitService bukkitService, final DataSource dataSource, + final Settings settings) { this.plugin = plugin; this.bukkitService = bukkitService; + this.dataSource = dataSource; reload(settings); } @Override - public void reload(Settings settings) { + public void reload(final Settings settings) { this.isEnabled = settings.getProperty(HooksSettings.BUNGEECORD); this.destinationServerOnLogin = settings.getProperty(HooksSettings.BUNGEECORD_SERVER); if (this.isEnabled) { - Messenger messenger = plugin.getServer().getMessenger(); + final Messenger messenger = plugin.getServer().getMessenger(); if (!messenger.isOutgoingChannelRegistered(plugin, "BungeeCord")) { messenger.registerOutgoingPluginChannel(plugin, "BungeeCord"); } @@ -48,21 +52,36 @@ public class BungeeSender implements SettingsDependent { return isEnabled; } - private void sendBungeecordMessage(String... data) { - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - for (String element : data) { + private void sendBungeecordMessage(final String... data) { + final ByteArrayDataOutput out = ByteStreams.newDataOutput(); + for (final String element : data) { out.writeUTF(element); } bukkitService.sendBungeeMessage(out.toByteArray()); } + private void sendForwardedBungeecordMessage(final String subChannel, final String... data) { + final ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("Forward"); + out.writeUTF("ONLINE"); + out.writeUTF(subChannel); + final ByteArrayDataOutput dataOut = ByteStreams.newDataOutput(); + for (final String element : data) { + dataOut.writeUTF(element); + } + final byte[] dataBytes = dataOut.toByteArray(); + out.writeShort(dataBytes.length); + out.write(dataBytes); + bukkitService.sendBungeeMessage(out.toByteArray()); + } + /** * Send a player to a specified server. If no server is configured, this will * do nothing. * * @param player The player to send. */ - public void connectPlayerOnLogin(Player player) { + public void connectPlayerOnLogin(final Player player) { if (isEnabled && !destinationServerOnLogin.isEmpty()) { bukkitService.scheduleSyncDelayedTask(() -> sendBungeecordMessage("ConnectOther", player.getName(), destinationServerOnLogin), 5L); @@ -75,13 +94,20 @@ public class BungeeSender implements SettingsDependent { * @param type The message type, See {@link MessageType} * @param playerName the player related to the message */ - public void sendAuthMeBungeecordMessage(String type, String playerName) { + public void sendAuthMeBungeecordMessage(final MessageType type, final String playerName) { if (isEnabled) { - if(!plugin.isEnabled()) { + if (!plugin.isEnabled()) { ConsoleLogger.debug("Tried to send a " + type + " bungeecord message but the plugin was disabled!"); return; } - sendBungeecordMessage("AuthMe", type, playerName.toLowerCase()); + if (type.isRequiresCaching() && !dataSource.isCached()) { + return; + } + if (type.isBroadcast()) { + sendForwardedBungeecordMessage("AuthMe.v2.Broadcast", type.getId(), playerName.toLowerCase()); + } else { + sendBungeecordMessage("AuthMe.v2", type.getId(), playerName.toLowerCase()); + } } } diff --git a/src/main/java/fr/xephi/authme/service/bungeecord/MessageType.java b/src/main/java/fr/xephi/authme/service/bungeecord/MessageType.java index c8543bea1..f90edd725 100644 --- a/src/main/java/fr/xephi/authme/service/bungeecord/MessageType.java +++ b/src/main/java/fr/xephi/authme/service/bungeecord/MessageType.java @@ -1,19 +1,59 @@ package fr.xephi.authme.service.bungeecord; -public final class MessageType { +import java.util.Optional; - public static final String LOGIN = "login"; - public static final String LOGOUT = "logout"; - public static final String REGISTER = "register"; - public static final String UNREGISTER = "unregister"; - public static final String REFRESH_PASSWORD = "refresh.password"; - public static final String REFRESH_SESSION = "refresh.session"; - public static final String REFRESH_QUITLOC = "refresh.quitloc"; - public static final String REFRESH_EMAIL = "refresh.email"; - public static final String REFRESH = "refresh"; - public static final String BUNGEE_LOGIN = "bungeelogin"; +public enum MessageType { + REFRESH_PASSWORD("refresh.password", true, true), + REFRESH_SESSION("refresh.session", true, true), + REFRESH_QUITLOC("refresh.quitloc", true, true), + REFRESH_EMAIL("refresh.email", true, true), + REFRESH("refresh", true, true), + REGISTER("register", true), + UNREGISTER("unregister", true), + LOGIN("login", true), + LOGOUT("logout", true), + PERFORM_LOGIN("perform.login", false); - private MessageType() { + private final String id; + private final boolean broadcast; + private final boolean requiresCaching; + + MessageType(final String id, final boolean broadcast, final boolean requiresCaching) { + this.id = id; + this.broadcast = broadcast; + this.requiresCaching = requiresCaching; + } + + MessageType(final String id, final boolean broadcast) { + this(id, broadcast, false); + } + + public String getId() { + return id; + } + + public boolean isBroadcast() { + return broadcast; + } + + public boolean isRequiresCaching() { + return requiresCaching; + } + + /** + * Returns the MessageType with the given ID. + * + * @param id the message type id. + * + * @return the MessageType with the given id, empty if invalid. + */ + public static Optional fromId(final String id) { + for (final MessageType current : values()) { + if (current.getId().equals(id)) { + return Optional.of(current); + } + } + return Optional.empty(); } } diff --git a/src/main/java/fr/xephi/authme/service/yaml/YamlFileResourceProvider.java b/src/main/java/fr/xephi/authme/service/yaml/YamlFileResourceProvider.java index b13294371..9509d7304 100644 --- a/src/main/java/fr/xephi/authme/service/yaml/YamlFileResourceProvider.java +++ b/src/main/java/fr/xephi/authme/service/yaml/YamlFileResourceProvider.java @@ -1,7 +1,8 @@ package fr.xephi.authme.service.yaml; +import ch.jalu.configme.exception.ConfigMeException; +import ch.jalu.configme.resource.PropertyReader; import ch.jalu.configme.resource.YamlFileResource; -import org.yaml.snakeyaml.parser.ParserException; import java.io.File; @@ -15,16 +16,32 @@ public final class YamlFileResourceProvider { /** * Creates a {@link YamlFileResource} instance for the given file. Wraps SnakeYAML's parse exception - * into an AuthMe exception. + * thrown when a reader is created into an AuthMe exception. * * @param file the file to load * @return the generated resource */ public static YamlFileResource loadFromFile(File file) { - try { - return new YamlFileResource(file); - } catch (ParserException e) { - throw new YamlParseException(file.getPath(), e); + return new AuthMeYamlFileResource(file); + } + + /** + * Extension of {@link YamlFileResource} which wraps SnakeYAML's parse exception into a custom + * exception when a reader is created. + */ + private static final class AuthMeYamlFileResource extends YamlFileResource { + + AuthMeYamlFileResource(File file) { + super(file); + } + + @Override + public PropertyReader createReader() { + try { + return super.createReader(); + } catch (ConfigMeException e) { + throw new YamlParseException(getFile().getPath(), e); + } } } } diff --git a/src/main/java/fr/xephi/authme/service/yaml/YamlParseException.java b/src/main/java/fr/xephi/authme/service/yaml/YamlParseException.java index b070bcf35..5cc3283ec 100644 --- a/src/main/java/fr/xephi/authme/service/yaml/YamlParseException.java +++ b/src/main/java/fr/xephi/authme/service/yaml/YamlParseException.java @@ -1,6 +1,8 @@ package fr.xephi.authme.service.yaml; -import org.yaml.snakeyaml.parser.ParserException; +import ch.jalu.configme.exception.ConfigMeException; + +import static com.google.common.base.MoreObjects.firstNonNull; /** * Exception when a YAML file could not be parsed. @@ -13,10 +15,10 @@ public class YamlParseException extends RuntimeException { * Constructor. * * @param file the file a parsing exception occurred with - * @param snakeYamlException the caught exception from SnakeYAML + * @param configMeException the caught exception from ConfigMe */ - public YamlParseException(String file, ParserException snakeYamlException) { - super(snakeYamlException); + public YamlParseException(String file, ConfigMeException configMeException) { + super(firstNonNull(configMeException.getCause(), configMeException)); this.file = file; } diff --git a/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java b/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java index b18944aac..ca611315f 100644 --- a/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java +++ b/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java @@ -1,7 +1,7 @@ package fr.xephi.authme.settings; -import ch.jalu.configme.properties.Property; -import ch.jalu.configme.resource.PropertyResource; +import ch.jalu.configme.properties.BaseProperty; +import ch.jalu.configme.resource.PropertyReader; import java.util.Collection; import java.util.LinkedHashSet; @@ -15,7 +15,7 @@ import static com.google.common.collect.Sets.newHashSet; * * @param the enum type */ -public class EnumSetProperty> extends Property> { +public class EnumSetProperty> extends BaseProperty> { private final Class enumClass; @@ -26,8 +26,8 @@ public class EnumSetProperty> extends Property> { } @Override - protected Set getFromResource(PropertyResource resource) { - Object entry = resource.getObject(getPath()); + protected Set getFromReader(PropertyReader reader) { + Object entry = reader.getObject(getPath()); if (entry instanceof Collection) { return ((Collection) entry).stream() .map(val -> toEnum(String.valueOf(val))) @@ -45,4 +45,11 @@ public class EnumSetProperty> extends Property> { } return null; } + + @Override + public Object toExportValue(Set value) { + return value.stream() + .map(Enum::name) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index 01bf25801..5465875dc 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -1,6 +1,6 @@ package fr.xephi.authme.settings; -import ch.jalu.configme.SettingsManager; +import ch.jalu.configme.SettingsManagerImpl; import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.migration.MigrationService; import ch.jalu.configme.resource.PropertyResource; @@ -16,7 +16,7 @@ import static fr.xephi.authme.util.FileUtils.copyFileFromResource; /** * The AuthMe settings manager. */ -public class Settings extends SettingsManager { +public class Settings extends SettingsManagerImpl { private final File pluginFolder; private String passwordEmailMessage; @@ -33,7 +33,7 @@ public class Settings extends SettingsManager { */ public Settings(File pluginFolder, PropertyResource resource, MigrationService migrationService, ConfigurationData configurationData) { - super(resource, migrationService, configurationData); + super(resource, configurationData, migrationService); this.pluginFolder = pluginFolder; loadSettingsFromFiles(); } diff --git a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java index d95f73ce5..2b9cdd830 100644 --- a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java +++ b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java @@ -1,8 +1,9 @@ package fr.xephi.authme.settings; +import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.migration.PlainMigrationService; import ch.jalu.configme.properties.Property; -import ch.jalu.configme.resource.PropertyResource; +import ch.jalu.configme.resource.PropertyReader; import com.google.common.base.MoreObjects; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.initialization.DataFolder; @@ -25,6 +26,7 @@ import java.util.Set; import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; +import static fr.xephi.authme.settings.properties.DatabaseSettings.MYSQL_POOL_SIZE; import static fr.xephi.authme.settings.properties.RegistrationSettings.DELAY_JOIN_MESSAGE; import static fr.xephi.authme.settings.properties.RegistrationSettings.REMOVE_JOIN_MESSAGE; import static fr.xephi.authme.settings.properties.RegistrationSettings.REMOVE_LEAVE_MESSAGE; @@ -53,33 +55,33 @@ public class SettingsMigrationService extends PlainMigrationService { @Override @SuppressWarnings("checkstyle:BooleanExpressionComplexity") - protected boolean performMigrations(PropertyResource resource, List> properties) { + protected boolean performMigrations(PropertyReader reader, ConfigurationData configurationData) { boolean changes = false; - if ("[a-zA-Z0-9_?]*".equals(resource.getString(ALLOWED_NICKNAME_CHARACTERS.getPath()))) { - resource.setValue(ALLOWED_NICKNAME_CHARACTERS.getPath(), "[a-zA-Z0-9_]*"); + if ("[a-zA-Z0-9_?]*".equals(reader.getString(ALLOWED_NICKNAME_CHARACTERS.getPath()))) { + configurationData.setValue(ALLOWED_NICKNAME_CHARACTERS, "[a-zA-Z0-9_]*"); changes = true; } - setOldOtherAccountsCommandFieldsIfSet(resource); + setOldOtherAccountsCommandFieldsIfSet(reader); // Note ljacqu 20160211: Concatenating migration methods with | instead of the usual || // ensures that all migrations will be performed return changes - | performMailTextToFileMigration(resource) - | migrateJoinLeaveMessages(resource) - | migrateForceSpawnSettings(resource) - | migratePoolSizeSetting(resource) - | changeBooleanSettingToLogLevelProperty(resource) - | hasOldHelpHeaderProperty(resource) - | hasSupportOldPasswordProperty(resource) - | convertToRegistrationType(resource) - | mergeAndMovePermissionGroupSettings(resource) - | moveDeprecatedHashAlgorithmIntoLegacySection(resource) - | moveSaltColumnConfigWithOtherColumnConfigs(resource) - || hasDeprecatedProperties(resource); + | performMailTextToFileMigration(reader) + | migrateJoinLeaveMessages(reader, configurationData) + | migrateForceSpawnSettings(reader, configurationData) + | migratePoolSizeSetting(reader, configurationData) + | changeBooleanSettingToLogLevelProperty(reader, configurationData) + | hasOldHelpHeaderProperty(reader) + | hasSupportOldPasswordProperty(reader) + | convertToRegistrationType(reader, configurationData) + | mergeAndMovePermissionGroupSettings(reader, configurationData) + | moveDeprecatedHashAlgorithmIntoLegacySection(reader, configurationData) + | moveSaltColumnConfigWithOtherColumnConfigs(reader, configurationData) + || hasDeprecatedProperties(reader); } - private static boolean hasDeprecatedProperties(PropertyResource resource) { + private static boolean hasDeprecatedProperties(PropertyReader reader) { String[] deprecatedProperties = { "Converter.Rakamak.newPasswordHash", "Hooks.chestshop", "Hooks.legacyChestshop", "Hooks.notifications", "Passpartu", "Performances", "settings.restrictions.enablePasswordVerifier", "Xenoforo.predefinedSalt", @@ -90,7 +92,7 @@ public class SettingsMigrationService extends PlainMigrationService { "settings.sessions.sessionExpireOnIpChange", "settings.restrictions.otherAccountsCmd", "settings.restrictions.otherAccountsCmdThreshold"}; for (String deprecatedPath : deprecatedProperties) { - if (resource.contains(deprecatedPath)) { + if (reader.contains(deprecatedPath)) { return true; } } @@ -119,12 +121,12 @@ public class SettingsMigrationService extends PlainMigrationService { /** * Check if {@code Email.mailText} is present and move it to the Email.html file if it doesn't exist yet. * - * @param resource The property resource + * @param reader The property reader * @return True if a migration has been completed, false otherwise */ - private boolean performMailTextToFileMigration(PropertyResource resource) { + private boolean performMailTextToFileMigration(PropertyReader reader) { final String oldSettingPath = "Email.mailText"; - final String oldMailText = resource.getString(oldSettingPath); + final String oldMailText = reader.getString(oldSettingPath); if (oldMailText == null) { return false; } @@ -149,12 +151,13 @@ public class SettingsMigrationService extends PlainMigrationService { * Detect deprecated {@code settings.delayJoinLeaveMessages} and inform user of new "remove join messages" * and "remove leave messages" settings. * - * @param resource The property resource + * @param reader The property reader + * @param configData Configuration data * @return True if the configuration has changed, false otherwise */ - private static boolean migrateJoinLeaveMessages(PropertyResource resource) { + private static boolean migrateJoinLeaveMessages(PropertyReader reader, ConfigurationData configData) { Property oldDelayJoinProperty = newProperty("settings.delayJoinLeaveMessages", false); - boolean hasMigrated = moveProperty(oldDelayJoinProperty, DELAY_JOIN_MESSAGE, resource); + boolean hasMigrated = moveProperty(oldDelayJoinProperty, DELAY_JOIN_MESSAGE, reader, configData); if (hasMigrated) { ConsoleLogger.info(String.format("Note that we now also have the settings %s and %s", @@ -167,31 +170,33 @@ public class SettingsMigrationService extends PlainMigrationService { * Detects old "force spawn loc on join" and "force spawn on these worlds" settings and moves them * to the new paths. * - * @param resource The property resource + * @param reader The property reader + * @param configData Configuration data * @return True if the configuration has changed, false otherwise */ - private static boolean migrateForceSpawnSettings(PropertyResource resource) { + private static boolean migrateForceSpawnSettings(PropertyReader reader, ConfigurationData configData) { Property oldForceLocEnabled = newProperty( "settings.restrictions.ForceSpawnLocOnJoinEnabled", false); Property> oldForceWorlds = newListProperty( "settings.restrictions.ForceSpawnOnTheseWorlds", "world", "world_nether", "world_the_ed"); - return moveProperty(oldForceLocEnabled, FORCE_SPAWN_LOCATION_AFTER_LOGIN, resource) - | moveProperty(oldForceWorlds, FORCE_SPAWN_ON_WORLDS, resource); + return moveProperty(oldForceLocEnabled, FORCE_SPAWN_LOCATION_AFTER_LOGIN, reader, configData) + | moveProperty(oldForceWorlds, FORCE_SPAWN_ON_WORLDS, reader, configData); } /** * Detects the old auto poolSize value and replaces it with the default value. * - * @param resource The property resource + * @param reader The property reader + * @param configData Configuration data * @return True if the configuration has changed, false otherwise */ - private static boolean migratePoolSizeSetting(PropertyResource resource) { - Integer oldValue = resource.getInt("DataSource.poolSize"); - if(oldValue == null || oldValue > 0) { + private static boolean migratePoolSizeSetting(PropertyReader reader, ConfigurationData configData) { + Integer oldValue = reader.getInt(MYSQL_POOL_SIZE.getPath()); + if (oldValue == null || oldValue > 0) { return false; } - resource.setValue("DataSource.poolSize", 10); + configData.setValue(MYSQL_POOL_SIZE, 10); return true; } @@ -199,24 +204,26 @@ public class SettingsMigrationService extends PlainMigrationService { * Changes the old boolean property "hide spam from console" to the new property specifying * the log level. * - * @param resource The property resource + * @param reader The property reader + * @param configData Configuration data * @return True if the configuration has changed, false otherwise */ - private static boolean changeBooleanSettingToLogLevelProperty(PropertyResource resource) { + private static boolean changeBooleanSettingToLogLevelProperty(PropertyReader reader, + ConfigurationData configData) { final String oldPath = "Security.console.noConsoleSpam"; final Property newProperty = PluginSettings.LOG_LEVEL; - if (!newProperty.isPresent(resource) && resource.contains(oldPath)) { + if (!newProperty.isPresent(reader) && reader.contains(oldPath)) { ConsoleLogger.info("Moving '" + oldPath + "' to '" + newProperty.getPath() + "'"); - boolean oldValue = MoreObjects.firstNonNull(resource.getBoolean(oldPath), false); + boolean oldValue = MoreObjects.firstNonNull(reader.getBoolean(oldPath), false); LogLevel level = oldValue ? LogLevel.INFO : LogLevel.FINE; - resource.setValue(newProperty.getPath(), level.name()); + configData.setValue(newProperty, level); return true; } return false; } - private static boolean hasOldHelpHeaderProperty(PropertyResource resource) { - if (resource.contains("settings.helpHeader")) { + private static boolean hasOldHelpHeaderProperty(PropertyReader reader) { + if (reader.contains("settings.helpHeader")) { ConsoleLogger.warning("Help header setting is now in messages/help_xx.yml, " + "please check the file to set it again"); return true; @@ -224,9 +231,9 @@ public class SettingsMigrationService extends PlainMigrationService { return false; } - private static boolean hasSupportOldPasswordProperty(PropertyResource resource) { + private static boolean hasSupportOldPasswordProperty(PropertyReader reader) { String path = "settings.security.supportOldPasswordHash"; - if (resource.contains(path)) { + if (reader.contains(path)) { ConsoleLogger.warning("Property '" + path + "' is no longer supported. " + "Use '" + SecuritySettings.LEGACY_HASHES.getPath() + "' instead."); return true; @@ -237,56 +244,58 @@ public class SettingsMigrationService extends PlainMigrationService { /** * Converts old boolean configurations for registration to the new enum properties, if applicable. * - * @param resource The property resource + * @param reader The property reader + * @param configData Configuration data * @return True if the configuration has changed, false otherwise */ - private static boolean convertToRegistrationType(PropertyResource resource) { + private static boolean convertToRegistrationType(PropertyReader reader, ConfigurationData configData) { String oldEmailRegisterPath = "settings.registration.enableEmailRegistrationSystem"; - if (RegistrationSettings.REGISTRATION_TYPE.isPresent(resource) || !resource.contains(oldEmailRegisterPath)) { + if (RegistrationSettings.REGISTRATION_TYPE.isPresent(reader) || !reader.contains(oldEmailRegisterPath)) { return false; } - boolean useEmail = newProperty(oldEmailRegisterPath, false).getValue(resource); + boolean useEmail = newProperty(oldEmailRegisterPath, false).determineValue(reader); RegistrationType registrationType = useEmail ? RegistrationType.EMAIL : RegistrationType.PASSWORD; String useConfirmationPath = useEmail ? "settings.registration.doubleEmailCheck" : "settings.restrictions.enablePasswordConfirmation"; - boolean hasConfirmation = newProperty(useConfirmationPath, false).getValue(resource); + boolean hasConfirmation = newProperty(useConfirmationPath, false).determineValue(reader); RegisterSecondaryArgument secondaryArgument = hasConfirmation ? RegisterSecondaryArgument.CONFIRMATION : RegisterSecondaryArgument.NONE; ConsoleLogger.warning("Merging old registration settings into '" + RegistrationSettings.REGISTRATION_TYPE.getPath() + "'"); - resource.setValue(RegistrationSettings.REGISTRATION_TYPE.getPath(), registrationType); - resource.setValue(RegistrationSettings.REGISTER_SECOND_ARGUMENT.getPath(), secondaryArgument); + configData.setValue(RegistrationSettings.REGISTRATION_TYPE, registrationType); + configData.setValue(RegistrationSettings.REGISTER_SECOND_ARGUMENT, secondaryArgument); return true; } /** * Migrates old permission group settings to the new configurations. * - * @param resource The property resource + * @param reader The property reader + * @param configData Configuration data * @return True if the configuration has changed, false otherwise */ - private static boolean mergeAndMovePermissionGroupSettings(PropertyResource resource) { + private static boolean mergeAndMovePermissionGroupSettings(PropertyReader reader, ConfigurationData configData) { boolean performedChanges; // We have two old settings replaced by only one: move the first non-empty one Property oldUnloggedInGroup = newProperty("settings.security.unLoggedinGroup", ""); Property oldRegisteredGroup = newProperty("GroupOptions.RegisteredPlayerGroup", ""); - if (!oldUnloggedInGroup.getValue(resource).isEmpty()) { - performedChanges = moveProperty(oldUnloggedInGroup, PluginSettings.REGISTERED_GROUP, resource); + if (!oldUnloggedInGroup.determineValue(reader).isEmpty()) { + performedChanges = moveProperty(oldUnloggedInGroup, PluginSettings.REGISTERED_GROUP, reader, configData); } else { - performedChanges = moveProperty(oldRegisteredGroup, PluginSettings.REGISTERED_GROUP, resource); + performedChanges = moveProperty(oldRegisteredGroup, PluginSettings.REGISTERED_GROUP, reader, configData); } // Move paths of other old options performedChanges |= moveProperty(newProperty("GroupOptions.UnregisteredPlayerGroup", ""), - PluginSettings.UNREGISTERED_GROUP, resource); + PluginSettings.UNREGISTERED_GROUP, reader, configData); performedChanges |= moveProperty(newProperty("permission.EnablePermissionCheck", false), - PluginSettings.ENABLE_PERMISSION_CHECK, resource); + PluginSettings.ENABLE_PERMISSION_CHECK, reader, configData); return performedChanges; } @@ -294,19 +303,21 @@ public class SettingsMigrationService extends PlainMigrationService { * If a deprecated hash is used, it is added to the legacy hashes option and the active hash * is changed to SHA256. * - * @param resource The property resource + * @param reader The property reader + * @param configData Configuration data * @return True if the configuration has changed, false otherwise */ - private static boolean moveDeprecatedHashAlgorithmIntoLegacySection(PropertyResource resource) { - HashAlgorithm currentHash = SecuritySettings.PASSWORD_HASH.getValue(resource); + private static boolean moveDeprecatedHashAlgorithmIntoLegacySection(PropertyReader reader, + ConfigurationData configData) { + HashAlgorithm currentHash = SecuritySettings.PASSWORD_HASH.determineValue(reader); // Skip CUSTOM (has no class) and PLAINTEXT (is force-migrated later on in the startup process) if (currentHash != HashAlgorithm.CUSTOM && currentHash != HashAlgorithm.PLAINTEXT) { Class encryptionClass = currentHash.getClazz(); if (encryptionClass.isAnnotationPresent(Deprecated.class)) { - resource.setValue(SecuritySettings.PASSWORD_HASH.getPath(), HashAlgorithm.SHA256); - Set legacyHashes = SecuritySettings.LEGACY_HASHES.getValue(resource); + configData.setValue(SecuritySettings.PASSWORD_HASH, HashAlgorithm.SHA256); + Set legacyHashes = SecuritySettings.LEGACY_HASHES.determineValue(reader); legacyHashes.add(currentHash); - resource.setValue(SecuritySettings.LEGACY_HASHES.getPath(), legacyHashes); + configData.setValue(SecuritySettings.LEGACY_HASHES, legacyHashes); ConsoleLogger.warning("The hash algorithm '" + currentHash + "' is no longer supported for active use. New hashes will be in SHA256."); return true; @@ -318,28 +329,30 @@ public class SettingsMigrationService extends PlainMigrationService { /** * Moves the property for the password salt column name to the same path as all other column name properties. * - * @param resource The property resource + * @param reader The property reader + * @param configData Configuration data * @return True if the configuration has changed, false otherwise */ - private static boolean moveSaltColumnConfigWithOtherColumnConfigs(PropertyResource resource) { + private static boolean moveSaltColumnConfigWithOtherColumnConfigs(PropertyReader reader, + ConfigurationData configData) { Property oldProperty = newProperty("ExternalBoardOptions.mySQLColumnSalt", DatabaseSettings.MYSQL_COL_SALT.getDefaultValue()); - return moveProperty(oldProperty, DatabaseSettings.MYSQL_COL_SALT, resource); + return moveProperty(oldProperty, DatabaseSettings.MYSQL_COL_SALT, reader, configData); } /** * Retrieves the old config to run a command when alt accounts are detected and sets them to this instance * for further processing. * - * @param resource The property resource + * @param reader The property reader */ - private void setOldOtherAccountsCommandFieldsIfSet(PropertyResource resource) { + private void setOldOtherAccountsCommandFieldsIfSet(PropertyReader reader) { Property commandProperty = newProperty("settings.restrictions.otherAccountsCmd", ""); Property commandThresholdProperty = newProperty("settings.restrictions.otherAccountsCmdThreshold", 0); - if (commandProperty.isPresent(resource) && commandThresholdProperty.getValue(resource) >= 2) { - oldOtherAccountsCommand = commandProperty.getValue(resource); - oldOtherAccountsCommandThreshold = commandThresholdProperty.getValue(resource); + if (commandProperty.isPresent(reader) && commandThresholdProperty.determineValue(reader) >= 2) { + oldOtherAccountsCommand = commandProperty.determineValue(reader); + oldOtherAccountsCommandThreshold = commandThresholdProperty.determineValue(reader); } } @@ -348,19 +361,21 @@ public class SettingsMigrationService extends PlainMigrationService { * * @param oldProperty The old property (create a temporary {@link Property} object with the path) * @param newProperty The new property to move the value to - * @param resource The property resource + * @param reader The property reader + * @param configData Configuration data * @param The type of the property * @return True if a migration has been done, false otherwise */ - private static boolean moveProperty(Property oldProperty, - Property newProperty, - PropertyResource resource) { - if (resource.contains(oldProperty.getPath())) { - if (resource.contains(newProperty.getPath())) { + protected static boolean moveProperty(Property oldProperty, + Property newProperty, + PropertyReader reader, + ConfigurationData configData) { + if (reader.contains(oldProperty.getPath())) { + if (reader.contains(newProperty.getPath())) { ConsoleLogger.info("Detected deprecated property " + oldProperty.getPath()); } else { ConsoleLogger.info("Renaming " + oldProperty.getPath() + " to " + newProperty.getPath()); - resource.setValue(newProperty.getPath(), oldProperty.getValue(resource)); + configData.setValue(newProperty, oldProperty.determineValue(reader)); } return true; } diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java index 50d73c9a5..b26844e53 100644 --- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java @@ -1,6 +1,7 @@ package fr.xephi.authme.settings.commandconfig; import ch.jalu.configme.SettingsManager; +import ch.jalu.configme.SettingsManagerBuilder; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.service.BukkitService; @@ -148,8 +149,11 @@ public class CommandManager implements Reloadable { File file = new File(dataFolder, "commands.yml"); FileUtils.copyFileFromResource(file, "commands.yml"); - SettingsManager settingsManager = new SettingsManager( - YamlFileResourceProvider.loadFromFile(file), commandMigrationService, CommandSettingsHolder.class); + SettingsManager settingsManager = SettingsManagerBuilder + .withResource(YamlFileResourceProvider.loadFromFile(file)) + .configurationData(CommandSettingsHolder.class) + .migrationService(commandMigrationService) + .create(); CommandConfig commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS); onJoinCommands = newReplacer(commandConfig.getOnJoin()); onLoginCommands = newOnLoginCmdReplacer(commandConfig.getOnLogin()); diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandMigrationService.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandMigrationService.java index a602e49c3..b88e0c882 100644 --- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandMigrationService.java +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandMigrationService.java @@ -1,8 +1,8 @@ package fr.xephi.authme.settings.commandconfig; +import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.migration.MigrationService; -import ch.jalu.configme.properties.Property; -import ch.jalu.configme.resource.PropertyResource; +import ch.jalu.configme.resource.PropertyReader; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import fr.xephi.authme.settings.SettingsMigrationService; @@ -30,10 +30,10 @@ class CommandMigrationService implements MigrationService { } @Override - public boolean checkAndMigrate(PropertyResource resource, List> properties) { - final CommandConfig commandConfig = CommandSettingsHolder.COMMANDS.getValue(resource); - if (moveOtherAccountsConfig(commandConfig) || isFileEmpty(resource)) { - resource.setValue("", commandConfig); + public boolean checkAndMigrate(PropertyReader reader, ConfigurationData configurationData) { + final CommandConfig commandConfig = CommandSettingsHolder.COMMANDS.determineValue(reader); + if (moveOtherAccountsConfig(commandConfig) || isAnyCommandMissing(reader)) { + configurationData.setValue(CommandSettingsHolder.COMMANDS, commandConfig); return true; } return false; @@ -59,7 +59,7 @@ class CommandMigrationService implements MigrationService { .replace("%playerip%", "%ip"); } - private static boolean isFileEmpty(PropertyResource resource) { - return COMMAND_CONFIG_PROPERTIES.stream().anyMatch(property -> resource.getObject(property) == null); + private static boolean isAnyCommandMissing(PropertyReader reader) { + return COMMAND_CONFIG_PROPERTIES.stream().anyMatch(property -> reader.getObject(property) == null); } } diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java index 965813c96..ce502c80f 100644 --- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java @@ -1,13 +1,10 @@ package fr.xephi.authme.settings.commandconfig; -import ch.jalu.configme.SectionComments; import ch.jalu.configme.SettingsHolder; +import ch.jalu.configme.configurationdata.CommentsConfiguration; import ch.jalu.configme.properties.BeanProperty; import ch.jalu.configme.properties.Property; -import java.util.HashMap; -import java.util.Map; - /** * Settings holder class for the commands.yml settings. */ @@ -16,12 +13,11 @@ public final class CommandSettingsHolder implements SettingsHolder { public static final Property COMMANDS = new BeanProperty<>(CommandConfig.class, "", new CommandConfig()); - private CommandSettingsHolder() { } - @SectionComments - public static Map sectionComments() { + @Override + public void registerComments(CommentsConfiguration conf) { String[] rootComments = { "This configuration file allows you to execute commands on various events.", "Supported placeholders in commands:", @@ -60,21 +56,15 @@ public final class CommandSettingsHolder implements SettingsHolder { " ifNumberOfAccountsAtLeast: 5" }; - Map commentMap = new HashMap<>(); - commentMap.put("", rootComments); - commentMap.put("onFirstLogin", new String[]{ - "Commands to run for players logging in whose 'last login date' was empty" - }); - commentMap.put("onUnregister", new String[]{ - "Commands to run whenever a player is unregistered (by himself, or by an admin)" - }); - commentMap.put("onLogout", new String[]{ + conf.setComment("", rootComments); + conf.setComment("onFirstLogin", + "Commands to run for players logging in whose 'last login date' was empty"); + conf.setComment("onUnregister", + "Commands to run whenever a player is unregistered (by himself, or by an admin)"); + conf.setComment("onLogout", "These commands are called whenever a logged in player uses /logout or quits.", "The commands are not run if a player that was not logged in quits the server.", "Note: if your server crashes, these commands won't be run, so don't rely on them to undo", - "'onLogin' commands that would be dangerous for non-logged in players to have!" - }); - return commentMap; + "'onLogin' commands that would be dangerous for non-logged in players to have!"); } - } diff --git a/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java b/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java index 2fbe7ea24..c3793485c 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java +++ b/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java @@ -19,7 +19,7 @@ public final class AuthMeSettingsRetriever { * @return configuration data */ public static ConfigurationData buildConfigurationData() { - return ConfigurationDataBuilder.collectData( + return ConfigurationDataBuilder.createConfiguration( DatabaseSettings.class, PluginSettings.class, RestrictionSettings.class, EmailSettings.class, HooksSettings.class, ProtectionSettings.class, PurgeSettings.class, SecuritySettings.class, RegistrationSettings.class, 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 d1c426d6b..451c9c244 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java @@ -1,12 +1,9 @@ package fr.xephi.authme.settings.properties; import ch.jalu.configme.Comment; -import ch.jalu.configme.SectionComments; import ch.jalu.configme.SettingsHolder; +import ch.jalu.configme.configurationdata.CommentsConfiguration; import ch.jalu.configme.properties.Property; -import com.google.common.collect.ImmutableMap; - -import java.util.Map; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; @@ -51,9 +48,9 @@ public final class ConverterSettings implements SettingsHolder { private ConverterSettings() { } - @SectionComments - public static Map buildSectionComments() { - return ImmutableMap.of("Converter", - new String[]{"Converter settings: see https://github.com/AuthMe/AuthMeReloaded/wiki/Converters"}); + @Override + public void registerComments(CommentsConfiguration conf) { + conf.setComment("Converter", + "Converter settings: see https://github.com/AuthMe/AuthMeReloaded/wiki/Converters"); } } diff --git a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java index 0818c2693..40e9933a3 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java @@ -10,7 +10,7 @@ import static ch.jalu.configme.properties.PropertyInitializer.newProperty; public final class DatabaseSettings implements SettingsHolder { @Comment({"What type of database do you want to use?", - "Valid values: SQLITE, MYSQL"}) + "Valid values: SQLITE, MYSQL, POSTGRESQL"}) public static final Property BACKEND = newProperty(DataSourceType.class, "DataSource.backend", DataSourceType.SQLITE); diff --git a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java index 68a4b7170..309b69231 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java @@ -1,17 +1,14 @@ package fr.xephi.authme.settings.properties; import ch.jalu.configme.Comment; -import ch.jalu.configme.SectionComments; import ch.jalu.configme.SettingsHolder; +import ch.jalu.configme.configurationdata.CommentsConfiguration; import ch.jalu.configme.properties.Property; -import com.google.common.collect.ImmutableMap; import fr.xephi.authme.data.limbo.AllowFlightRestoreType; import fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType; import fr.xephi.authme.data.limbo.persistence.LimboPersistenceType; import fr.xephi.authme.data.limbo.persistence.SegmentSize; -import java.util.Map; - import static ch.jalu.configme.properties.PropertyInitializer.newProperty; /** @@ -72,8 +69,8 @@ public final class LimboSettings implements SettingsHolder { private LimboSettings() { } - @SectionComments - public static Map createSectionComments() { + @Override + public void registerComments(CommentsConfiguration conf) { String[] limboExplanation = { "Before a user logs in, various properties are temporarily removed from the player,", "such as OP status, ability to fly, and walk/fly speed.", @@ -81,6 +78,6 @@ public final class LimboSettings implements SettingsHolder { "In this section, you may define how these properties should be handled.", "Read more at https://github.com/AuthMe/AuthMeReloaded/wiki/Limbo-players" }; - return ImmutableMap.of("limbo", limboExplanation); + conf.setComment("limbo", limboExplanation); } } diff --git a/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java index cc715d0ca..ba724afa9 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java @@ -23,9 +23,10 @@ public final class ProtectionSettings implements SettingsHolder { @Comment({ "Countries allowed to join the server and register. For country codes, see", "https://dev.maxmind.com/geoip/legacy/codes/iso3166/", + "Use \"LOCALHOST\" for local addresses.", "PLEASE USE QUOTES!"}) public static final Property> COUNTRIES_WHITELIST = - newListProperty("Protection.countries", "US", "GB"); + newListProperty("Protection.countries", "US", "GB", "LOCALHOST"); @Comment({ "Countries not allowed to join the server and register", diff --git a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java index e769dd9b0..2fa90c317 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java @@ -5,9 +5,10 @@ import ch.jalu.configme.SettingsHolder; import ch.jalu.configme.properties.Property; import java.util.List; +import java.util.Set; import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; -import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseListProperty; +import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseStringSetProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; public final class RestrictionSettings implements SettingsHolder { @@ -24,8 +25,8 @@ public final class RestrictionSettings implements SettingsHolder { newProperty("settings.restrictions.hideChat", false); @Comment("Allowed commands for unauthenticated players") - public static final Property> ALLOW_COMMANDS = - newLowercaseListProperty("settings.restrictions.allowCommands", + public static final Property> ALLOW_COMMANDS = + newLowercaseStringSetProperty("settings.restrictions.allowCommands", "/login", "/register", "/l", "/reg", "/email", "/captcha", "/2fa", "/totp"); @Comment({ @@ -83,8 +84,8 @@ public final class RestrictionSettings implements SettingsHolder { " AllowedRestrictedUser:", " - playername;127.0.0.1", " - playername;regex:127\\.0\\.0\\..*"}) - public static final Property> RESTRICTED_USERS = - newLowercaseListProperty("settings.restrictions.AllowedRestrictedUser"); + public static final Property> RESTRICTED_USERS = + newLowercaseStringSetProperty("settings.restrictions.AllowedRestrictedUser"); @Comment("Ban unknown IPs trying to log in with a restricted username?") public static final Property BAN_UNKNOWN_IP = @@ -177,8 +178,8 @@ public final class RestrictionSettings implements SettingsHolder { "- 'npcPlayer'", "- 'npcPlayer2'" }) - public static final Property> UNRESTRICTED_NAMES = - newLowercaseListProperty("settings.unrestrictions.UnrestrictedName"); + public static final Property> UNRESTRICTED_NAMES = + newLowercaseStringSetProperty("settings.unrestrictions.UnrestrictedName"); private RestrictionSettings() { } diff --git a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java index d4ce71fc5..0cffd2804 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java @@ -6,10 +6,9 @@ import ch.jalu.configme.properties.Property; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.settings.EnumSetProperty; -import java.util.List; import java.util.Set; -import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseListProperty; +import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseStringSetProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; public final class SecuritySettings implements SettingsHolder { @@ -86,8 +85,8 @@ public final class SecuritySettings implements SettingsHolder { "- '123456'", "- 'password'", "- 'help'"}) - public static final Property> UNSAFE_PASSWORDS = - newLowercaseListProperty("settings.security.unsafePasswords", + public static final Property> UNSAFE_PASSWORDS = + newLowercaseStringSetProperty("settings.security.unsafePasswords", "123456", "password", "qwerty", "12345", "54321", "123456789", "help"); @Comment("Tempban a user's IP address if they enter the wrong password too many times") diff --git a/src/main/java/fr/xephi/authme/task/purge/PurgeTask.java b/src/main/java/fr/xephi/authme/task/purge/PurgeTask.java index 686bab86d..06bf06f50 100644 --- a/src/main/java/fr/xephi/authme/task/purge/PurgeTask.java +++ b/src/main/java/fr/xephi/authme/task/purge/PurgeTask.java @@ -3,7 +3,6 @@ package fr.xephi.authme.task.purge; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; -import fr.xephi.authme.permission.handlers.PermissionLoadUserException; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.OfflinePlayer; @@ -74,7 +73,7 @@ class PurgeTask extends BukkitRunnable { OfflinePlayer offlinePlayer = offlinePlayers[nextPosition]; if (offlinePlayer.getName() != null && toPurge.remove(offlinePlayer.getName().toLowerCase())) { - if(!permissionsManager.loadUserData(offlinePlayer)) { + if (!permissionsManager.loadUserData(offlinePlayer)) { ConsoleLogger.warning("Unable to check if the user " + offlinePlayer.getName() + " can be purged!"); continue; } diff --git a/src/main/resources/messages/help_hu.yml b/src/main/resources/messages/help_hu.yml index 032e23cb0..93bfabd8f 100644 --- a/src/main/resources/messages/help_hu.yml +++ b/src/main/resources/messages/help_hu.yml @@ -3,29 +3,29 @@ # ------------------------------------------------------- # List of texts used in the help section common: - header: '==========[ AuthMeReloaded Segítség ]==========' - optional: 'Opcionális' - hasPermission: 'Van ehhez jogod' - noPermission: 'Nincs jogod ehhez' - default: 'Alapértelmezett' - result: 'Eredmény' - defaultPermissions: - notAllowed: 'Nincs jogod ehhez' - opOnly: 'OP jog szükséges' - allowed: 'Mindenkinek elérhető' + header: ======[ AuthMeReloaded Segítség ]====== + optional: Opcionális + hasPermission: Van ehhez jogod + noPermission: Nincs jogod ehhez + default: Alapértelmezett + result: Eredmény + defaultPermissions: + notAllowed: Nincs jogod ehhez + opOnly: Op jog szükséges + allowed: Mindenkinek elérhető # ------------------------------------------------------- # Titles of the individual help sections # Set the translation text to empty text to disable the section, e.g. to hide alternatives: # alternatives: '' section: - command: 'Parancs' - description: 'Rövid leírás' - detailedDescription: 'Hosszabb leírás' - arguments: 'Argumentum (változó)' - permissions: 'Jogok' - alternatives: 'Alternatívák' - children: 'Parancsok' + command: Parancs + description: Rövid leírás + detailedDescription: Hosszabb leírás + arguments: Argumentum (változó) + permissions: Jogok + alternatives: Alternatívák + children: Parancsok # ------------------------------------------------------- # You can translate the data for all commands using the below pattern. @@ -34,12 +34,318 @@ section: # Translations don't need to be complete; any missing section will be taken from the default silently # Important: Put main commands like "authme" before their children (e.g. "authme.reload") commands: - authme.register: - description: 'Játékos regisztrálása' - detailedDescription: 'Megadott játékos regisztrálása egy megadott jelszóval.' - arg1: - label: 'Játékos' - description: 'Játékos neve' - arg2: - label: 'jelszó' - description: 'Jelszó' + authme: + description: AuthMe operátor parancsok + detailedDescription: Az AuthMeReloaded fő parancsa. Az összes adminisztrációs parancs gyökere. + authme.register: + description: Játékos regisztrálása + detailedDescription: Megadott játékos regisztrálása egy megadott jelszóval. + arg1: + label: játékos + description: Játékos név + arg2: + label: jelszó + description: Jelszó + authme.unregister: + description: A játékos fiókjának visszavonása + detailedDescription: Kiveszi a játékos regisztrált fiókját. + arg1: + label: játékos + description: Játékos név + authme.forcelogin: + description: Kötelező bejelentkezés + detailedDescription: Ez arra kényszerít egy adott játékost, hogy jelentkezzen be. + arg1: + label: játékos + description: A játékos neve, ha online + authme.password: + description: A játékos jelszavának módosítása + detailedDescription: A játékos jelszavának módosítása. + arg1: + label: játékos + description: Játékos név + arg2: + label: jelszó + description: Új jelszó + authme.lastlogin: + description: Utolsó bejelentkezés + detailedDescription: Megtekintheti egy adott játékos utolsó bejelentkezésének dátumát. + arg1: + label: játékos + description: Játékos név + authme.accounts: + description: Játékosok megjelenítése + detailedDescription: Megmutassa a játékos összes fiókját a játékos nevével vagy IP-jével. + arg1: + label: játékos + description: Játékos neve vagy IP címe + authme.email: + description: Játékos levelek megjelenítése + detailedDescription: Egy adott játékos e-mailjét mutatja, ha a fiókjában beállította. + arg1: + label: játékos + description: Játékos név + authme.setemail: + description: Módosítsa a játékos e-mailjét + detailedDescription: Egy adott játékos e-mail címének módosítása. + arg1: + label: játékos + description: Játékos név + arg2: + label: email + description: A játékos e-mailje + authme.getip: + description: A játékos IP cím mutatása + detailedDescription: Kap egy adott online játékos IP-címét. + arg1: + label: játékos + description: Játékos név + authme.spawn: + description: Teleportálás a spawnra + detailedDescription: Teleportál a spawnra. + authme.setspawn: + description: Spawn beállítása + detailedDescription: Beállítja a spawn kezdőhelyet a játékos jelenlegi pozicíójában. + authme.firstspawn: + description: Teleportálás az első spawnhoz + detailedDescription: Teleportál az első spawn kezdőhelyre. + authme.setfirstspawn: + description: Első spawn beállítása + detailedDescription: Beállítja az első spawn kezdőhelyet a játékos jelenlegi pozicíójában. + authme.purge: + description: Törli a régi adatokat + detailedDescription: Törli az AuthMeReloaded játékos adatokat a megadott nap szerint. + arg1: + label: nap + description: Napok száma + authme.purgeplayer: + description: Játékos adat törlés + detailedDescription: Törli a kiválasztott játékos adatait. + arg1: + label: játékos + description: A játékos törlése az adatbázisból + arg2: + label: opciók + description: 'Írd be a parancs végén a ''force'' parancsot, hogy ellenőrizze a játékos regisztrálva van-e' + authme.backup: + description: Biztonsági másolat készítése + detailedDescription: Létrehoz egy biztonsági másolatot a regisztrált felhasználókról. + authme.resetpos: + description: Visszaállítja a játékos utolsó helyzetét + detailedDescription: Visszaállítja az adott játékos vagy az összes játékos utolsó ismert pozícióját. + arg1: + label: játékos + description: Játékos neve vagy írd be a '*' gomb az összes játékos kiválasztásához + authme.purgebannedplayers: + description: Törli az adatokat a kitiltott játékosoktól + detailedDescription: Törli az összes AuthMeReloaded adatokat a kitiltott játékosoktól. + authme.switchantibot: + description: Módosítja az AntiBot módot + detailedDescription: Az AntiBot mód megváltoztatása vagy átkapcsolása a megadott állapotra. + arg1: + label: mód + description: ON / OFF + authme.reload: + description: Plugin újratöltés + detailedDescription: Újratölti az AuthMeReloaded plugint. + authme.version: + description: Plugin verzió + detailedDescription: Részletes információ az AuthMeReloaded telepített verziójáról, az alkotókról, a közreműködőkről és az engedélyekről. + authme.converter: + description: Parancs konvertáló + detailedDescription: AuthMeReloaded konvertáló parancs. + arg1: + label: munka + description: 'Konverziós munka: xauth / crazylogin / rakamak / royalauth / vauth / sqliteToSql / mysqlToSqlite / loginsecurity' + authme.messages: + description: Add hiányzó üzeneteket + detailedDescription: Hozzáadja a hiányzó üzeneteket az aktuális üzenetfájlhoz. + arg1: + label: help + description: A 'help' frissítéséhez, hogy frissítse a segítséget + authme.recent: + description: Utolsó bejelentkezett játékosok megjelenítése + detailedDescription: Megjeleníti az utolsó bejelentkezett játékosokat. + authme.debug: + description: Hibakeresés funkciói + detailedDescription: Lehetővé tesz több hibakeresési műveletet. + arg1: + label: gyermek + description: A végrehajtandó gyermek (gyermek) + arg2: + label: argumentum + description: Argumentum (a debug-szekciótól függ) + arg3: + label: argumentum + description: Argumentum (a debug-szekciótól függ) + authme.help: + description: Segítség megjelenítése + detailedDescription: Részletesen megmutatja az authme parancsok segítséget. + arg1: + label: konzultáció + description: A parancs vagy lekérdezés leírása. + email: + description: Hozzáadja az e-mailt vagy megjeleníti a játékos email-jét + detailedDescription: Az AuthMeReloaded e-mail parancs alapja. + email.show: + description: E-mail megjelenítése + detailedDescription: Jelenlegi e-mail megjelenítése. + email.add: + description: E-mail hozzáadása + detailedDescription: Új e-mail üzenet hozzáadása. + arg1: + label: email + description: E-mail cím + arg2: + label: emailMegerősítés + description: Az e-mail cím ellenőrzése + email.change: + description: E-mail megváltoztatása + detailedDescription: Megváltoztatja a fiók e-mail címét. + arg1: + label: RégiEmail + description: Korábbi e-mail cím + arg2: + label: újEmail + description: Új e-mail cím + email.recover: + description: Jelszó lekérése e-mail alapján + detailedDescription: Visszaszerezheted a fiókod e-mailben, hogy új jelszót küldjön. + arg1: + label: email + description: E-mail cím + email.code: + description: Kód elküldése a jelszó visszaállításához + detailedDescription: Visszaszerezheted a fiókod a kapott kód e-mailben történő elküldésével. + arg1: + label: kód + description: Helyreállítási kód + email.setpassword: + description: Jelszó beállítása + detailedDescription: Új jelszó beállítása a fiók teljes körű visszaállítása után. + arg1: + label: jelszó + description: Új jelszó + email.help: + description: E-mail segítség + detailedDescription: Részletes segítség az /email parancshoz. + arg1: + label: konzultáció + description: A parancs vagy a lekérdezés, hogy lásd a segítséget. + login: + description: Bejelentkezés parancs + detailedDescription: Az AuthMeReloadedban használt bejelentkezési parancs. + arg1: + label: jelszó + description: Bejelentkezés jelszó + login.help: + description: Bejelentkezési segítség + detailedDescription: Részletes segítség a /login parancsról. + arg1: + label: konzultáció + description: A parancs vagy a lekérdezés, hogy lásd a segítséget. + logout: + description: Kijelentkezés parancs + detailedDescription: Az AuthMeReloaded által használt kijelentkezés parancs. + logout.help: + description: Kijelentkezési segítség + detailedDescription: Részletes segítség a /logout parancsról. + arg1: + label: konzultáció + description: A parancs vagy a lekérdezés, hogy lásd a segítséget. + register: + description: Fiók regisztrálása + detailedDescription: Parancs az AuthMeReloaded által használt fiók regisztrálásához. + arg1: + label: jelszó + description: Jelszó + arg2: + label: jelszóMegerősítő + description: Jelszó megerősítése + register.help: + description: Regisztrálási segítség + detailedDescription: Részletes segítség a /register parancsról. + arg1: + label: konzultáció + description: A parancs vagy a lekérdezés, hogy lássuk a segítséget. + unregister: + description: Fiók törlése + detailedDescription: Parancs az AuthMeReloaded által használt regisztrált fiók törléséhez. + arg1: + label: jelszó + description: Jelszó + unregister.help: + description: Fiók törlési segítség + detailedDescription: Részletes segítség az /unregister parancsról. + arg1: + label: konzultáció + description: A parancs vagy a lekérdezés, hogy lássuk a segítséget. + changepassword: + description: Fiók jelszó módosítása + detailedDescription: Parancs az AuthMeReloaded által használt jelszó megváltoztatásához. + arg1: + label: régiJelszó + description: Régi jelszó + arg2: + label: újJelszó + description: Új jelszó + changepassword.help: + description: Jelszó változtatási segítség + detailedDescription: Részletes segítség a /changepassword parancsról. + arg1: + label: konzultáció + description: A parancs vagy a lekérdezés, hogy lássuk a segítséget. + totp: + description: Kétfaktoros hitelesítéssel kapcsolatos műveleteket végez + detailedDescription: Elvégzi a kétfaktoros hitelesítéssel kapcsolatos műveleteket. + totp.code: + description: Feldolgozza a kétfaktorú hitelesítési kódot bejelentkezés során + detailedDescription: A bejelentkezés során feldolgozza a kétfaktorú hitelesítési kódot. + arg1: + label: kód + description: Kód + totp.add: + description: Fiók kétütemű hitelesítése + detailedDescription: Engedélyezi a fiók kétütemű hitelesítését. + totp.confirm: + description: TOTP titkokat visszaigazolás után ment + detailedDescription: A létrehozott TOTP titkokat a visszaigazolás után elmenti. + arg1: + label: kód + description: Kód + totp.remove: + description: Fiók kétütemű hitelesítés letiltása + detailedDescription: Letiltja a fiók kétütemű hitelesítését. + arg1: + label: kód + description: Kód + totp.help: + description: /totp parancsok részletes segítsége + detailedDescription: Megtekinti a /totp parancsok részletes segítségét. + arg1: + label: kérdés + description: Kérdés + captcha: + description: Captcha parancs + detailedDescription: Captcha parancs az AuthMeReloaded számára. + arg1: + label: captcha + description: A captcha + captcha.help: + description: Segítség megjelenítése + detailedDescription: Megmutassa a /captcha parancs részletes segítségét. + arg1: + label: konzultáció + description: A parancs vagy a lekérdezés, hogy lásd a segítséget. + verification: + description: AuthMeReloaded hitelesítési folyamatának befejezése + detailedDescription: Parancs az AuthMeReloaded hitelesítési folyamatának befejezéséhez. + arg1: + label: kód + description: Kód + verification.help: + description: Részletes segítség a /verification parancshoz + detailedDescription: Részletes segítséget mutat a /verification parancshoz. + arg1: + label: kérdés + description: Kérdés diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml index 784de68fa..8ab9ee080 100644 --- a/src/main/resources/messages/messages_es.yml +++ b/src/main/resources/messages/messages_es.yml @@ -17,8 +17,8 @@ registration: # Password errors on registration password: match_error: '&fLas contraseñas no son iguales' - name_in_password: '&cNo puedes utilizar tu nombre como contraseña, por favor elige otra...' - unsafe_password: '&cLa contraseña elejida no es segura, por favor elige otra...' + name_in_password: '&cNo puedes utilizar tu nombre como contraseña. Por favor, elija otra...' + unsafe_password: '&cLa contraseña elegida no es segura, por favor elija otra...' forbidden_characters: '&cTu contraseña tiene carácteres no admitidos, los cuales son: %valid_chars' wrong_length: '&fTu contraseña es muy larga o muy corta' @@ -146,8 +146,8 @@ time: # Two-factor authentication two_factor: code_created: '&2Tu código secreto es %code. Lo puedes escanear desde aquí %url' - confirmation_required: ' Por favor confirma tu código con /2fa confirm ' - code_required: 'Por favor envía tu código de atenticación de dos factores con /2fa code ' + confirmation_required: 'Por favor, confirma tu código con /2fa confirm ' + code_required: 'Por favor, envía tu código de atenticación de dos factores con /2fa code ' already_enabled: '¡La autenticación de dos factores ha sido habilitada para tu cuenta!' enable_error_no_code: 'No se ha generado ninguna clave o código 2fa o ha expirado. Por favor usa /2fa add' enable_success: 'Se ha habilitado correctamente la autenticación de dos factores para tu cuenta' diff --git a/src/main/resources/messages/messages_hu.yml b/src/main/resources/messages/messages_hu.yml index 4f2fe6020..e1570ad3f 100644 --- a/src/main/resources/messages/messages_hu.yml +++ b/src/main/resources/messages/messages_hu.yml @@ -11,7 +11,7 @@ registration: command_usage: '&cHasználat: "&7/register &c".' reg_only: '&4Csak a regisztrált játékosok tudnak csatlakozni a szerverhez!' success: '&aA regisztráció sikeres!' - kicked_admin_registered: 'Az adminisztrátor által regisztrálva lettél. Kérlek, lépj be újra a szerverbe!' + kicked_admin_registered: 'Adminisztrátor által regisztrálva lettél. Kérlek, lépj be újra a szerverbe!' # Password errors on registration password: @@ -78,7 +78,7 @@ on_join_validation: country_banned: '&4Az országod a tiltólistán van ezen a szerveren!' not_owner_error: 'Ez nem a te felhasználód. Kérlek, válassz másik nevet!' invalid_name_case: '%valid a felhasználó neved nem? Akkor ne %invalid névvel próbálj feljönni.' - # TODO quick_command: 'You used a command too fast! Please, join the server again and wait more before using any command.' + quick_command: 'Túl gyorsan használtad a parancsot! Kérjük, csatlakoz a szerverhez, és várj, amíg bármilyen parancsot használsz.' # Email email: @@ -89,11 +89,11 @@ email: old_email_invalid: '&cHibás a régi email cím, próbáld újra!' invalid: '&cHibás az email cím, próbáld újra!' added: '&2Az email címed rögzítése sikeresen megtörtént!' - # TODO add_not_allowed: '&cAdding email was not allowed' + add_not_allowed: '&cAz email hozzáadása nem engedélyezett' request_confirmation: '&cKérlek, ellenőrízd az email címedet!' changed: '&2Az email cím cseréje sikeresen megtörtént!' - # TODO change_not_allowed: '&cChanging email was not allowed' - email_show: '&2A jelenlegi email-ed a következő: &f%email' + change_not_allowed: '&cAz emailek módosítása nem engedélyezett' + email_show: '&2A jelenlegi emailed a következő: &f%email' no_email_for_account: '&2Ehhez a felhasználóhoz jelenleg még nincs email hozzárendelve.' already_used: '&4Ez az email cím már használatban van!' incomplete_settings: 'Hiba: nem lett beállítva az összes szükséges beállítás az email küldéshez. Vedd fel a kapcsolatot egy adminnal.' @@ -118,18 +118,18 @@ captcha: usage_captcha: '&3A bejelentkezéshez CAPTCHA szükséges, kérlek, használd a következő parancsot: "&7/captcha %captcha_code&3".' wrong_captcha: '&cHibás CAPTCHA, kérlek, írd be a következő parancsot: "&7/captcha %captcha_code&c"!' valid_captcha: '&2A CAPTCHA sikeresen feloldva!' - # TODO captcha_for_registration: 'To register you have to solve a captcha first, please use the command: /captcha %captcha_code' - # TODO register_captcha_valid: '&2Valid captcha! You may now register with /register' + captcha_for_registration: 'A regisztrációhoz meg kell oldanod a captcha-t, kérjük, használd a parancsot: /captcha %captcha_code' + register_captcha_valid: '&2Érvényes captcha! Most regisztrálhatsz a /register paranccsal.' # Verification code verification: - code_required: '&3Ez a parancs érzékeny, és e-mailes igazolást igényel! Ellenőrizze a bejövő postafiókot, és kövesse az e-mail utasításait.' + code_required: '&3Ez a parancs érzékeny, és emailes igazolást igényel! Ellenőrizze a bejövő postafiókot, és kövesse az email utasításait.' command_usage: '&cHasználat: /verification ' - incorrect_code: '&cHelytelen kód, írd be "/verification " be a chatbe, az e-mailben kapott kód használatával' + incorrect_code: '&cHelytelen kód, írd be "/verification " be a chatbe, az emailben kapott kód használatával' success: '&2Az Ön személyazonosságát ellenőrizték! Mostantól végrehajthatja az összes parancsot az aktuális munkamenetben!' already_verified: '&2Már minden érzékeny parancsot végrehajthat az aktuális munkameneten belül!' code_expired: '&3A kód lejárt! Végezzen el egy másik érzékeny parancsot, hogy új kódot kapjon!' - email_needed: '&3A személyazonosságának igazolásához e-mail címet kell csatolnia fiókjához!' + email_needed: '&3A személyazonosságának igazolásához email címet kell csatolnia fiókjához!' # Time units time: @@ -145,12 +145,12 @@ time: # Two-factor authentication two_factor: code_created: '&2A titkos kódod a következő: %code. Vagy skenneld be a következő oldalról: %url' - # TODO confirmation_required: 'Please confirm your code with /2fa confirm ' - # TODO code_required: 'Please submit your two-factor authentication code with /2fa code ' - # TODO already_enabled: 'Two-factor authentication is already enabled for your account!' - # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add' - # TODO enable_success: 'Successfully enabled two-factor authentication for your account' - # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add' - # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add' - # TODO removed_success: 'Successfully removed two-factor auth from your account' - # TODO invalid_code: 'Invalid code!' + confirmation_required: 'Kérjük, erősítsd meg a kódot /2fa confirm ' + code_required: 'Kérjük, küldd el a kétütemű hitelesítési kódot /2fa code ' + already_enabled: 'Kétszámjegyű hitelesítés már engedélyezve van a fiókodban!' + enable_error_no_code: 'Nem hoztad létre a 2fa kulcsot számodra, vagy lejárt. Kérlek, futtasd a /2fa add' + enable_success: 'Sikeresen engedélyezted a fiók kétütemű hitelesítését' + enable_error_wrong_code: 'Hibás kód vagy a kód lejárt. Futtasd a /2fa add' + not_enabled_error: 'Kétszámjegyű hitelesítés nincs engedélyezve a fiókodban. Futtasd a /2fa add' + removed_success: 'Sikeresen eltávolítottad a fiók két számjegyű hitelesítőjét' + invalid_code: 'Érvénytelen kód!' diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 7b7f44ac4..c5347e2fc 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -168,6 +168,9 @@ permissions: authme.allowmultipleaccounts: description: Permission to be able to register multiple accounts. default: op + authme.bypassbungeesend: + description: Permission node to bypass BungeeCord server teleportation. + default: op authme.bypassantibot: description: Permission node to bypass AntiBot protection. default: op diff --git a/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java b/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java index 6d1770d4e..887bf6d09 100644 --- a/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java +++ b/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java @@ -41,6 +41,7 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; /** @@ -91,7 +92,7 @@ public class AuthMeInitializationTest { public void shouldInitializeAllServices() { // given Settings settings = - new Settings(dataFolder, mock(PropertyResource.class), null, buildConfigurationData()); + new Settings(dataFolder, mock(PropertyResource.class, RETURNS_DEEP_STUBS), null, buildConfigurationData()); Injector injector = new InjectorBuilder() .addDefaultHandlers("fr.xephi.authme") diff --git a/src/test/java/fr/xephi/authme/IsEqualByReflectionMatcher.java b/src/test/java/fr/xephi/authme/IsEqualByReflectionMatcher.java index 55df85579..2acf5ee70 100644 --- a/src/test/java/fr/xephi/authme/IsEqualByReflectionMatcher.java +++ b/src/test/java/fr/xephi/authme/IsEqualByReflectionMatcher.java @@ -45,6 +45,13 @@ public final class IsEqualByReflectionMatcher extends TypeSafeMatcher { description.appendText("parameters " + expected); } + /** + * Checks that all fields of the given {@code item} are equal to the {@link #expected} object. Both objects must + * be exactly of the same type. + * + * @param item the object to check + * @return true if all fields are equal, false otherwise + */ private boolean assertAreFieldsEqual(T item) { if (expected.getClass() != item.getClass()) { fail("Classes don't match, got " + expected.getClass().getSimpleName() diff --git a/src/test/java/fr/xephi/authme/ReflectionTestUtils.java b/src/test/java/fr/xephi/authme/ReflectionTestUtils.java index 963fbf71d..69a1e1d99 100644 --- a/src/test/java/fr/xephi/authme/ReflectionTestUtils.java +++ b/src/test/java/fr/xephi/authme/ReflectionTestUtils.java @@ -25,13 +25,14 @@ public final class ReflectionTestUtils { * @param instance the instance to modify (pass null for static fields) * @param fieldName the field name * @param value the value to set the field to + * @param the instance type */ public static void setField(Class clazz, T instance, String fieldName, Object value) { try { Field field = getField(clazz, fieldName); field.set(instance, value); } catch (IllegalAccessException e) { - throw new UnsupportedOperationException( + throw new IllegalStateException( format("Could not set value to field '%s' for instance '%s' of class '%s'", fieldName, instance, clazz.getName()), e); } @@ -55,7 +56,7 @@ public final class ReflectionTestUtils { field.setAccessible(true); return field; } catch (NoSuchFieldException e) { - throw new UnsupportedOperationException(format("Could not get field '%s' from class '%s'", + throw new IllegalStateException(format("Could not get field '%s' from class '%s'", fieldName, clazz.getName()), e); } } @@ -66,13 +67,21 @@ public final class ReflectionTestUtils { return getFieldValue(field, instance); } + /** + * Returns the value of the field on the given instance. Wraps exceptions into a runtime exception. + * + * @param field the field to read + * @param instance the instance to get the value from, null if field is static + * @param type of the field + * @return value of the field + */ @SuppressWarnings("unchecked") public static V getFieldValue(Field field, Object instance) { field.setAccessible(true); try { return (V) field.get(instance); } catch (IllegalAccessException e) { - throw new UnsupportedOperationException("Could not get value of field '" + field.getName() + "'", e); + throw new IllegalStateException("Could not get value of field '" + field.getName() + "'", e); } } @@ -90,7 +99,7 @@ public final class ReflectionTestUtils { method.setAccessible(true); return method; } catch (NoSuchMethodException e) { - throw new UnsupportedOperationException("Could not retrieve method '" + methodName + "' from class '" + throw new IllegalStateException("Could not retrieve method '" + methodName + "' from class '" + clazz.getName() + "'"); } } @@ -110,7 +119,7 @@ public final class ReflectionTestUtils { try { return (V) method.invoke(instance, parameters); } catch (InvocationTargetException | IllegalAccessException e) { - throw new UnsupportedOperationException("Could not invoke method '" + method + "'", e); + throw new IllegalStateException("Could not invoke method '" + method + "'", e); } } @@ -138,7 +147,7 @@ public final class ReflectionTestUtils { constructor.setAccessible(true); return constructor.newInstance(); } catch (ReflectiveOperationException e) { - throw new UnsupportedOperationException("Could not invoke no-args constructor of class " + clazz, e); + throw new IllegalStateException("Could not invoke no-args constructor of class " + clazz, e); } } } diff --git a/src/test/java/fr/xephi/authme/command/TestCommandsUtil.java b/src/test/java/fr/xephi/authme/command/TestCommandsUtil.java index 111eb088d..c7a2d1d3f 100644 --- a/src/test/java/fr/xephi/authme/command/TestCommandsUtil.java +++ b/src/test/java/fr/xephi/authme/command/TestCommandsUtil.java @@ -84,7 +84,8 @@ public final class TestCommandsUtil { /* Shortcut command to initialize a new test command. */ private static CommandDescription createCommand(PermissionNode permission, CommandDescription parent, - List labels, Class commandClass, + List labels, + Class commandClass, CommandArgumentDescription... arguments) { CommandDescription.CommandBuilder command = CommandDescription.builder() .labels(labels) diff --git a/src/test/java/fr/xephi/authme/command/executable/verification/VerificationCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/verification/VerificationCommandTest.java index 53ede2d89..d4b38f1ad 100644 --- a/src/test/java/fr/xephi/authme/command/executable/verification/VerificationCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/verification/VerificationCommandTest.java @@ -1,9 +1,11 @@ package fr.xephi.authme.command.executable.verification; +import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.VerificationCodeManager; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; import org.bukkit.entity.Player; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -33,6 +35,11 @@ public class VerificationCommandTest { @Mock private VerificationCodeManager codeManager; + @BeforeClass + public static void setUpLogger() { + TestHelper.setupLogger(); + } + @Test public void shouldDetectIfMailHasASetup() { // given diff --git a/src/test/java/fr/xephi/authme/command/help/HelpMessagesConsistencyTest.java b/src/test/java/fr/xephi/authme/command/help/HelpMessagesConsistencyTest.java index 5e79ef993..1c4f7542f 100644 --- a/src/test/java/fr/xephi/authme/command/help/HelpMessagesConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/command/help/HelpMessagesConsistencyTest.java @@ -1,5 +1,7 @@ package fr.xephi.authme.command.help; +import ch.jalu.configme.resource.PropertyReader; +import ch.jalu.configme.resource.YamlFileReader; import fr.xephi.authme.TestHelper; import fr.xephi.authme.command.CommandDescription; import fr.xephi.authme.command.CommandInitializer; @@ -61,16 +63,16 @@ public class HelpMessagesConsistencyTest { @Test public void shouldHaveEntryForEachHelpMessageKey() { // given - FileConfiguration configuration = YamlConfiguration.loadConfiguration(DEFAULT_MESSAGES_FILE); + PropertyReader reader = new YamlFileReader(DEFAULT_MESSAGES_FILE); // when / then for (HelpMessage message : HelpMessage.values()) { - assertThat("Default configuration has entry for message '" + message + "'", - configuration.contains(message.getKey()), equalTo(true)); + assertThat("Default configuration should have entry for message '" + message + "'", + reader.contains(message.getKey()), equalTo(true)); } for (HelpSection section : HelpSection.values()) { - assertThat("Default configuration has entry for section '" + section + "'", - configuration.contains(section.getKey()), equalTo(true)); + assertThat("Default configuration should have entry for section '" + section + "'", + reader.contains(section.getKey()), equalTo(true)); } } diff --git a/src/test/java/fr/xephi/authme/datasource/PostgreSqlIntegrationTest.java b/src/test/java/fr/xephi/authme/datasource/PostgreSqlIntegrationTest.java new file mode 100644 index 000000000..54a00bf40 --- /dev/null +++ b/src/test/java/fr/xephi/authme/datasource/PostgreSqlIntegrationTest.java @@ -0,0 +1,92 @@ +package fr.xephi.authme.datasource; + +import ch.jalu.configme.properties.Property; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.DatabaseSettings; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Integration test for {@link PostgreSqlDataSource}. + */ +public class PostgreSqlIntegrationTest extends AbstractDataSourceIntegrationTest { + + /** Mock of a settings instance. */ + private static Settings settings; + /** SQL statement to execute before running a test. */ + private static String sqlInitialize; + /** Connection to the H2 test database. */ + private HikariDataSource hikariSource; + + /** + * Set up the settings mock to return specific values for database settings and load {@link #sqlInitialize}. + */ + @BeforeClass + public static void initializeSettings() throws IOException, ClassNotFoundException { + // Check that we have an H2 driver + Class.forName("org.h2.jdbcx.JdbcDataSource"); + + settings = mock(Settings.class); + TestHelper.returnDefaultsForAllProperties(settings); + set(DatabaseSettings.MYSQL_DATABASE, "h2_test"); + set(DatabaseSettings.MYSQL_TABLE, "authme"); + TestHelper.setRealLogger(); + + Path sqlInitFile = TestHelper.getJarPath(TestHelper.PROJECT_ROOT + "datasource/sql-initialize.sql"); + sqlInitialize = new String(Files.readAllBytes(sqlInitFile)); + } + + @Before + public void initializeConnectionAndTable() throws SQLException { + HikariConfig config = new HikariConfig(); + config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource"); + config.setConnectionTestQuery("VALUES 1"); + config.addDataSourceProperty("URL", "jdbc:h2:mem:test;ignorecase=true"); + config.addDataSourceProperty("user", "sa"); + config.addDataSourceProperty("password", "sa"); + HikariDataSource ds = new HikariDataSource(config); + Connection connection = ds.getConnection(); + + try (Statement st = connection.createStatement()) { + st.execute("DROP TABLE IF EXISTS authme"); + st.execute(sqlInitialize); + } + hikariSource = ds; + } + + @After + public void closeConnection() { + silentClose(hikariSource); + } + + @Override + protected DataSource getDataSource(String saltColumn) { + when(settings.getProperty(DatabaseSettings.MYSQL_COL_SALT)).thenReturn(saltColumn); + return SqlDataSourceTestUtil.createPostgres(settings, hikariSource); + } + + private static void set(Property property, T value) { + when(settings.getProperty(property)).thenReturn(value); + } + + private static void silentClose(HikariDataSource con) { + if (con != null && !con.isClosed()) { + con.close(); + } + } + +} diff --git a/src/test/java/fr/xephi/authme/datasource/PostgreSqlResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/PostgreSqlResourceClosingTest.java new file mode 100644 index 000000000..40a1d391f --- /dev/null +++ b/src/test/java/fr/xephi/authme/datasource/PostgreSqlResourceClosingTest.java @@ -0,0 +1,33 @@ +package fr.xephi.authme.datasource; + +import com.zaxxer.hikari.HikariDataSource; +import fr.xephi.authme.datasource.mysqlextensions.MySqlExtension; +import fr.xephi.authme.datasource.mysqlextensions.MySqlExtensionsFactory; +import fr.xephi.authme.settings.Settings; + +import java.lang.reflect.Method; +import java.sql.Connection; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Resource closing test for {@link PostgreSqlDataSource}. + */ +public class PostgreSqlResourceClosingTest extends AbstractSqlDataSourceResourceClosingTest { + + public PostgreSqlResourceClosingTest(Method method, String name) { + super(method, name); + } + + @Override + protected DataSource createDataSource(Settings settings, Connection connection) throws Exception { + HikariDataSource hikariDataSource = mock(HikariDataSource.class); + given(hikariDataSource.getConnection()).willReturn(connection); + MySqlExtensionsFactory extensionsFactory = mock(MySqlExtensionsFactory.class); + given(extensionsFactory.buildExtension(any(Columns.class))).willReturn(mock(MySqlExtension.class)); + return new PostgreSqlDataSource(settings, hikariDataSource, extensionsFactory); + } + +} diff --git a/src/test/java/fr/xephi/authme/datasource/SqlDataSourceTestUtil.java b/src/test/java/fr/xephi/authme/datasource/SqlDataSourceTestUtil.java index dd54e39a4..48ecb8b1d 100644 --- a/src/test/java/fr/xephi/authme/datasource/SqlDataSourceTestUtil.java +++ b/src/test/java/fr/xephi/authme/datasource/SqlDataSourceTestUtil.java @@ -27,6 +27,12 @@ public final class SqlDataSourceTestUtil { return new MySQL(settings, hikariDataSource, extensionsFactory); } + public static PostgreSqlDataSource createPostgres(Settings settings, HikariDataSource hikariDataSource) { + MySqlExtensionsFactory extensionsFactory = mock(MySqlExtensionsFactory.class); + given(extensionsFactory.buildExtension(any())).willReturn(mock(MySqlExtension.class)); + return new PostgreSqlDataSource(settings, hikariDataSource, extensionsFactory); + } + /** * Creates a SQLite implementation for testing purposes. Methods are overridden so the * provided connection is never overridden. diff --git a/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java b/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java index 59d837a9c..59eae81a9 100644 --- a/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java +++ b/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java @@ -57,6 +57,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import static com.google.common.collect.Sets.newHashSet; import static fr.xephi.authme.listener.EventCancelVerifier.withServiceMock; import static fr.xephi.authme.service.BukkitServiceTestHelper.setBukkitServiceToScheduleSyncDelayedTaskWithDelay; import static org.hamcrest.Matchers.contains; @@ -206,8 +207,7 @@ public class PlayerListenerTest { public void shouldNotStopAllowedCommand() { // given given(settings.getProperty(HooksSettings.USE_ESSENTIALS_MOTD)).willReturn(true); - given(settings.getProperty(RestrictionSettings.ALLOW_COMMANDS)) - .willReturn(Arrays.asList("/plugins", "/mail", "/msg")); + given(settings.getProperty(RestrictionSettings.ALLOW_COMMANDS)).willReturn(newHashSet("/plugins", "/mail", "/msg")); PlayerCommandPreprocessEvent event = mockCommandEvent("/Mail send test Test"); // when @@ -222,7 +222,7 @@ public class PlayerListenerTest { public void shouldNotCancelEventForAuthenticatedPlayer() { // given given(settings.getProperty(HooksSettings.USE_ESSENTIALS_MOTD)).willReturn(false); - given(settings.getProperty(RestrictionSettings.ALLOW_COMMANDS)).willReturn(Collections.emptyList()); + given(settings.getProperty(RestrictionSettings.ALLOW_COMMANDS)).willReturn(Collections.emptySet()); Player player = playerWithMockedServer(); // PlayerCommandPreprocessEvent#getPlayer is final, so create a spy instead of a mock PlayerCommandPreprocessEvent event = spy(new PlayerCommandPreprocessEvent(player, "/hub")); @@ -243,7 +243,7 @@ public class PlayerListenerTest { public void shouldCancelCommandEvent() { // given given(settings.getProperty(HooksSettings.USE_ESSENTIALS_MOTD)).willReturn(false); - given(settings.getProperty(RestrictionSettings.ALLOW_COMMANDS)).willReturn(Arrays.asList("/spawn", "/help")); + given(settings.getProperty(RestrictionSettings.ALLOW_COMMANDS)).willReturn(newHashSet("/spawn", "/help")); Player player = playerWithMockedServer(); PlayerCommandPreprocessEvent event = spy(new PlayerCommandPreprocessEvent(player, "/hub")); given(listenerService.shouldCancelEvent(player)).willReturn(true); @@ -262,7 +262,7 @@ public class PlayerListenerTest { public void shouldCancelFastCommandEvent() { // given given(settings.getProperty(HooksSettings.USE_ESSENTIALS_MOTD)).willReturn(false); - given(settings.getProperty(RestrictionSettings.ALLOW_COMMANDS)).willReturn(Arrays.asList("/spawn", "/help")); + given(settings.getProperty(RestrictionSettings.ALLOW_COMMANDS)).willReturn(newHashSet("/spawn", "/help")); Player player = playerWithMockedServer(); PlayerCommandPreprocessEvent event = spy(new PlayerCommandPreprocessEvent(player, "/hub")); given(quickCommandsProtectionManager.isAllowed(player.getName())).willReturn(false); diff --git a/src/test/java/fr/xephi/authme/message/HelpMessageConsistencyTest.java b/src/test/java/fr/xephi/authme/message/HelpMessageConsistencyTest.java index 399fd93a7..83db38d2f 100644 --- a/src/test/java/fr/xephi/authme/message/HelpMessageConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/message/HelpMessageConsistencyTest.java @@ -1,11 +1,11 @@ package fr.xephi.authme.message; +import ch.jalu.configme.resource.PropertyReader; +import ch.jalu.configme.resource.YamlFileReader; import fr.xephi.authme.TestHelper; import fr.xephi.authme.command.help.HelpMessage; import fr.xephi.authme.command.help.HelpSection; import fr.xephi.authme.permission.DefaultPermission; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; import org.hamcrest.Matcher; import org.junit.Before; import org.junit.Test; @@ -49,27 +49,27 @@ public class HelpMessageConsistencyTest { public void shouldHaveRequiredEntries() { for (File file : helpFiles) { // given - FileConfiguration configuration = YamlConfiguration.loadConfiguration(file); + PropertyReader reader = new YamlFileReader(file); // when / then - assertHasAllHelpSectionEntries(file.getName(), configuration); + assertHasAllHelpSectionEntries(file.getName(), reader); } } - private void assertHasAllHelpSectionEntries(String filename, FileConfiguration configuration) { + private void assertHasAllHelpSectionEntries(String filename, PropertyReader reader) { for (HelpSection section : HelpSection.values()) { assertThat(filename + " should have entry for HelpSection '" + section + "'", - configuration.getString(section.getKey()), notEmptyString()); + reader.getString(section.getKey()), notEmptyString()); } for (HelpMessage message : HelpMessage.values()) { assertThat(filename + " should have entry for HelpMessage '" + message + "'", - configuration.getString(message.getKey()), notEmptyString()); + reader.getString(message.getKey()), notEmptyString()); } for (DefaultPermission defaultPermission : DefaultPermission.values()) { assertThat(filename + " should have entry for DefaultPermission '" + defaultPermission + "'", - configuration.getString(getPathForDefaultPermission(defaultPermission)), notEmptyString()); + reader.getString(getPathForDefaultPermission(defaultPermission)), notEmptyString()); } } diff --git a/src/test/java/fr/xephi/authme/message/MessageFilePlaceholderTest.java b/src/test/java/fr/xephi/authme/message/MessageFilePlaceholderTest.java index 4e692c18d..f7b8596d0 100644 --- a/src/test/java/fr/xephi/authme/message/MessageFilePlaceholderTest.java +++ b/src/test/java/fr/xephi/authme/message/MessageFilePlaceholderTest.java @@ -1,10 +1,10 @@ package fr.xephi.authme.message; +import ch.jalu.configme.resource.PropertyReader; +import ch.jalu.configme.resource.YamlFileReader; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import fr.xephi.authme.TestHelper; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -51,12 +51,12 @@ public class MessageFilePlaceholderTest { @Test public void shouldHaveAllPlaceholders() { // given - FileConfiguration configuration = YamlConfiguration.loadConfiguration(messagesFile); - List errors = new ArrayList<>(); + PropertyReader reader = new YamlFileReader(messagesFile); + List errors = new ArrayList<>(0); // when for (MessageKey key : MessageKey.values()) { - List missingTags = findMissingTags(key, configuration); + List missingTags = findMissingTags(key, reader); if (!missingTags.isEmpty()) { errors.add("Message key '" + key + "' should have tags: " + String.join(", ", missingTags)); } @@ -68,9 +68,9 @@ public class MessageFilePlaceholderTest { } } - private List findMissingTags(MessageKey key, FileConfiguration configuration) { - if (key.getTags().length > 0 && configuration.contains(key.getKey())) { - String message = configuration.getString(key.getKey()); + private List findMissingTags(MessageKey key, PropertyReader reader) { + if (key.getTags().length > 0 && reader.contains(key.getKey())) { + String message = reader.getString(key.getKey()); return Arrays.stream(key.getTags()) .filter(tag -> !EXCLUSIONS.get(key).contains(tag) && !message.contains(tag)) .collect(Collectors.toList()); diff --git a/src/test/java/fr/xephi/authme/message/MessagesFileConsistencyTest.java b/src/test/java/fr/xephi/authme/message/MessagesFileConsistencyTest.java index 9e16994da..c1362dd61 100644 --- a/src/test/java/fr/xephi/authme/message/MessagesFileConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/message/MessagesFileConsistencyTest.java @@ -1,9 +1,9 @@ package fr.xephi.authme.message; +import ch.jalu.configme.resource.PropertyReader; +import ch.jalu.configme.resource.YamlFileReader; import fr.xephi.authme.TestHelper; import fr.xephi.authme.util.StringUtils; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; import org.junit.Test; import java.io.File; @@ -24,10 +24,10 @@ public class MessagesFileConsistencyTest { @Test public void shouldHaveAllMessages() { File file = TestHelper.getJarFile(MESSAGES_FILE); - FileConfiguration configuration = YamlConfiguration.loadConfiguration(file); + PropertyReader reader = new YamlFileReader(file); List errors = new ArrayList<>(); for (MessageKey messageKey : MessageKey.values()) { - validateMessage(messageKey, configuration, errors); + validateMessage(messageKey, reader, errors); } if (!errors.isEmpty()) { @@ -36,9 +36,9 @@ public class MessagesFileConsistencyTest { } } - private static void validateMessage(MessageKey messageKey, FileConfiguration configuration, List errors) { + private static void validateMessage(MessageKey messageKey, PropertyReader reader, List errors) { final String key = messageKey.getKey(); - final String message = configuration.getString(key); + final String message = reader.getString(key); if (StringUtils.isEmpty(message)) { errors.add("Messages file should have message for key '" + key + "'"); diff --git a/src/test/java/fr/xephi/authme/message/YamlTextFileCheckerTest.java b/src/test/java/fr/xephi/authme/message/YamlTextFileCheckerTest.java index b04259b21..2b1820fb6 100644 --- a/src/test/java/fr/xephi/authme/message/YamlTextFileCheckerTest.java +++ b/src/test/java/fr/xephi/authme/message/YamlTextFileCheckerTest.java @@ -1,10 +1,11 @@ package fr.xephi.authme.message; +import ch.jalu.configme.resource.PropertyReader; +import ch.jalu.configme.resource.YamlFileReader; import fr.xephi.authme.TestHelper; import fr.xephi.authme.command.help.HelpSection; import fr.xephi.authme.util.ExceptionUtils; import fr.xephi.authme.util.StringUtils; -import org.bukkit.configuration.file.YamlConfiguration; import org.junit.BeforeClass; import org.junit.Test; @@ -81,8 +82,8 @@ public class YamlTextFileCheckerTest { */ private void checkFile(File file, String mandatoryKey, List errors) { try { - YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file); - if (StringUtils.isEmpty(configuration.getString(mandatoryKey))) { + PropertyReader reader = new YamlFileReader(file); + if (StringUtils.isEmpty(reader.getString(mandatoryKey))) { errors.add("Message for '" + mandatoryKey + "' is empty"); } } catch (Exception e) { diff --git a/src/test/java/fr/xephi/authme/message/updater/MessageUpdaterTest.java b/src/test/java/fr/xephi/authme/message/updater/MessageUpdaterTest.java index 254e0b1a6..cb4d03158 100644 --- a/src/test/java/fr/xephi/authme/message/updater/MessageUpdaterTest.java +++ b/src/test/java/fr/xephi/authme/message/updater/MessageUpdaterTest.java @@ -1,13 +1,11 @@ package fr.xephi.authme.message.updater; -import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.properties.Property; +import ch.jalu.configme.resource.PropertyReader; +import ch.jalu.configme.resource.YamlFileReader; import com.google.common.io.Files; -import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; import fr.xephi.authme.message.MessageKey; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @@ -16,6 +14,7 @@ import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.IOException; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -65,13 +64,13 @@ public class MessageUpdaterTest { // then assertThat(wasChanged, equalTo(true)); - FileConfiguration configuration = YamlConfiguration.loadConfiguration(messagesFile); + PropertyReader reader = new YamlFileReader(messagesFile); // Existing keys should not be overridden - assertThat(configuration.getString(MessageKey.LOGIN_SUCCESS.getKey()), equalTo("&cHere we have&bdefined some colors &dand some other <hings")); - assertThat(configuration.getString(MessageKey.EMAIL_ALREADY_USED_ERROR.getKey()), equalTo("")); + assertThat(reader.getString(MessageKey.LOGIN_SUCCESS.getKey()), equalTo("&cHere we have&bdefined some colors &dand some other <hings")); + assertThat(reader.getString(MessageKey.EMAIL_ALREADY_USED_ERROR.getKey()), equalTo("")); // Check that new keys were added - assertThat(configuration.getString(MessageKey.SECOND.getKey()), equalTo("second")); - assertThat(configuration.getString(MessageKey.ERROR.getKey()), equalTo("&4An unexpected error occurred, please contact an administrator!")); + assertThat(reader.getString(MessageKey.SECOND.getKey()), equalTo("second")); + assertThat(reader.getString(MessageKey.ERROR.getKey()), equalTo("&4An unexpected error occurred, please contact an administrator!")); } @Test @@ -85,18 +84,18 @@ public class MessageUpdaterTest { // then assertThat(wasChanged, equalTo(true)); - FileConfiguration configuration = YamlConfiguration.loadConfiguration(messagesFile); - assertThat(configuration.getString(MessageKey.PASSWORD_MATCH_ERROR.getKey()), + PropertyReader reader = new YamlFileReader(messagesFile); + assertThat(reader.getString(MessageKey.PASSWORD_MATCH_ERROR.getKey()), equalTo("Password error message")); - assertThat(configuration.getString(MessageKey.INVALID_NAME_CHARACTERS.getKey()), + assertThat(reader.getString(MessageKey.INVALID_NAME_CHARACTERS.getKey()), equalTo("not valid username: Allowed chars are %valid_chars")); - assertThat(configuration.getString(MessageKey.INVALID_OLD_EMAIL.getKey()), + assertThat(reader.getString(MessageKey.INVALID_OLD_EMAIL.getKey()), equalTo("Email (old) is not valid!!")); - assertThat(configuration.getString(MessageKey.CAPTCHA_WRONG_ERROR.getKey()), + assertThat(reader.getString(MessageKey.CAPTCHA_WRONG_ERROR.getKey()), equalTo("The captcha code is %captcha_code for you")); - assertThat(configuration.getString(MessageKey.CAPTCHA_FOR_REGISTRATION_REQUIRED.getKey()), + assertThat(reader.getString(MessageKey.CAPTCHA_FOR_REGISTRATION_REQUIRED.getKey()), equalTo("Now type /captcha %captcha_code")); - assertThat(configuration.getString(MessageKey.SECONDS.getKey()), + assertThat(reader.getString(MessageKey.SECONDS.getKey()), equalTo("seconds in plural")); } @@ -111,10 +110,10 @@ public class MessageUpdaterTest { // then assertThat(wasChanged, equalTo(true)); - FileConfiguration configuration = YamlConfiguration.loadConfiguration(messagesFile); - assertThat(configuration.getString(MessageKey.TWO_FACTOR_CREATE.getKey()), equalTo("Old 2fa create text")); - assertThat(configuration.getString(MessageKey.WRONG_PASSWORD.getKey()), equalTo("test2 - wrong password")); // from pre-5.5 key - assertThat(configuration.getString(MessageKey.SECOND.getKey()), equalTo("second")); // from messages_en.yml + PropertyReader reader = new YamlFileReader(messagesFile); + assertThat(reader.getString(MessageKey.TWO_FACTOR_CREATE.getKey()), equalTo("Old 2fa create text")); + assertThat(reader.getString(MessageKey.WRONG_PASSWORD.getKey()), equalTo("test2 - wrong password")); // from pre-5.5 key + assertThat(reader.getString(MessageKey.SECOND.getKey()), equalTo("second")); // from messages_en.yml } @Test @@ -125,7 +124,7 @@ public class MessageUpdaterTest { .collect(Collectors.toSet()); // when - Set messageKeysFromConfigData = MessageUpdater.getConfigurationData().getProperties().stream() + Set messageKeysFromConfigData = MessageUpdater.createConfigurationData().getProperties().stream() .map(Property::getPath) .collect(Collectors.toSet()); @@ -141,8 +140,7 @@ public class MessageUpdaterTest { .collect(Collectors.toSet()); // when - Map comments = ReflectionTestUtils.getFieldValue( - ConfigurationData.class, MessageUpdater.getConfigurationData(), "sectionComments"); + Map> comments = MessageUpdater.createConfigurationData().getAllComments(); // then assertThat(comments.keySet(), equalTo(rootPaths)); diff --git a/src/test/java/fr/xephi/authme/message/updater/MigraterYamlFileResourceTest.java b/src/test/java/fr/xephi/authme/message/updater/MigraterYamlFileResourceTest.java index f6e6dcd2d..03d79e71f 100644 --- a/src/test/java/fr/xephi/authme/message/updater/MigraterYamlFileResourceTest.java +++ b/src/test/java/fr/xephi/authme/message/updater/MigraterYamlFileResourceTest.java @@ -1,8 +1,10 @@ package fr.xephi.authme.message.updater; import ch.jalu.configme.configurationdata.ConfigurationData; +import ch.jalu.configme.configurationdata.ConfigurationDataBuilder; import ch.jalu.configme.properties.Property; import ch.jalu.configme.properties.StringProperty; +import ch.jalu.configme.resource.PropertyReader; import com.google.common.io.Files; import fr.xephi.authme.TestHelper; import org.junit.Rule; @@ -31,14 +33,15 @@ public class MigraterYamlFileResourceTest { public void shouldReadChineseFile() { // given File file = TestHelper.getJarFile(CHINESE_MESSAGES_FILE); - - // when MigraterYamlFileResource resource = new MigraterYamlFileResource(file); + // when + PropertyReader reader = resource.createReader(); + // then - assertThat(resource.getString("first"), equalTo("错误的密码")); - assertThat(resource.getString("second"), equalTo("为了验证您的身份,您需要将一个电子邮件地址与您的帐户绑定!")); - assertThat(resource.getString("third"), equalTo("您已经可以在当前会话中执行任何敏感命令!")); + assertThat(reader.getString("first"), equalTo("错误的密码")); + assertThat(reader.getString("second"), equalTo("为了验证您的身份,您需要将一个电子邮件地址与您的帐户绑定!")); + assertThat(reader.getString("third"), equalTo("您已经可以在当前会话中执行任何敏感命令!")); } @Test @@ -47,24 +50,26 @@ public class MigraterYamlFileResourceTest { File file = temporaryFolder.newFile(); Files.copy(TestHelper.getJarFile(CHINESE_MESSAGES_FILE), file); MigraterYamlFileResource resource = new MigraterYamlFileResource(file); + ConfigurationData configurationData = buildConfigurationData(); + configurationData.initializeValues(resource.createReader()); String newMessage = "您当前并没有任何邮箱与该账号绑定"; - resource.setValue("third", newMessage); + configurationData.setValue(new StringProperty("third", ""), newMessage); // when - resource.exportProperties(buildConfigurationData()); + resource.exportProperties(configurationData); // then - resource = new MigraterYamlFileResource(file); - assertThat(resource.getString("first"), equalTo("错误的密码")); - assertThat(resource.getString("second"), equalTo("为了验证您的身份,您需要将一个电子邮件地址与您的帐户绑定!")); - assertThat(resource.getString("third"), equalTo(newMessage)); + PropertyReader reader = resource.createReader(); + assertThat(reader.getString("first"), equalTo("错误的密码")); + assertThat(reader.getString("second"), equalTo("为了验证您的身份,您需要将一个电子邮件地址与您的帐户绑定!")); + assertThat(reader.getString("third"), equalTo(newMessage)); } private static ConfigurationData buildConfigurationData() { - List> properties = Arrays.asList( + List> properties = Arrays.asList( new StringProperty("first", "first"), new StringProperty("second", "second"), new StringProperty("third", "third")); - return new ConfigurationData(properties); + return ConfigurationDataBuilder.createConfiguration(properties); } } diff --git a/src/test/java/fr/xephi/authme/security/HashUtilsTest.java b/src/test/java/fr/xephi/authme/security/HashUtilsTest.java index 440e748a2..16cba7043 100644 --- a/src/test/java/fr/xephi/authme/security/HashUtilsTest.java +++ b/src/test/java/fr/xephi/authme/security/HashUtilsTest.java @@ -117,10 +117,11 @@ public class HashUtilsTest { public void shouldCheckForValidBcryptHashStart() { // given / when / then assertThat(HashUtils.isValidBcryptHash(""), equalTo(false)); - assertThat(HashUtils.isValidBcryptHash("$2afsdaf"), equalTo(true)); assertThat(HashUtils.isValidBcryptHash("$2"), equalTo(false)); - assertThat(HashUtils.isValidBcryptHash("$2aead234adef"), equalTo(true)); assertThat(HashUtils.isValidBcryptHash("#2ae5fc78"), equalTo(false)); + assertThat(HashUtils.isValidBcryptHash("$2afsdaf"), equalTo(false)); + assertThat(HashUtils.isValidBcryptHash("$fdfasdfasdfasdfasdfasdfasdfasdfasdfasdfsadfasdfasdfasdfasdf"), equalTo(false)); + assertThat(HashUtils.isValidBcryptHash("$2y$asdfasdfasdfasdfasdfasdfasdfasdfasdfsadfasdfasdfasdfasdf"), equalTo(true)); } @Test diff --git a/src/test/java/fr/xephi/authme/security/crypts/BCrypt2yTest.java b/src/test/java/fr/xephi/authme/security/crypts/BCrypt2yTest.java index 83308c9fc..f34bac622 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/BCrypt2yTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/BCrypt2yTest.java @@ -1,5 +1,11 @@ package fr.xephi.authme.security.crypts; +import org.junit.Test; + +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertThat; + /** * Test for {@link BCrypt2y}. */ @@ -13,4 +19,16 @@ public class BCrypt2yTest extends AbstractEncryptionMethodTest { "$2y$10$a8097db1fa4423b93f1b2eF6rMAGFkSX178fpROf/OvCFtrDebp6K"); // âË_3(íù* } + @Test + public void shouldGenerateWith2yPrefixAndCostFactor10() { + // given + BCrypt2y bCrypt2y = new BCrypt2y(); + + // when + HashedPassword result = bCrypt2y.computeHash("test", null); + + // then + assertThat(result.getHash(), startsWith("$2y$10$")); + assertThat(result.getSalt(), nullValue()); + } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/BCryptTest.java b/src/test/java/fr/xephi/authme/security/crypts/BCryptTest.java index c2d9d6f5a..9c3946f1c 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/BCryptTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/BCryptTest.java @@ -2,7 +2,10 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.HooksSettings; +import org.junit.Test; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -20,10 +23,21 @@ public class BCryptTest extends AbstractEncryptionMethodTest { ); } + @Test + public void shouldGenerateWith2aPrefix() { + // given + BCrypt bCrypt = new BCrypt(mockSettings()); + + // when + HashedPassword result = bCrypt.computeHash("test", null); + + // then + assertThat(result.getHash(), startsWith("$2a$08$")); + } + private static Settings mockSettings() { Settings settings = mock(Settings.class); given(settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND)).willReturn(8); return settings; } - } diff --git a/src/test/java/fr/xephi/authme/security/crypts/Ipb4Test.java b/src/test/java/fr/xephi/authme/security/crypts/Ipb4Test.java index 5f71c23d7..d019f3d79 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/Ipb4Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Ipb4Test.java @@ -1,5 +1,13 @@ package fr.xephi.authme.security.crypts; +import org.junit.Test; + +import static fr.xephi.authme.AuthMeMatchers.stringWithLength; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + /** * Test for {@link Ipb4}. */ @@ -13,4 +21,32 @@ public class Ipb4Test extends AbstractEncryptionMethodTest { new HashedPassword("$2a$13$18dKXZLoGpeHHL81edM9HuipiUcMjn5VDJHlxwRUMRXfJ1b.ZQrJ.", "18dKXZLoGpeHHL81edM9H6")); //âË_3(íù* } + @Test + public void shouldCreateHashesWith2aAndCostFactor13() { + // given + Ipb4 hashMethod = new Ipb4(); + + // when + HashedPassword result = hashMethod.computeHash("test", "name"); + + // then + assertThat(result.getHash(), startsWith("$2a$13$")); + assertThat(result.getSalt(), stringWithLength(22)); + } + + @Test + public void shouldThrowForInvalidSalt() { + // given + Ipb4 hashMethod = new Ipb4(); + + // when + try { + hashMethod.computeHash("pass", "invalid salt", "name"); + + // then + fail("Expected exception to be thrown"); + } catch (IllegalStateException e) { + assertThat(e.getMessage(), containsString("Cannot parse hash with salt")); + } + } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/XfBCryptTest.java b/src/test/java/fr/xephi/authme/security/crypts/XfBCryptTest.java index af1f4589b..982f5d488 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/XfBCryptTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/XfBCryptTest.java @@ -1,5 +1,11 @@ package fr.xephi.authme.security.crypts; +import org.junit.Test; + +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertThat; + /** * Test for {@link XfBCrypt}. */ @@ -12,4 +18,17 @@ public class XfBCryptTest extends AbstractEncryptionMethodTest { "$2a$10$yHjm02.K4HP5iFU1F..yLeTeo7PWZVbKAr/QGex5jU4.J3mdq/uuO", // &^%te$t?Pw@_ "$2a$10$joIayhGStExKWxNbiqMMPOYFSpQ76HVNjpOB7.QwTmG5q.TiJJ.0e"); // âË_3(íù* } + + @Test + public void shouldGenerateWith2aPrefixAndCostFactor10() { + // given + XfBCrypt xfBCrypt = new XfBCrypt(); + + // when + HashedPassword result = xfBCrypt.computeHash("test", null); + + // then + assertThat(result.getHash(), startsWith("$2a$10$")); + assertThat(result.getSalt(), nullValue()); + } } diff --git a/src/test/java/fr/xephi/authme/service/GeoIpServiceTest.java b/src/test/java/fr/xephi/authme/service/GeoIpServiceTest.java index 47dfb54e4..29220808b 100644 --- a/src/test/java/fr/xephi/authme/service/GeoIpServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/GeoIpServiceTest.java @@ -79,7 +79,7 @@ public class GeoIpServiceTest { String result = geoIpService.getCountryCode(ip); // then - assertThat(result, equalTo("--")); + assertThat(result, equalTo("LOCALHOST")); verify(lookupService, never()).getCountry(any()); } @@ -113,7 +113,7 @@ public class GeoIpServiceTest { String result = geoIpService.getCountryName(ip.getHostAddress()); // then - assertThat(result, equalTo("N/A")); + assertThat(result, equalTo("LocalHost")); verify(lookupService, never()).getCountry(ip); } } diff --git a/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java b/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java index ad777eab9..003036542 100644 --- a/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java @@ -22,10 +22,10 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import java.util.Arrays; import java.util.Collections; import java.util.logging.Logger; +import static com.google.common.collect.Sets.newHashSet; import static java.util.Arrays.asList; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; @@ -57,10 +57,9 @@ public class ValidationServiceTest { given(settings.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX)).willReturn("[a-zA-Z]+"); given(settings.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)).willReturn(3); given(settings.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)).willReturn(20); - given(settings.getProperty(SecuritySettings.UNSAFE_PASSWORDS)) - .willReturn(asList("unsafe", "other-unsafe")); + given(settings.getProperty(SecuritySettings.UNSAFE_PASSWORDS)).willReturn(newHashSet("unsafe", "other-unsafe")); given(settings.getProperty(EmailSettings.MAX_REG_PER_EMAIL)).willReturn(3); - given(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)).willReturn(asList("name01", "npc")); + given(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)).willReturn(newHashSet("name01", "npc")); given(settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)).willReturn(false); } @@ -261,7 +260,7 @@ public class ValidationServiceTest { assertThat(validationService.isUnrestricted("NAME01"), equalTo(true)); // Check reloading - given(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)).willReturn(asList("new", "names")); + given(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)).willReturn(newHashSet("new", "names")); validationService.reload(); assertThat(validationService.isUnrestricted("npc"), equalTo(false)); assertThat(validationService.isUnrestricted("New"), equalTo(true)); @@ -350,7 +349,7 @@ public class ValidationServiceTest { // given given(settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)).willReturn(true); given(settings.getProperty(RestrictionSettings.RESTRICTED_USERS)) - .willReturn(Arrays.asList("Bobby;127.0.0.4", "Tamara;32.24.16.8", "Gabriel;regex:93\\.23\\.44\\..*", "emanuel;94.65.24.*", "imyourisp;*.yourisp.net")); + .willReturn(newHashSet("Bobby;127.0.0.4", "Tamara;32.24.16.8", "Gabriel;regex:93\\.23\\.44\\..*", "emanuel;94.65.24.*", "imyourisp;*.yourisp.net")); validationService.reload(); Player bobby = mockPlayer("bobby", "127.0.0.4"); @@ -389,7 +388,7 @@ public class ValidationServiceTest { Logger logger = TestHelper.setupLogger(); given(settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)).willReturn(true); given(settings.getProperty(RestrictionSettings.RESTRICTED_USERS)) - .willReturn(Arrays.asList("Bobby;127.0.0.4", "Tamara;")); + .willReturn(newHashSet("Bobby;127.0.0.4", "Tamara;")); // when validationService.reload(); diff --git a/src/test/java/fr/xephi/authme/service/yaml/YamlFileResourceProviderTest.java b/src/test/java/fr/xephi/authme/service/yaml/YamlFileResourceProviderTest.java index f870a6b43..3b6bbddbb 100644 --- a/src/test/java/fr/xephi/authme/service/yaml/YamlFileResourceProviderTest.java +++ b/src/test/java/fr/xephi/authme/service/yaml/YamlFileResourceProviderTest.java @@ -26,7 +26,7 @@ public class YamlFileResourceProviderTest { YamlFileResource resource = YamlFileResourceProvider.loadFromFile(yamlFile); // then - assertThat(resource.getString("test.jkl"), equalTo("Test test")); + assertThat(resource.createReader().getString("test.jkl"), equalTo("Test test")); } @Test @@ -36,7 +36,7 @@ public class YamlFileResourceProviderTest { // when try { - YamlFileResourceProvider.loadFromFile(yamlFile); + YamlFileResourceProvider.loadFromFile(yamlFile).createReader(); // then fail("Expected exception to be thrown"); diff --git a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java index 87d9b8aac..5a1de0e6e 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java @@ -1,30 +1,22 @@ package fr.xephi.authme.settings; -import ch.jalu.configme.SectionComments; -import ch.jalu.configme.SettingsHolder; import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.properties.EnumProperty; import ch.jalu.configme.properties.Property; import com.google.common.collect.ImmutableSet; -import fr.xephi.authme.ClassCollector; -import fr.xephi.authme.ReflectionTestUtils; -import fr.xephi.authme.TestHelper; import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever; import fr.xephi.authme.settings.properties.SecuritySettings; import org.junit.BeforeClass; import org.junit.Test; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkArgument; import static fr.xephi.authme.ReflectionTestUtils.getFieldValue; import static org.junit.Assert.fail; @@ -58,7 +50,7 @@ public class SettingsConsistencyTest { // when / then for (Property property : properties) { - if (configurationData.getCommentsForSection(property.getPath()).length == 0) { + if (configurationData.getCommentsForSection(property.getPath()).isEmpty()) { fail("No comment defined for " + property); } } @@ -67,83 +59,27 @@ public class SettingsConsistencyTest { @Test public void shouldNotHaveVeryLongCommentLines() { // given - List> properties = configurationData.getProperties(); - List> badProperties = new ArrayList<>(); + Map> commentEntries = configurationData.getAllComments(); + List badPaths = new ArrayList<>(0); // when - for (Property property : properties) { - for (String comment : configurationData.getCommentsForSection(property.getPath())) { + for (Map.Entry> commentEntry : commentEntries.entrySet()) { + for (String comment : commentEntry.getValue()) { if (comment.length() > MAX_COMMENT_LENGTH) { - badProperties.add(property); + badPaths.add(commentEntry.getKey()); break; } } } // then - if (!badProperties.isEmpty()) { + if (!badPaths.isEmpty()) { fail("Comment lines should not be longer than " + MAX_COMMENT_LENGTH + " chars, " - + "but found too long comments for:\n- " - + badProperties.stream().map(Property::getPath).collect(Collectors.joining("\n- "))); + + "but found too long comments for paths:\n- " + + String.join("\n- ", badPaths)); } } - @Test - public void shouldNotHaveVeryLongSectionCommentLines() { - // given - List sectionCommentMethods = getSectionCommentMethods(); - Set badMethods = new HashSet<>(); - - // when - for (Method method : sectionCommentMethods) { - boolean hasTooLongLine = getSectionComments(method).stream() - .anyMatch(line -> line.length() > MAX_COMMENT_LENGTH); - if (hasTooLongLine) { - badMethods.add(method); - } - } - - // then - if (!badMethods.isEmpty()) { - String methodList = badMethods.stream() - .map(m -> m.getName() + " in " + m.getDeclaringClass().getSimpleName()) - .collect(Collectors.joining("\n- ")); - fail("Found SectionComments methods with too long comments:\n- " + methodList); - } - } - - /** - * Gets all {@link SectionComments} methods from {@link SettingsHolder} implementations. - */ - @SuppressWarnings("unchecked") - private List getSectionCommentMethods() { - // Find all SettingsHolder classes - List> settingsClasses = - new ClassCollector(TestHelper.SOURCES_FOLDER, TestHelper.PROJECT_ROOT + "settings/properties/") - .collectClasses(SettingsHolder.class); - checkArgument(!settingsClasses.isEmpty(), "Could not find any SettingsHolder classes"); - - // Find all @SectionComments methods in these classes - return settingsClasses.stream() - .map(Class::getDeclaredMethods) - .flatMap(Arrays::stream) - .filter(method -> method.isAnnotationPresent(SectionComments.class)) - .collect(Collectors.toList()); - } - - /** - * Returns all comments returned from the given SectionComments method, flattened into one list. - * - * @param sectionCommentsMethod the method whose comments should be retrieved - * @return flattened list of all comments provided by the method - */ - private static List getSectionComments(Method sectionCommentsMethod) { - // @SectionComments methods are static - Map comments = ReflectionTestUtils.invokeMethod(sectionCommentsMethod, null); - return comments.values().stream() - .flatMap(Arrays::stream) - .collect(Collectors.toList()); - } /** * Checks that enum properties have all possible enum values listed in their comment diff --git a/src/test/java/fr/xephi/authme/settings/SettingsIntegrationTest.java b/src/test/java/fr/xephi/authme/settings/SettingsIntegrationTest.java index 705b51f06..e9a58c677 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsIntegrationTest.java @@ -38,7 +38,7 @@ public class SettingsIntegrationTest { private static final String INCOMPLETE_FILE = TestHelper.PROJECT_ROOT + "settings/config-incomplete-sample.yml"; private static ConfigurationData CONFIG_DATA = - ConfigurationDataBuilder.collectData(TestConfiguration.class); + ConfigurationDataBuilder.createConfiguration(TestConfiguration.class); @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); diff --git a/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java b/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java index 51cef1936..021bfb93b 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java @@ -1,7 +1,7 @@ package fr.xephi.authme.settings; import ch.jalu.configme.configurationdata.ConfigurationData; -import ch.jalu.configme.properties.Property; +import ch.jalu.configme.resource.PropertyReader; import ch.jalu.configme.resource.PropertyResource; import ch.jalu.configme.resource.YamlFileResource; import com.google.common.io.Files; @@ -108,7 +108,7 @@ public class SettingsMigrationServiceTest { SettingsMigrationService migrationService = new SettingsMigrationService(dataFolder); // when - migrationService.performMigrations(resource, AuthMeSettingsRetriever.buildConfigurationData().getProperties()); + migrationService.performMigrations(resource.createReader(), AuthMeSettingsRetriever.buildConfigurationData()); // then assertThat(migrationService.hasOldOtherAccountsCommand(), equalTo(true)); @@ -146,8 +146,8 @@ public class SettingsMigrationServiceTest { } @Override - protected boolean performMigrations(PropertyResource resource, List> properties) { - boolean result = super.performMigrations(resource, properties); + protected boolean performMigrations(PropertyReader reader, ConfigurationData configurationData) { + boolean result = super.performMigrations(reader, configurationData); returnedValues.add(result); return result; } diff --git a/src/test/java/fr/xephi/authme/settings/SettingsTest.java b/src/test/java/fr/xephi/authme/settings/SettingsTest.java index 9482c09a3..93934b1ab 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsTest.java @@ -17,6 +17,7 @@ import java.nio.file.Files; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; /** @@ -25,7 +26,7 @@ import static org.mockito.Mockito.mock; public class SettingsTest { private static final ConfigurationData CONFIG_DATA = - ConfigurationDataBuilder.collectData(TestConfiguration.class); + ConfigurationDataBuilder.createConfiguration(TestConfiguration.class); @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -49,7 +50,7 @@ public class SettingsTest { createFile(emailFile); Files.write(emailFile.toPath(), emailMessage.getBytes()); - PropertyResource resource = mock(PropertyResource.class); + PropertyResource resource = mock(PropertyResource.class, RETURNS_DEEP_STUBS); Settings settings = new Settings(testPluginFolder, resource, null, CONFIG_DATA); // when @@ -67,7 +68,7 @@ public class SettingsTest { createFile(emailFile); Files.write(emailFile.toPath(), emailMessage.getBytes()); - PropertyResource resource = mock(PropertyResource.class); + PropertyResource resource = mock(PropertyResource.class, RETURNS_DEEP_STUBS); Settings settings = new Settings(testPluginFolder, resource, null, CONFIG_DATA); // when @@ -85,7 +86,7 @@ public class SettingsTest { createFile(emailFile); Files.write(emailFile.toPath(), emailMessage.getBytes()); - PropertyResource resource = mock(PropertyResource.class); + PropertyResource resource = mock(PropertyResource.class, RETURNS_DEEP_STUBS); Settings settings = new Settings(testPluginFolder, resource, null, CONFIG_DATA); // when diff --git a/src/test/java/fr/xephi/authme/settings/commandconfig/CommandMigrationServiceTest.java b/src/test/java/fr/xephi/authme/settings/commandconfig/CommandMigrationServiceTest.java index 307a48587..800bcc052 100644 --- a/src/test/java/fr/xephi/authme/settings/commandconfig/CommandMigrationServiceTest.java +++ b/src/test/java/fr/xephi/authme/settings/commandconfig/CommandMigrationServiceTest.java @@ -1,7 +1,8 @@ package fr.xephi.authme.settings.commandconfig; -import ch.jalu.configme.beanmapper.BeanDescriptionFactory; -import ch.jalu.configme.beanmapper.BeanPropertyDescription; +import ch.jalu.configme.beanmapper.propertydescription.BeanDescriptionFactoryImpl; +import ch.jalu.configme.beanmapper.propertydescription.BeanPropertyDescription; +import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.configurationdata.ConfigurationDataBuilder; import ch.jalu.configme.resource.PropertyResource; import ch.jalu.configme.resource.YamlFileResource; @@ -52,7 +53,7 @@ public class CommandMigrationServiceTest { // when boolean result = commandMigrationService.checkAndMigrate( - resource, ConfigurationDataBuilder.collectData(CommandSettingsHolder.class).getProperties()); + resource.createReader(), ConfigurationDataBuilder.createConfiguration(CommandSettingsHolder.class)); // then assertThat(result, equalTo(true)); @@ -66,7 +67,7 @@ public class CommandMigrationServiceTest { // when boolean result = commandMigrationService.checkAndMigrate( - resource, ConfigurationDataBuilder.collectData(CommandSettingsHolder.class).getProperties()); + resource.createReader(), ConfigurationDataBuilder.createConfiguration(CommandSettingsHolder.class)); // then assertThat(result, equalTo(true)); @@ -80,7 +81,7 @@ public class CommandMigrationServiceTest { // when boolean result = commandMigrationService.checkAndMigrate( - resource, ConfigurationDataBuilder.collectData(CommandSettingsHolder.class).getProperties()); + resource.createReader(), ConfigurationDataBuilder.createConfiguration(CommandSettingsHolder.class)); // then assertThat(result, equalTo(false)); @@ -93,8 +94,8 @@ public class CommandMigrationServiceTest { @Test public void shouldHaveAllPropertiesFromCommandConfig() { // given - String[] properties = new BeanDescriptionFactory() - .collectWritableFields(CommandConfig.class) + String[] properties = new BeanDescriptionFactoryImpl() + .getAllProperties(CommandConfig.class) .stream() .map(BeanPropertyDescription::getName) .toArray(String[]::new); @@ -112,13 +113,14 @@ public class CommandMigrationServiceTest { given(settingsMigrationService.getOldOtherAccountsCommandThreshold()).willReturn(3); File commandFile = TestHelper.getJarFile(TestHelper.PROJECT_ROOT + "settings/commandconfig/commands.complete.yml"); PropertyResource resource = new YamlFileResource(commandFile); + ConfigurationData configurationData = ConfigurationDataBuilder.createConfiguration(CommandSettingsHolder.class); // when commandMigrationService.checkAndMigrate( - resource, ConfigurationDataBuilder.collectData(CommandSettingsHolder.class).getProperties()); + resource.createReader(), configurationData); // then - Map onLoginCommands = CommandSettingsHolder.COMMANDS.getValue(resource).getOnLogin(); + Map onLoginCommands = configurationData.getValue(CommandSettingsHolder.COMMANDS).getOnLogin(); assertThat(onLoginCommands, aMapWithSize(6)); // 5 in the file + the newly migrated on OnLoginCommand newCommand = getUnknownOnLoginCommand(onLoginCommands); assertThat(newCommand.getCommand(), equalTo("helpop %p (%ip) has other accounts!")); diff --git a/src/test/java/fr/xephi/authme/settings/commandconfig/CommandYmlConsistencyTest.java b/src/test/java/fr/xephi/authme/settings/commandconfig/CommandYmlConsistencyTest.java index 668fae785..99b4d615b 100644 --- a/src/test/java/fr/xephi/authme/settings/commandconfig/CommandYmlConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/settings/commandconfig/CommandYmlConsistencyTest.java @@ -42,7 +42,7 @@ public class CommandYmlConsistencyTest { // when boolean result = commandMigrationService.checkAndMigrate( - resource, ConfigurationDataBuilder.collectData(CommandSettingsHolder.class).getProperties()); + resource.createReader(), ConfigurationDataBuilder.createConfiguration(CommandSettingsHolder.class)); // then assertThat(result, equalTo(false)); diff --git a/src/test/java/tools/docs/config/UpdateConfigPageTask.java b/src/test/java/tools/docs/config/UpdateConfigPageTask.java index 15cd50e76..d82cda195 100644 --- a/src/test/java/tools/docs/config/UpdateConfigPageTask.java +++ b/src/test/java/tools/docs/config/UpdateConfigPageTask.java @@ -1,7 +1,7 @@ package tools.docs.config; import ch.jalu.configme.SettingsManager; -import ch.jalu.configme.resource.YamlFileResource; +import ch.jalu.configme.SettingsManagerBuilder; import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever; import fr.xephi.authme.util.FileUtils; import tools.utils.AutoToolTask; @@ -31,8 +31,9 @@ public class UpdateConfigPageTask implements AutoToolTask { try { // Create empty temporary .yml file and save the config to it config = File.createTempFile("authme-config-", ".yml"); - SettingsManager settingsManager = new SettingsManager( - new YamlFileResource(config), null, AuthMeSettingsRetriever.buildConfigurationData()); + SettingsManager settingsManager = SettingsManagerBuilder.withYamlFile(config) + .configurationData(AuthMeSettingsRetriever.buildConfigurationData()) + .create(); settingsManager.save(); // Get the contents and generate template file diff --git a/src/test/java/tools/docs/translations/TranslationPageGenerator.java b/src/test/java/tools/docs/translations/TranslationPageGenerator.java index dd87973d3..806819879 100644 --- a/src/test/java/tools/docs/translations/TranslationPageGenerator.java +++ b/src/test/java/tools/docs/translations/TranslationPageGenerator.java @@ -20,7 +20,8 @@ import static com.google.common.base.MoreObjects.firstNonNull; public class TranslationPageGenerator implements AutoToolTask { private static final String DOCS_PAGE = ToolsConstants.DOCS_FOLDER + "translations.md"; - private static final String TEMPLATE_FILE = ToolsConstants.TOOLS_SOURCE_ROOT + "docs/translations/translations.tpl.md"; + private static final String TEMPLATE_FILE = + ToolsConstants.TOOLS_SOURCE_ROOT + "docs/translations/translations.tpl.md"; private static final Map LANGUAGE_NAMES = buildLanguageNames(); // Color configuration for the bars shown next to translation percentage diff --git a/src/test/java/tools/docs/translations/TranslationsGatherer.java b/src/test/java/tools/docs/translations/TranslationsGatherer.java index e42b266d1..3e2119ff2 100644 --- a/src/test/java/tools/docs/translations/TranslationsGatherer.java +++ b/src/test/java/tools/docs/translations/TranslationsGatherer.java @@ -1,8 +1,8 @@ package tools.docs.translations; +import ch.jalu.configme.resource.PropertyReader; +import ch.jalu.configme.resource.YamlFileReader; import fr.xephi.authme.message.MessageKey; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; import tools.utils.ToolsConstants; import java.io.File; @@ -43,10 +43,10 @@ public class TranslationsGatherer { } private void processMessagesFile(String code, File file) { - FileConfiguration configuration = YamlConfiguration.loadConfiguration(file); + PropertyReader reader = new YamlFileReader(file); int availableMessages = 0; for (MessageKey key : MessageKey.values()) { - if (configuration.contains(key.getKey())) { + if (reader.contains(key.getKey())) { ++availableMessages; } } diff --git a/src/test/java/tools/filegeneration/GenerateCommandsYml.java b/src/test/java/tools/filegeneration/GenerateCommandsYml.java index 73232d90d..34a1bb1b3 100644 --- a/src/test/java/tools/filegeneration/GenerateCommandsYml.java +++ b/src/test/java/tools/filegeneration/GenerateCommandsYml.java @@ -1,7 +1,7 @@ package tools.filegeneration; import ch.jalu.configme.SettingsManager; -import ch.jalu.configme.resource.YamlFileResource; +import ch.jalu.configme.SettingsManagerBuilder; import fr.xephi.authme.settings.commandconfig.CommandConfig; import fr.xephi.authme.settings.commandconfig.CommandSettingsHolder; import tools.utils.AutoToolTask; @@ -24,8 +24,9 @@ public class GenerateCommandsYml implements AutoToolTask { CommandConfig commandConfig = CommandSettingsHolder.COMMANDS.getDefaultValue(); // Export the value to the file - SettingsManager settingsManager = new SettingsManager( - new YamlFileResource(file), null, CommandSettingsHolder.class); + SettingsManager settingsManager = SettingsManagerBuilder.withYamlFile(file) + .configurationData(CommandSettingsHolder.class) + .create(); settingsManager.setProperty(CommandSettingsHolder.COMMANDS, commandConfig); settingsManager.save(); diff --git a/src/test/java/tools/messages/MessageFileVerifier.java b/src/test/java/tools/messages/MessageFileVerifier.java index dd33e487a..650c9ff77 100644 --- a/src/test/java/tools/messages/MessageFileVerifier.java +++ b/src/test/java/tools/messages/MessageFileVerifier.java @@ -70,6 +70,10 @@ public class MessageFileVerifier { return !missingKeys.isEmpty() || !missingTags.isEmpty() || !unknownKeys.isEmpty(); } + /** + * Performs the actual verification for the given file and collects all found issues into this + * instance's fields. + */ private void verifyKeys() { FileConfiguration configuration = YamlConfiguration.loadConfiguration(messagesFile); diff --git a/src/test/java/tools/messages/MessagesFileWriter.java b/src/test/java/tools/messages/MessagesFileWriter.java index 534be000c..8f43247a4 100644 --- a/src/test/java/tools/messages/MessagesFileWriter.java +++ b/src/test/java/tools/messages/MessagesFileWriter.java @@ -1,10 +1,10 @@ package tools.messages; -import ch.jalu.configme.SettingsManager; import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.properties.Property; -import ch.jalu.configme.resource.PropertyResource; +import ch.jalu.configme.resource.PropertyReader; import ch.jalu.configme.resource.YamlFileResource; +import fr.xephi.authme.message.updater.MessageKeyConfigurationData; import fr.xephi.authme.message.updater.MessageUpdater; import fr.xephi.authme.message.updater.MigraterYamlFileResource; import org.bukkit.configuration.file.FileConfiguration; @@ -47,12 +47,18 @@ public final class MessagesFileWriter { } private void performWrite() { + // Initialize ConfigMe classes + MessageKeyConfigurationData configurationData = MessageUpdater.createConfigurationData(); + YamlFileResource resource = new MigraterYamlFileResource(file); + PropertyReader reader = resource.createReader(); + configurationData.initializeValues(reader); + // Store initial comments so we can add them back later - List initialComments = getInitialUserComments(); + List initialComments = getInitialUserComments(configurationData); // Create property resource with new defaults, save with ConfigMe for proper sections & comments - PropertyResource resource = createPropertyResourceWithCommentEntries(); - new SettingsManager(resource, null, MessageUpdater.getConfigurationData()).save(); + addMissingMessagesWithCommentMarker(reader, configurationData); + resource.exportProperties(configurationData); // Go through the newly saved file and replace texts with comment marker to actual YAML comments // and add initial comments back to the file @@ -60,11 +66,14 @@ public final class MessagesFileWriter { } /** + * Returns the comments at the top which are custom to the file. + * + * @param configurationData the configuration data * @return any custom comments at the top of the file, for later usage */ - private List getInitialUserComments() { + private List getInitialUserComments(ConfigurationData configurationData) { final List initialComments = new ArrayList<>(); - final String firstCommentByConfigMe = getFirstCommentByConfigMe(); + final String firstCommentByConfigMe = getFirstCommentByConfigMe(configurationData); for (String line : FileIoUtils.readLinesFromFile(file.toPath())) { if (line.isEmpty() || line.startsWith("#") && !line.equals(firstCommentByConfigMe)) { @@ -86,27 +95,30 @@ public final class MessagesFileWriter { } /** - * @return the first comment generated by ConfigMe (comment of the first root path) + * Returns the first comment generated by ConfigMe (comment of the first root path). + * + * @param configurationData the configuration data + * @return first comment which is generated by ConfigMe */ - private static String getFirstCommentByConfigMe() { - ConfigurationData configurationData = MessageUpdater.getConfigurationData(); + private static String getFirstCommentByConfigMe(ConfigurationData configurationData) { String firstRootPath = configurationData.getProperties().get(0).getPath().split("\\.")[0]; - return "# " + configurationData.getCommentsForSection(firstRootPath)[0]; + return "# " + configurationData.getCommentsForSection(firstRootPath).get(0); } /** - * @return generated {@link PropertyResource} with missing entries taken from the default file and marked - * with the {@link #COMMENT_MARKER} + * Adds a text with a {@link #COMMENT_MARKER} for all properties which are not yet present in the reader. + * + * @param reader the property reader + * @param configurationData the configuration data */ - private PropertyResource createPropertyResourceWithCommentEntries() { - YamlFileResource resource = new MigraterYamlFileResource(file); - for (Property property : MessageUpdater.getConfigurationData().getProperties()) { - String text = resource.getString(property.getPath()); + private void addMissingMessagesWithCommentMarker(PropertyReader reader, + MessageKeyConfigurationData configurationData) { + for (Property property : configurationData.getAllMessageProperties()) { + String text = reader.getString(property.getPath()); if (text == null) { - resource.setValue(property.getPath(), COMMENT_MARKER + defaultFile.getString(property.getPath())); + configurationData.setValue(property, COMMENT_MARKER + defaultFile.getString(property.getPath())); } } - return resource; } /** diff --git a/src/test/java/tools/utils/TagReplacer.java b/src/test/java/tools/utils/TagReplacer.java index 32c744526..df3f5bdb5 100644 --- a/src/test/java/tools/utils/TagReplacer.java +++ b/src/test/java/tools/utils/TagReplacer.java @@ -18,7 +18,7 @@ import java.util.regex.Pattern; * version (in case the page is viewed on a fork) * */ -public class TagReplacer { +public final class TagReplacer { private TagReplacer() { } diff --git a/src/test/java/tools/utils/TagValueHolder.java b/src/test/java/tools/utils/TagValueHolder.java index 838628307..f6c9ec557 100644 --- a/src/test/java/tools/utils/TagValueHolder.java +++ b/src/test/java/tools/utils/TagValueHolder.java @@ -6,7 +6,7 @@ import java.util.HashMap; import java.util.Map; -public class TagValueHolder { +public final class TagValueHolder { private Map> values;