diff --git a/.checkstyle.xml b/.checkstyle.xml index d8f9076d..8b8b4969 100644 --- a/.checkstyle.xml +++ b/.checkstyle.xml @@ -145,7 +145,7 @@ - + @@ -159,6 +159,8 @@ + + diff --git a/.codeclimate.yml b/.codeclimate.yml index ee496167..9c4405f8 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -25,4 +25,6 @@ exclude_paths: - '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/.gitignore b/.gitignore index 82df141d..da292ac5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,8 @@ MANIFEST.MF # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* - +# Mac OS +.DS_Store ### Intellij ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm diff --git a/README.md b/README.md index c9c717c7..2138ed4f 100644 --- a/README.md +++ b/README.md @@ -19,20 +19,14 @@ - Project status: - Dependencies: [![Dependencies status](https://www.versioneye.com/user/projects/57b182e8d6ffcd0032d7cf2d/badge.svg)](https://www.versioneye.com/user/projects/57b182e8d6ffcd0032d7cf2d) - Test coverage: [![Coverage status](https://coveralls.io/repos/AuthMe-Team/AuthMeReloaded/badge.svg?branch=master&service=github)](https://coveralls.io/github/AuthMe-Team/AuthMeReloaded?branch=master) + - Code climate: [![Code Climate](https://codeclimate.com/github/AuthMe/AuthMeReloaded/badges/gpa.svg)](https://codeclimate.com/github/AuthMe/AuthMeReloaded) - Development resources: - JavaDocs - Maven Repository -#####Statistics: - -McStats: http://mcstats.org/plugin/AuthMe - - - - - - +- Statistics: + - bStats: [AuthMe on bstats.org](https://bstats.org/plugin/bukkit/AuthMe)
@@ -92,7 +86,7 @@ You can also create your own translation file and, if you want, you can share it
  • DoubleSaltedMD5: SALTED2MD5
  • WordPress: WORDPRESS
  • -
  • Custom MySQL tables/columns names (useful with forums databases)
  • +
  • Custom MySQL tables/columns names (useful with forum databases)
  • Cached database queries!
  • Fully compatible with Citizens2, CombatTag, CombatTagPlus!
  • Compatible with Minecraft mods like BuildCraft or RedstoneCraft
  • diff --git a/docs/config.md b/docs/config.md index 4321aea8..8216aafd 100644 --- a/docs/config.md +++ b/docs/config.md @@ -73,17 +73,6 @@ ExternalBoardOptions: phpbbActivatedGroupId: 2 # Wordpress prefix defined during WordPress installation wordpressTablePrefix: 'wp_' -Converter: - Rakamak: - # Rakamak file name - fileName: 'users.rak' - # Rakamak use IP? - useIP: false - # Rakamak IP file name - ipFileName: 'UsersIp.rak' - CrazyLogin: - # CrazyLogin database file name - fileName: 'accounts.db' settings: sessions: # Do you want to enable the session feature? @@ -452,6 +441,23 @@ Security: # Seconds a user has to wait for before a password recovery mail may be sent again # This prevents an attacker from abusing AuthMe's email feature. cooldown: 60 +# Before a user logs in, various properties are temporarily removed from the player, +# such as OP status, ability to fly, and walk/fly speed. +# Once the user is logged in, we add back the properties we previously saved. +# In this section, you may define how the properties should be restored. +limbo: + # Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE. + # RESTORE sets back the old property from the player. + restoreAllowFlight: 'RESTORE' + # Restore fly speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO. + # RESTORE: restore the speed the player had; + # DEFAULT: always set to default speed; + # MAX_RESTORE: take the maximum of the player's current speed and the previous one + # RESTORE_NO_ZERO: Like 'restore' but sets speed to default if the player's speed was 0 + restoreFlySpeed: 'MAX_RESTORE' + # Restore walk speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO. + # See above for a description of the values. + restoreWalkSpeed: 'MAX_RESTORE' BackupSystem: # Enable or disable automatic backup ActivateBackup: false @@ -461,6 +467,17 @@ BackupSystem: OnServerStop: true # Windows only mysql installation Path MysqlWindowsPath: 'C:\Program Files\MySQL\MySQL Server 5.1\' +Converter: + Rakamak: + # Rakamak file name + fileName: 'users.rak' + # Rakamak use IP? + useIP: false + # Rakamak IP file name + ipFileName: 'UsersIp.rak' + CrazyLogin: + # CrazyLogin database file name + fileName: 'accounts.db' ``` To change settings on a running server, save your changes to config.yml and use diff --git a/docs/translations.md b/docs/translations.md index 70c26d8b..689c303d 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 @@ -8,25 +8,25 @@ in your config.yml to use the language, or use another language code to start a Code | Language | Translated |   ---- | -------- | ---------: | ------ [en](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_en.yml) | English | 100% | bar -[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 61% | bar +[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 100% | bar [br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 89% | bar [cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 89% | bar [de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 89% | bar -[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 89% | bar +[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 100% | bar [eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 55% | bar [fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 59% | bar -[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 89% | bar +[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 100% | bar [gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 63% | bar [hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 88% | bar [id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 63% | bar -[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 89% | bar +[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 100% | bar [ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 64% | bar [lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 47% | bar [nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 89% | bar [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 | 77% | 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 | 88% | bar -[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 89% | bar +[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 100% | bar [sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 41% | 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 | 83% | bar @@ -36,7 +36,6 @@ Code | Language | Translated |   [zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 86% | bar [zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 72% | bar - --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Tue Feb 28 19:25:18 CET 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Mon Mar 13 20:34:31 CET 2017 diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 21b9bcb5..b66258cb 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -15,6 +15,7 @@ import fr.xephi.authme.initialization.OnStartupTasks; import fr.xephi.authme.initialization.SettingsProvider; import fr.xephi.authme.initialization.TaskCloser; import fr.xephi.authme.initialization.factory.FactoryDependencyHandler; +import fr.xephi.authme.initialization.factory.SingletonStoreDependencyHandler; import fr.xephi.authme.listener.BlockListener; import fr.xephi.authme.listener.EntityListener; import fr.xephi.authme.listener.PlayerListener; @@ -25,7 +26,7 @@ import fr.xephi.authme.listener.PlayerListener19; import fr.xephi.authme.listener.ServerListener; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsSystemType; -import fr.xephi.authme.security.crypts.SHA256; +import fr.xephi.authme.security.crypts.Sha256; import fr.xephi.authme.service.BackupService; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.MigrationService; @@ -36,6 +37,7 @@ import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.task.CleanupTask; import fr.xephi.authme.task.purge.PurgeService; +import org.apache.commons.lang.SystemUtils; import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @@ -195,12 +197,17 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.setLogger(getLogger()); ConsoleLogger.setLogFile(new File(getDataFolder(), LOG_FILENAME)); + // Check java version + if(!SystemUtils.isJavaVersionAtLeast(1.8f)) { + throw new IllegalStateException("You need Java 1.8 or above to run this plugin!"); + } + // Create plugin folder getDataFolder().mkdir(); // Create injector, provide elements from the Bukkit environment and register providers injector = new InjectorBuilder() - .addHandlers(new FactoryDependencyHandler()) + .addHandlers(new FactoryDependencyHandler(), new SingletonStoreDependencyHandler()) .addDefaultHandlers("fr.xephi.authme") .create(); injector.register(AuthMe.class, this); @@ -220,7 +227,7 @@ public class AuthMe extends JavaPlugin { instantiateServices(injector); // Convert deprecated PLAINTEXT hash entries - MigrationService.changePlainTextToSha256(settings, database, new SHA256()); + MigrationService.changePlainTextToSha256(settings, database, new Sha256()); // TODO: does this still make sense? -sgdc3 // If the server is empty (fresh start) just set all the players as unlogged diff --git a/src/main/java/fr/xephi/authme/api/API.java b/src/main/java/fr/xephi/authme/api/API.java index d05fbe6b..75e746e6 100644 --- a/src/main/java/fr/xephi/authme/api/API.java +++ b/src/main/java/fr/xephi/authme/api/API.java @@ -23,6 +23,7 @@ import javax.inject.Inject; * @deprecated Use {@link NewAPI} */ @Deprecated +@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) // Justification: Class name cannot be changed anymore public class API { private static AuthMe instance; diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index d4f34b2c..db2c46e0 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -5,7 +5,8 @@ import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.process.Management; -import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider; +import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams; +import fr.xephi.authme.process.register.executors.RegistrationMethod; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.service.PluginHookService; @@ -24,6 +25,7 @@ import java.util.List; * NewAPI authmeApi = AuthMe.getApi(); * */ +@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) // Justification: Class name cannot be changed anymore public class NewAPI { private static NewAPI singleton; @@ -34,15 +36,13 @@ public class NewAPI { private final Management management; private final ValidationService validationService; private final PlayerCache playerCache; - private final RegistrationExecutorProvider registrationExecutorProvider; /* * Constructor for NewAPI. */ @Inject NewAPI(AuthMe plugin, PluginHookService pluginHookService, DataSource dataSource, PasswordSecurity passwordSecurity, - Management management, ValidationService validationService, PlayerCache playerCache, - RegistrationExecutorProvider registrationExecutorProvider) { + Management management, ValidationService validationService, PlayerCache playerCache) { this.plugin = plugin; this.pluginHookService = pluginHookService; this.dataSource = dataSource; @@ -50,7 +50,6 @@ public class NewAPI { this.management = management; this.validationService = validationService; this.playerCache = playerCache; - this.registrationExecutorProvider = registrationExecutorProvider; NewAPI.singleton = this; } @@ -203,8 +202,8 @@ public class NewAPI { * @param autoLogin Should the player be authenticated automatically after the registration? */ public void forceRegister(Player player, String password, boolean autoLogin) { - management.performRegister(player, - registrationExecutorProvider.getPasswordRegisterExecutor(player, password, autoLogin)); + management.performRegister(RegistrationMethod.API_REGISTRATION, + ApiPasswordRegisterParams.of(player, password, autoLogin)); } /** diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java index 18463bca..e195c475 100644 --- a/src/main/java/fr/xephi/authme/command/CommandDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandDescription.java @@ -19,6 +19,7 @@ import static java.util.Arrays.asList; * {@code /authme} has a child whose label is {@code "register"}, then {@code /authme register} is the command that * the child defines. */ +@SuppressWarnings("checkstyle:FinalClass") // Justification: class is mocked in multiple tests public class CommandDescription { /** diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java index e1c0661c..c59c4cb9 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java @@ -10,8 +10,8 @@ import fr.xephi.authme.datasource.converter.MySqlToSqlite; import fr.xephi.authme.datasource.converter.RakamakConverter; import fr.xephi.authme.datasource.converter.RoyalAuthConverter; import fr.xephi.authme.datasource.converter.SqliteToSql; -import fr.xephi.authme.datasource.converter.vAuthConverter; -import fr.xephi.authme.datasource.converter.xAuthConverter; +import fr.xephi.authme.datasource.converter.VAuthConverter; +import fr.xephi.authme.datasource.converter.XAuthConverter; import fr.xephi.authme.initialization.factory.Factory; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.BukkitService; @@ -78,11 +78,11 @@ public class ConverterCommand implements ExecutableCommand { */ private static Map> getConverters() { return ImmutableMap.>builder() - .put("xauth", xAuthConverter.class) + .put("xauth", XAuthConverter.class) .put("crazylogin", CrazyLoginConverter.class) .put("rakamak", RakamakConverter.class) .put("royalauth", RoyalAuthConverter.class) - .put("vauth", vAuthConverter.class) + .put("vauth", VAuthConverter.class) .put("sqlitetosql", SqliteToSql.class) .put("mysqltosqlite", MySqlToSqlite.class) .build(); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java index cba5edf0..2f1341b9 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java @@ -1,9 +1,8 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.security.PasswordSecurity; @@ -38,9 +37,6 @@ public class RegisterAdminCommand implements ExecutableCommand { @Inject private ValidationService validationService; - @Inject - private LimboCache limboCache; - @Override public void executeCommand(final CommandSender sender, List arguments) { // Get the player name and password @@ -83,7 +79,6 @@ public class RegisterAdminCommand implements ExecutableCommand { bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() { @Override public void run() { - limboCache.restoreData(player); player.kickPlayer(commonService.retrieveSingleMessage(MessageKey.KICK_FOR_ADMIN_REGISTER)); } }); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java index 037846aa..5a3604f1 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java @@ -11,10 +11,10 @@ import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.DatabaseSettings; +import fr.xephi.authme.util.Utils; import org.bukkit.command.CommandSender; import javax.inject.Inject; -import java.util.Collection; import java.util.List; /** @@ -44,8 +44,7 @@ public class ReloadCommand implements ExecutableCommand { ConsoleLogger.setLoggingOptions(settings); // We do not change database type for consistency issues, but we'll output a note in the logs if (!settings.getProperty(DatabaseSettings.BACKEND).equals(dataSource.getType())) { - ConsoleLogger.info("Note: cannot change database type during /authme reload"); - sender.sendMessage("Note: cannot change database type during /authme reload"); + Utils.logAndSendMessage(sender, "Note: cannot change database type during /authme reload"); } performReloadOnServices(); commonService.send(sender, MessageKey.CONFIG_RELOAD_SUCCESS); @@ -57,14 +56,10 @@ public class ReloadCommand implements ExecutableCommand { } private void performReloadOnServices() { - Collection reloadables = injector.retrieveAllOfType(Reloadable.class); - for (Reloadable reloadable : reloadables) { - reloadable.reload(); - } + injector.retrieveAllOfType(Reloadable.class) + .forEach(r -> r.reload()); - Collection settingsDependents = injector.retrieveAllOfType(SettingsDependent.class); - for (SettingsDependent dependent : settingsDependents) { - dependent.reload(settings); - } + injector.retrieveAllOfType(SettingsDependent.class) + .forEach(s -> s.reload(settings)); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java new file mode 100644 index 00000000..05de8ab1 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java @@ -0,0 +1,77 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.service.GeoIpService; +import fr.xephi.authme.service.ValidationService; +import fr.xephi.authme.settings.properties.ProtectionSettings; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; +import java.util.List; +import java.util.regex.Pattern; + +/** + * Shows the GeoIP information as returned by the geoIpService. + */ +class CountryLookup implements DebugSection { + + private static final Pattern IS_IP_ADDR = Pattern.compile("(\\d{1,3}\\.){3}\\d{1,3}"); + + @Inject + private GeoIpService geoIpService; + + @Inject + private DataSource dataSource; + + @Inject + private ValidationService validationService; + + @Override + public String getName() { + return "cty"; + } + + @Override + public String getDescription() { + return "Check country protection / country data"; + } + + @Override + public void execute(CommandSender sender, List arguments) { + if (arguments.isEmpty()) { + sender.sendMessage("Check player: /authme debug cty Bobby"); + sender.sendMessage("Check IP address: /authme debug cty 127.123.45.67"); + return; + } + + String argument = arguments.get(0); + if (IS_IP_ADDR.matcher(argument).matches()) { + outputInfoForIpAddr(sender, argument); + } else { + outputInfoForPlayer(sender, argument); + } + } + + private void outputInfoForIpAddr(CommandSender sender, String ipAddr) { + sender.sendMessage("IP '" + ipAddr + "' maps to country '" + geoIpService.getCountryCode(ipAddr) + + "' (" + geoIpService.getCountryName(ipAddr) + ")"); + if (validationService.isCountryAdmitted(ipAddr)) { + sender.sendMessage(ChatColor.DARK_GREEN + "This IP address' country is not blocked"); + } else { + sender.sendMessage(ChatColor.DARK_RED + "This IP address' country is blocked from the server"); + } + sender.sendMessage("Note: if " + ProtectionSettings.ENABLE_PROTECTION + " is false no country is blocked"); + } + + private void outputInfoForPlayer(CommandSender sender, String name) { + PlayerAuth auth = dataSource.getAuth(name); + if (auth == null) { + sender.sendMessage("No player with name '" + name + "'"); + } else { + sender.sendMessage("Player '" + name + "' has IP address " + auth.getIp()); + outputInfoForIpAddr(sender, auth.getIp()); + } + } +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java new file mode 100644 index 00000000..3bf28e05 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java @@ -0,0 +1,72 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.data.auth.PlayerCache; +import fr.xephi.authme.data.limbo.LimboService; +import fr.xephi.authme.datasource.CacheDataSource; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.initialization.HasCleanup; +import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.initialization.SettingsDependent; +import fr.xephi.authme.initialization.factory.SingletonStore; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; +import java.util.List; +import java.util.Map; + +import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.applyToLimboPlayersMap; + +/** + * Fetches various statistics, particularly regarding in-memory data that is stored. + */ +class DataStatistics implements DebugSection { + + @Inject + private PlayerCache playerCache; + + @Inject + private LimboService limboService; + + @Inject + private DataSource dataSource; + + @Inject + private SingletonStore singletonStore; + + @Override + public String getName() { + return "stats"; + } + + @Override + public String getDescription() { + return "Outputs general data statistics"; + } + + @Override + public void execute(CommandSender sender, List arguments) { + sender.sendMessage("LimboPlayers in memory: " + applyToLimboPlayersMap(limboService, Map::size)); + sender.sendMessage("PlayerCache size: " + playerCache.getLogged() + " (= logged in players)"); + + outputDatabaseStats(sender); + outputInjectorStats(sender); + } + + private void outputDatabaseStats(CommandSender sender) { + sender.sendMessage("Total players in DB: " + dataSource.getAccountsRegistered()); + sender.sendMessage("Total marked as logged in in DB: " + dataSource.getLoggedPlayers().size()); + if (dataSource instanceof CacheDataSource) { + CacheDataSource cacheDataSource = (CacheDataSource) this.dataSource; + sender.sendMessage("Cached PlayerAuth objects: " + cacheDataSource.getCachedAuths().size()); + } + } + + private void outputInjectorStats(CommandSender sender) { + sender.sendMessage( + String.format("Singleton Java classes: %d (Reloadable: %d / SettingsDependent: %d / HasCleanup: %d)", + singletonStore.retrieveAllOfType().size(), + singletonStore.retrieveAllOfType(Reloadable.class).size(), + singletonStore.retrieveAllOfType(SettingsDependent.class).size(), + singletonStore.retrieveAllOfType(HasCleanup.class).size())); + } +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java index c910cb92..5cd7fb4f 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java @@ -6,10 +6,10 @@ import fr.xephi.authme.initialization.factory.Factory; import org.bukkit.command.CommandSender; import javax.inject.Inject; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; /** * Debug command main. @@ -19,8 +19,9 @@ public class DebugCommand implements ExecutableCommand { @Inject private Factory debugSectionFactory; - private Set> sectionClasses = - ImmutableSet.of(PermissionGroups.class, TestEmailSender.class); + private Set> sectionClasses = ImmutableSet.of(PermissionGroups.class, + DataStatistics.class, CountryLookup.class, PlayerAuthViewer.class, LimboPlayerViewer.class, CountryLookup.class, + HasPermissionChecker.class, TestEmailSender.class); private Map sections; @@ -46,7 +47,7 @@ public class DebugCommand implements ExecutableCommand { // Lazy getter private Map getSections() { if (sections == null) { - Map sections = new HashMap<>(); + Map sections = new TreeMap<>(); for (Class sectionClass : sectionClasses) { DebugSection section = debugSectionFactory.newInstance(sectionClass); sections.put(section.getName(), section); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java new file mode 100644 index 00000000..78960ce8 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java @@ -0,0 +1,98 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboService; +import org.bukkit.Location; + +import java.lang.reflect.Field; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.util.Map; +import java.util.function.Function; + +/** + * Utilities used within the DebugSection implementations. + */ +final class DebugSectionUtils { + + private static Field limboEntriesField; + + private DebugSectionUtils() { + } + + /** + * Formats the given location in a human readable way. Null-safe. + * + * @param location the location to format + * @return the formatted location + */ + static String formatLocation(Location location) { + if (location == null) { + return "null"; + } + + String worldName = location.getWorld() == null ? "null" : location.getWorld().getName(); + return formatLocation(location.getX(), location.getY(), location.getZ(), worldName); + } + + /** + * Formats the given location in a human readable way. + * + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param world the world name + * @return the formatted location + */ + static String formatLocation(double x, double y, double z, String world) { + return "(" + round(x) + ", " + round(y) + ", " + round(z) + ") in '" + world + "'"; + } + + /** + * Rounds the given number to two decimals. + * + * @param number the number to round + * @return the rounded number + */ + private static String round(double number) { + DecimalFormat df = new DecimalFormat("#.##"); + df.setRoundingMode(RoundingMode.HALF_UP); + return df.format(number); + } + + private static Field getLimboPlayerEntriesField() { + if (limboEntriesField == null) { + try { + Field field = LimboService.class.getDeclaredField("entries"); + field.setAccessible(true); + limboEntriesField = field; + } catch (Exception e) { + ConsoleLogger.logException("Could not retrieve LimboService entries field:", e); + } + } + return limboEntriesField; + } + + /** + * Applies the given function to the map in LimboService containing the LimboPlayers. + * As we don't want to expose this information in non-debug settings, this is done with reflection. + * Exceptions are generously caught and {@code null} is returned on failure. + * + * @param limboService the limbo service instance to get the map from + * @param function the function to apply to the map + * @param the result type of the function + * + * @return player names for which there is a LimboPlayer (or error message upon failure) + */ + static U applyToLimboPlayersMap(LimboService limboService, Function function) { + Field limboPlayerEntriesField = getLimboPlayerEntriesField(); + if (limboPlayerEntriesField != null) { + try { + return function.apply((Map) limboEntriesField.get(limboService)); + } catch (Exception e) { + ConsoleLogger.logException("Could not retrieve LimboService values:", e); + } + } + return null; + } +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java new file mode 100644 index 00000000..0df27bcc --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java @@ -0,0 +1,130 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.permission.AdminPermission; +import fr.xephi.authme.permission.DefaultPermission; +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.permission.PlayerPermission; +import fr.xephi.authme.permission.PlayerStatePermission; +import fr.xephi.authme.service.BukkitService; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * Checks if a player has a given permission, as checked by AuthMe. + */ +class HasPermissionChecker implements DebugSection { + + static final List> PERMISSION_NODE_CLASSES = + Arrays.asList(AdminPermission.class, PlayerPermission.class, PlayerStatePermission.class); + + @Inject + private PermissionsManager permissionsManager; + + @Inject + private BukkitService bukkitService; + + @Override + public String getName() { + return "perm"; + } + + @Override + public String getDescription() { + return "Checks if player has given permission: /authme debug perm bobby my.perm"; + } + + @Override + public void execute(CommandSender sender, List arguments) { + if (arguments.size() < 2) { + sender.sendMessage("Check if a player has permission:"); + sender.sendMessage("Example: /authme debug perm bobby my.perm.node"); + return; + } + + final String playerName = arguments.get(0); + final String permissionNode = arguments.get(1); + + Player player = bukkitService.getPlayerExact(playerName); + if (player == null) { + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName); + if (offlinePlayer == null) { + sender.sendMessage(ChatColor.DARK_RED + "Player '" + playerName + "' does not exist"); + } else { + sender.sendMessage("Player '" + playerName + "' not online; checking with offline player"); + performPermissionCheck(offlinePlayer, permissionNode, permissionsManager::hasPermissionOffline, sender); + } + } else { + performPermissionCheck(player, permissionNode, permissionsManager::hasPermission, sender); + } + } + + /** + * Performs a permission check and informs the given sender of the result. {@code permissionChecker} is the + * permission check to perform with the given {@code node} and the {@code player}. + * + * @param player the player to check a permission for + * @param node the node of the permission to check + * @param permissionChecker permission checking function + * @param sender the sender to inform of the result + * @param

    the player type + */ + private static

    void performPermissionCheck( + P player, String node, BiFunction permissionChecker, CommandSender sender) { + + PermissionNode permNode = getPermissionNode(sender, node); + if (permissionChecker.apply(player, permNode)) { + sender.sendMessage(ChatColor.DARK_GREEN + "Success: player '" + player.getName() + + "' has permission '" + node + "'"); + } else { + sender.sendMessage(ChatColor.DARK_RED + "Check failed: player '" + player.getName() + + "' does NOT have permission '" + node + "'"); + } + + } + + /** + * Based on the given permission node (String), tries to find the according AuthMe {@link PermissionNode} + * instance, or creates a new one if not available. + * + * @param sender the sender (used to inform him if no AuthMe PermissionNode can be matched) + * @param node the node to search for + * @return the node as {@link PermissionNode} object + */ + private static PermissionNode getPermissionNode(CommandSender sender, String node) { + Optional permNode = PERMISSION_NODE_CLASSES.stream() + .map(Class::getEnumConstants) + .flatMap(Arrays::stream) + .filter(perm -> perm.getNode().equals(node)) + .findFirst(); + if (permNode.isPresent()) { + return permNode.get(); + } else { + sender.sendMessage("Did not detect AuthMe permission; using default permission = DENIED"); + return createPermNode(node); + } + } + + private static PermissionNode createPermNode(String node) { + return new PermissionNode() { + @Override + public String getNode() { + return node; + } + + @Override + public DefaultPermission getDefaultPermission() { + return DefaultPermission.NOT_ALLOWED; + } + }; + } +} 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 new file mode 100644 index 00000000..bf7f9b3c --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java @@ -0,0 +1,129 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.data.limbo.LimboService; +import fr.xephi.authme.data.limbo.persistence.LimboPersistence; +import fr.xephi.authme.service.BukkitService; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.List; +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; + +/** + * Shows the data stored in LimboPlayers and the equivalent properties on online players. + */ +class LimboPlayerViewer implements DebugSection { + + @Inject + private LimboService limboService; + + @Inject + private LimboPersistence limboPersistence; + + @Inject + private BukkitService bukkitService; + + @Override + public String getName() { + return "limbo"; + } + + @Override + public String getDescription() { + return "View LimboPlayers and player's \"limbo stats\""; + } + + @Override + public void execute(CommandSender sender, List arguments) { + if (arguments.isEmpty()) { + sender.sendMessage("/authme debug limbo : show a player's limbo info"); + sender.sendMessage("Available limbo records: " + applyToLimboPlayersMap(limboService, Map::keySet)); + return; + } + + LimboPlayer memoryLimbo = limboService.getLimboPlayer(arguments.get(0)); + Player player = bukkitService.getPlayerExact(arguments.get(0)); + LimboPlayer diskLimbo = player != null ? limboPersistence.getLimboPlayer(player) : null; + if (memoryLimbo == null && player == null) { + sender.sendMessage("No limbo info and no player online with name '" + arguments.get(0) + "'"); + return; + } + + sender.sendMessage(ChatColor.GOLD + "Showing disk limbo / limbo / player info for '" + arguments.get(0) + "'"); + new InfoDisplayer(sender, diskLimbo, memoryLimbo, player) + .sendEntry("Is op", LimboPlayer::isOperator, Player::isOp) + .sendEntry("Walk speed", LimboPlayer::getWalkSpeed, Player::getWalkSpeed) + .sendEntry("Can fly", LimboPlayer::isCanFly, Player::getAllowFlight) + .sendEntry("Fly speed", LimboPlayer::getFlySpeed, Player::getFlySpeed) + .sendEntry("Location", l -> formatLocation(l.getLocation()), p -> formatLocation(p.getLocation())) + .sendEntry("Group", LimboPlayer::getGroup, p -> ""); + sender.sendMessage("Note: group is not shown for Player. Use /authme debug groups"); + } + + /** + * Displays the info for the given LimboPlayer and Player to the provided CommandSender. + */ + private static final class InfoDisplayer { + private final CommandSender sender; + private final Optional diskLimbo; + private final Optional memoryLimbo; + private final Optional player; + + /** + * Constructor. + * + * @param sender command sender to send the information to + * @param memoryLimbo the limbo player to get data from + * @param player the player to get data from + */ + InfoDisplayer(CommandSender sender, LimboPlayer diskLimbo, LimboPlayer memoryLimbo, Player player) { + this.sender = sender; + this.diskLimbo = Optional.ofNullable(diskLimbo); + this.memoryLimbo = Optional.ofNullable(memoryLimbo); + this.player = Optional.ofNullable(player); + + if (memoryLimbo == null) { + sender.sendMessage("Note: no Limbo information available"); + } + if (player == null) { + sender.sendMessage("Note: player is not online"); + } else if (diskLimbo == null) { + sender.sendMessage("Note: no Limbo on disk available"); + } + } + + /** + * Displays a piece of information to the command sender. + * + * @param title the designation of the piece of information + * @param limboGetter getter for data retrieval on the LimboPlayer + * @param playerGetter getter for data retrieval on Player + * @param the data type + * @return this instance (for chaining) + */ + InfoDisplayer sendEntry(String title, + Function limboGetter, + Function playerGetter) { + sender.sendMessage( + title + ": " + + getData(diskLimbo, limboGetter) + + " / " + + getData(memoryLimbo, limboGetter) + + " / " + + getData(player, playerGetter)); + return this; + } + + static String getData(Optional entity, Function getter) { + return entity.map(getter).map(String::valueOf).orElse(" -- "); + } + } +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java new file mode 100644 index 00000000..a985c827 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java @@ -0,0 +1,104 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.util.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.List; + +import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation; + +/** + * Allows to view the data of a PlayerAuth in the database. + */ +class PlayerAuthViewer implements DebugSection { + + @Inject + private DataSource dataSource; + + @Override + public String getName() { + return "db"; + } + + @Override + public String getDescription() { + return "View player's data in the database"; + } + + @Override + public void execute(CommandSender sender, List arguments) { + if (arguments.isEmpty()) { + sender.sendMessage("Enter player name to view his data in the database."); + sender.sendMessage("Example: /authme debug db Bobby"); + return; + } + + PlayerAuth auth = dataSource.getAuth(arguments.get(0)); + if (auth == null) { + sender.sendMessage("No record exists for '" + arguments.get(0) + "'"); + } else { + displayAuthToSender(auth, sender); + } + } + + /** + * Outputs the PlayerAuth information to the given sender. + * + * @param auth the PlayerAuth to display + * @param sender the sender to send the messages to + */ + private void displayAuthToSender(PlayerAuth auth, CommandSender sender) { + sender.sendMessage(ChatColor.GOLD + "[AuthMe] Player " + auth.getNickname() + " / " + auth.getRealName()); + sender.sendMessage("Email: " + auth.getEmail() + ". IP: " + auth.getIp() + ". Group: " + auth.getGroupId()); + sender.sendMessage("Quit location: " + + formatLocation(auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ(), auth.getWorld())); + sender.sendMessage("Last login: " + formatLastLogin(auth)); + + HashedPassword hashedPass = auth.getPassword(); + sender.sendMessage("Hash / salt (partial): '" + safeSubstring(hashedPass.getHash(), 6) + + "' / '" + safeSubstring(hashedPass.getSalt(), 4) + "'"); + } + + /** + * Fail-safe substring method. Guarantees not to show the entire String. + * + * @param str the string to transform + * @param length number of characters to show from the start of the String + * @return the first length characters of the string, or half of the string if it is shorter, + * or empty string if the string is null or empty + */ + private static String safeSubstring(String str, int length) { + if (StringUtils.isEmpty(str)) { + return ""; + } else if (str.length() < length) { + return str.substring(0, str.length() / 2) + "..."; + } else { + return str.substring(0, length) + "..."; + } + } + + /** + * Formats the last login date from the given PlayerAuth. + * + * @param auth the auth object + * @return the last login as human readable date + */ + private static String formatLastLogin(PlayerAuth auth) { + long lastLogin = auth.getLastLogin(); + if (lastLogin == 0) { + return "Never (0)"; + } else { + LocalDateTime date = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastLogin), ZoneId.systemDefault()); + return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(date); + } + } +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java index b0abcd55..1d505254 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java @@ -41,8 +41,8 @@ class TestEmailSender implements DebugSection { @Override public void execute(CommandSender sender, List arguments) { if (!sendMailSSL.hasAllInformation()) { - sender.sendMessage(ChatColor.RED + "You haven't set all required configurations in config.yml " + - "for sending emails. Please check your config.yml"); + sender.sendMessage(ChatColor.RED + "You haven't set all required configurations in config.yml " + + "for sending emails. Please check your config.yml"); return; } @@ -69,7 +69,8 @@ class TestEmailSender implements DebugSection { } String email = auth.getEmail(); if (email == null || "your@email.com".equals(email)) { - sender.sendMessage(ChatColor.RED + "No email set for your account! Please use /authme debug mail "); + sender.sendMessage(ChatColor.RED + "No email set for your account!" + + " Please use /authme debug mail "); return null; } return email; diff --git a/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java b/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java index c5756cda..259e20f9 100644 --- a/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java @@ -3,8 +3,7 @@ package fr.xephi.authme.command.executable.captcha; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.data.CaptchaManager; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.command.PlayerCommand; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; import org.bukkit.entity.Player; @@ -24,7 +23,7 @@ public class CaptchaCommand extends PlayerCommand { private CommonService commonService; @Inject - private LimboCache limboCache; + private LimboService limboService; @Override public void runCommand(Player player, List arguments) { @@ -44,7 +43,7 @@ public class CaptchaCommand extends PlayerCommand { if (isCorrectCode) { commonService.send(player, MessageKey.CAPTCHA_SUCCESS); commonService.send(player, MessageKey.LOGIN_MESSAGE); - limboCache.getPlayerData(player.getName()).getMessageTask().setMuted(false); + limboService.unmuteMessageTask(player); } else { String newCode = captchaManager.generateCode(player.getName()); commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode); 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 151236e1..24a300ef 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 @@ -24,7 +24,7 @@ public class ShowEmailCommand extends PlayerCommand { @Override public void runCommand(Player player, List arguments) { PlayerAuth auth = playerCache.getAuth(player.getName()); - if (auth.getEmail() != null && !"your@email.com".equalsIgnoreCase(auth.getEmail())) { + if (auth != null && auth.getEmail() != null && !"your@email.com".equalsIgnoreCase(auth.getEmail())) { commonService.send(player, MessageKey.EMAIL_SHOW, auth.getEmail()); } else { commonService.send(player, MessageKey.SHOW_NO_EMAIL); diff --git a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java index 6a0a590c..5b9d75ab 100644 --- a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java @@ -7,7 +7,10 @@ import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; import fr.xephi.authme.process.register.RegisterSecondaryArgument; import fr.xephi.authme.process.register.RegistrationType; -import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider; +import fr.xephi.authme.process.register.executors.EmailRegisterParams; +import fr.xephi.authme.process.register.executors.PasswordRegisterParams; +import fr.xephi.authme.process.register.executors.RegistrationMethod; +import fr.xephi.authme.process.register.executors.TwoFactorRegisterParams; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.ValidationService; @@ -42,15 +45,12 @@ public class RegisterCommand extends PlayerCommand { @Inject private ValidationService validationService; - @Inject - private RegistrationExecutorProvider registrationExecutorProvider; - @Override public void runCommand(Player player, List arguments) { if (commonService.getProperty(SecuritySettings.PASSWORD_HASH) == HashAlgorithm.TWO_FACTOR) { //for two factor auth we don't need to check the usage - management.performRegister(player, - registrationExecutorProvider.getTwoFactorRegisterExecutor(player)); + management.performRegister(RegistrationMethod.TWO_FACTOR_REGISTRATION, + TwoFactorRegisterParams.of(player)); return; } else if (arguments.size() < 1) { commonService.send(player, MessageKey.USAGE_REGISTER); @@ -82,8 +82,8 @@ public class RegisterCommand extends PlayerCommand { final String password = arguments.get(0); final String email = getEmailIfAvailable(arguments); - management.performRegister( - player, registrationExecutorProvider.getPasswordRegisterExecutor(player, password, email)); + management.performRegister(RegistrationMethod.PASSWORD_REGISTRATION, + PasswordRegisterParams.of(player, password, email)); } } @@ -138,7 +138,8 @@ public class RegisterCommand extends PlayerCommand { if (!validationService.validateEmail(email)) { commonService.send(player, MessageKey.INVALID_EMAIL); } else if (isSecondArgValidForEmailRegistration(player, arguments)) { - management.performRegister(player, registrationExecutorProvider.getEmailRegisterExecutor(player, email)); + management.performRegister(RegistrationMethod.EMAIL_REGISTRATION, + EmailRegisterParams.of(player, email)); } } diff --git a/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java b/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java new file mode 100644 index 00000000..f085afbb --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java @@ -0,0 +1,43 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.entity.Player; + +import java.util.function.Function; + +/** + * Possible types to restore the "allow flight" property + * from LimboPlayer to Bukkit Player. + */ +public enum AllowFlightRestoreType { + + /** Set value from LimboPlayer to Player. */ + RESTORE(LimboPlayer::isCanFly), + + /** Always set flight enabled to true. */ + ENABLE(l -> true), + + /** Always set flight enabled to false. */ + DISABLE(l -> false); + + private final Function valueGetter; + + /** + * Constructor. + * + * @param valueGetter function with which the value to set on the player can be retrieved + */ + AllowFlightRestoreType(Function valueGetter) { + this.valueGetter = valueGetter; + } + + /** + * Restores the "allow flight" property from the LimboPlayer to the Player. + * This method behaves differently for each restoration type. + * + * @param player the player to modify + * @param limbo the limbo player to read from + */ + public void restoreAllowFlight(Player player, LimboPlayer limbo) { + player.setAllowFlight(valueGetter.apply(limbo)); + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java deleted file mode 100644 index 26883ca9..00000000 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java +++ /dev/null @@ -1,151 +0,0 @@ -package fr.xephi.authme.data.limbo; - -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.settings.SpawnLoader; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -import javax.inject.Inject; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * Manages all {@link LimboPlayer} instances. - */ -public class LimboCache { - - private final Map cache = new ConcurrentHashMap<>(); - - private LimboPlayerStorage limboPlayerStorage; - private PermissionsManager permissionsManager; - private SpawnLoader spawnLoader; - - @Inject - LimboCache(PermissionsManager permissionsManager, SpawnLoader spawnLoader, LimboPlayerStorage limboPlayerStorage) { - this.permissionsManager = permissionsManager; - this.spawnLoader = spawnLoader; - this.limboPlayerStorage = limboPlayerStorage; - } - - /** - * Load player data if exist, otherwise current player's data will be stored. - * - * @param player Player instance to add. - */ - public void addPlayerData(Player player) { - String name = player.getName().toLowerCase(); - Location location = spawnLoader.getPlayerLocationOrSpawn(player); - boolean operator = player.isOp(); - boolean flyEnabled = player.getAllowFlight(); - float walkSpeed = player.getWalkSpeed(); - float flySpeed = player.getFlySpeed(); - String playerGroup = ""; - if (permissionsManager.hasGroupSupport()) { - playerGroup = permissionsManager.getPrimaryGroup(player); - } - ConsoleLogger.debug("Player `{0}` has primary group `{1}`", player.getName(), playerGroup); - - if (limboPlayerStorage.hasData(player)) { - LimboPlayer cache = limboPlayerStorage.readData(player); - if (cache != null) { - location = cache.getLocation(); - playerGroup = cache.getGroup(); - operator = cache.isOperator(); - flyEnabled = cache.isCanFly(); - walkSpeed = cache.getWalkSpeed(); - flySpeed = cache.getFlySpeed(); - } - } else { - limboPlayerStorage.saveData(player); - } - - cache.put(name, new LimboPlayer(location, operator, playerGroup, flyEnabled, walkSpeed, flySpeed)); - } - - /** - * Restore player's data to player if exist. - * - * @param player Player instance to restore - */ - public void restoreData(Player player) { - String lowerName = player.getName().toLowerCase(); - if (cache.containsKey(lowerName)) { - LimboPlayer data = cache.get(lowerName); - player.setOp(data.isOperator()); - player.setAllowFlight(data.isCanFly()); - float walkSpeed = data.getWalkSpeed(); - float flySpeed = data.getFlySpeed(); - // Reset the speed value if it was 0 - if (walkSpeed < 0.01f) { - walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED; - } - if (flySpeed < 0.01f) { - flySpeed = LimboPlayer.DEFAULT_FLY_SPEED; - } - player.setWalkSpeed(walkSpeed); - player.setFlySpeed(flySpeed); - data.clearTasks(); - } - } - - /** - * Remove PlayerData from cache and disk. - * - * @param player Player player to remove. - */ - public void deletePlayerData(Player player) { - removeFromCache(player); - limboPlayerStorage.removeData(player); - } - - /** - * Remove PlayerData from cache. - * - * @param player player to remove. - */ - public void removeFromCache(Player player) { - String name = player.getName().toLowerCase(); - LimboPlayer cachedPlayer = cache.remove(name); - if (cachedPlayer != null) { - cachedPlayer.clearTasks(); - } - } - - /** - * Method getPlayerData. - * - * @param name String - * - * @return PlayerData - */ - public LimboPlayer getPlayerData(String name) { - checkNotNull(name); - return cache.get(name.toLowerCase()); - } - - /** - * Method hasPlayerData. - * - * @param name String - * - * @return boolean - */ - public boolean hasPlayerData(String name) { - checkNotNull(name); - return cache.containsKey(name.toLowerCase()); - } - - /** - * Method updatePlayerData. - * - * @param player Player - */ - public void updatePlayerData(Player player) { - checkNotNull(player); - removeFromCache(player); - addPlayerData(player); - } -} diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java index 45fcd7ad..6ba4ae2c 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java @@ -118,13 +118,7 @@ public class LimboPlayer { * Clears all tasks associated to the player. */ public void clearTasks() { - if (messageTask != null) { - messageTask.cancel(); - } - messageTask = null; - if (timeoutTask != null) { - timeoutTask.cancel(); - } - timeoutTask = null; + setMessageTask(null); + setTimeoutTask(null); } } diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java deleted file mode 100644 index 1f077d2a..00000000 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java +++ /dev/null @@ -1,213 +0,0 @@ -package fr.xephi.authme.data.limbo; - -import com.google.common.io.Files; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.initialization.DataFolder; -import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.service.BukkitService; -import fr.xephi.authme.settings.SpawnLoader; -import fr.xephi.authme.util.FileUtils; -import fr.xephi.authme.util.PlayerUtils; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Player; - -import javax.inject.Inject; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; - -/** - * Class used to store player's data (OP, flying, speed, position) to disk. - */ -public class LimboPlayerStorage { - - private final Gson gson; - private final File cacheDir; - private PermissionsManager permissionsManager; - private SpawnLoader spawnLoader; - private BukkitService bukkitService; - - @Inject - LimboPlayerStorage(@DataFolder File dataFolder, PermissionsManager permsMan, - SpawnLoader spawnLoader, BukkitService bukkitService) { - this.permissionsManager = permsMan; - this.spawnLoader = spawnLoader; - this.bukkitService = bukkitService; - - cacheDir = new File(dataFolder, "playerdata"); - if (!cacheDir.exists() && !cacheDir.isDirectory() && !cacheDir.mkdir()) { - ConsoleLogger.warning("Failed to create userdata directory."); - } - gson = new GsonBuilder() - .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer()) - .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer()) - .setPrettyPrinting() - .create(); - } - - /** - * Read and construct new PlayerData from existing player data. - * - * @param player player to read - * - * @return PlayerData object if the data is exist, null otherwise. - */ - public LimboPlayer readData(Player player) { - String id = PlayerUtils.getUUIDorName(player); - File file = new File(cacheDir, id + File.separator + "data.json"); - if (!file.exists()) { - return null; - } - - try { - String str = Files.toString(file, StandardCharsets.UTF_8); - return gson.fromJson(str, LimboPlayer.class); - } catch (IOException e) { - ConsoleLogger.logException("Could not read player data on disk for '" + player.getName() + "'", e); - return null; - } - } - - /** - * Save player data (OP, flying, location, etc) to disk. - * - * @param player player to save - */ - public void saveData(Player player) { - String id = PlayerUtils.getUUIDorName(player); - Location location = spawnLoader.getPlayerLocationOrSpawn(player); - String group = ""; - if (permissionsManager.hasGroupSupport()) { - group = permissionsManager.getPrimaryGroup(player); - } - boolean operator = player.isOp(); - boolean canFly = player.getAllowFlight(); - float walkSpeed = player.getWalkSpeed(); - float flySpeed = player.getFlySpeed(); - LimboPlayer limboPlayer = new LimboPlayer(location, operator, group, canFly, walkSpeed, flySpeed); - try { - File file = new File(cacheDir, id + File.separator + "data.json"); - Files.createParentDirs(file); - Files.touch(file); - Files.write(gson.toJson(limboPlayer), file, StandardCharsets.UTF_8); - } catch (IOException e) { - ConsoleLogger.logException("Failed to write " + player.getName() + " data.", e); - } - } - - /** - * Remove player data, this will delete - * "playerdata/<uuid or name>/" folder from disk. - * - * @param player player to remove - */ - public void removeData(Player player) { - String id = PlayerUtils.getUUIDorName(player); - File file = new File(cacheDir, id); - if (file.exists()) { - FileUtils.purgeDirectory(file); - if (!file.delete()) { - ConsoleLogger.warning("Failed to remove " + player.getName() + " cache."); - } - } - } - - /** - * Use to check is player data is exist. - * - * @param player player to check - * - * @return true if data exist, false otherwise. - */ - public boolean hasData(Player player) { - String id = PlayerUtils.getUUIDorName(player); - File file = new File(cacheDir, id + File.separator + "data.json"); - return file.exists(); - } - - private class LimboPlayerDeserializer implements JsonDeserializer { - @Override - public LimboPlayer deserialize(JsonElement jsonElement, Type type, - JsonDeserializationContext context) { - JsonObject jsonObject = jsonElement.getAsJsonObject(); - if (jsonObject == null) { - return null; - } - - Location loc = null; - String group = ""; - boolean operator = false; - boolean canFly = false; - float walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED; - float flySpeed = LimboPlayer.DEFAULT_FLY_SPEED; - - JsonElement e; - if ((e = jsonObject.getAsJsonObject("location")) != null) { - JsonObject obj = e.getAsJsonObject(); - World world = bukkitService.getWorld(obj.get("world").getAsString()); - if (world != null) { - double x = obj.get("x").getAsDouble(); - double y = obj.get("y").getAsDouble(); - double z = obj.get("z").getAsDouble(); - float yaw = obj.get("yaw").getAsFloat(); - float pitch = obj.get("pitch").getAsFloat(); - loc = new Location(world, x, y, z, yaw, pitch); - } - } - if ((e = jsonObject.get("group")) != null) { - group = e.getAsString(); - } - if ((e = jsonObject.get("operator")) != null) { - operator = e.getAsBoolean(); - } - if ((e = jsonObject.get("can-fly")) != null) { - canFly = e.getAsBoolean(); - } - if ((e = jsonObject.get("walk-speed")) != null) { - walkSpeed = e.getAsFloat(); - } - if ((e = jsonObject.get("fly-speed")) != null) { - flySpeed = e.getAsFloat(); - } - - return new LimboPlayer(loc, operator, group, canFly, walkSpeed, flySpeed); - } - } - - private class LimboPlayerSerializer implements JsonSerializer { - @Override - public JsonElement serialize(LimboPlayer limboPlayer, Type type, - JsonSerializationContext context) { - JsonObject obj = new JsonObject(); - obj.addProperty("group", limboPlayer.getGroup()); - - Location loc = limboPlayer.getLocation(); - JsonObject obj2 = new JsonObject(); - obj2.addProperty("world", loc.getWorld().getName()); - obj2.addProperty("x", loc.getX()); - obj2.addProperty("y", loc.getY()); - obj2.addProperty("z", loc.getZ()); - obj2.addProperty("yaw", loc.getYaw()); - obj2.addProperty("pitch", loc.getPitch()); - obj.add("location", obj2); - - obj.addProperty("operator", limboPlayer.isOperator()); - obj.addProperty("can-fly", limboPlayer.isCanFly()); - obj.addProperty("walk-speed", limboPlayer.getWalkSpeed()); - obj.addProperty("fly-speed", limboPlayer.getFlySpeed()); - return obj; - } - } - - -} diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManager.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManager.java new file mode 100644 index 00000000..4e0c0a33 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManager.java @@ -0,0 +1,97 @@ +package fr.xephi.authme.data.limbo; + +import fr.xephi.authme.data.auth.PlayerCache; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.message.Messages; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.task.MessageTask; +import fr.xephi.authme.task.TimeoutTask; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; + +import javax.inject.Inject; + +import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND; + +/** + * Registers tasks associated with a LimboPlayer. + */ +class LimboPlayerTaskManager { + + @Inject + private Messages messages; + + @Inject + private Settings settings; + + @Inject + private BukkitService bukkitService; + + @Inject + private PlayerCache playerCache; + + LimboPlayerTaskManager() { + } + + /** + * Registers a {@link MessageTask} for the given player name. + * + * @param player the player + * @param limbo the associated limbo player of the player + * @param isRegistered whether the player is registered or not + * (false shows "please register", true shows "please log in") + */ + void registerMessageTask(Player player, LimboPlayer limbo, boolean isRegistered) { + int interval = settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL); + MessageKey key = getMessageKey(isRegistered); + if (interval > 0) { + MessageTask messageTask = new MessageTask(player, messages.retrieve(key)); + bukkitService.runTaskTimer(messageTask, 2 * TICKS_PER_SECOND, interval * TICKS_PER_SECOND); + limbo.setMessageTask(messageTask); + } + } + + /** + * Registers a {@link TimeoutTask} for the given player according to the configuration. + * + * @param player the player to register a timeout task for + * @param limbo the associated limbo player + */ + void registerTimeoutTask(Player player, LimboPlayer limbo) { + final int timeout = settings.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; + if (timeout > 0) { + String message = messages.retrieveSingle(MessageKey.LOGIN_TIMEOUT_ERROR); + BukkitTask task = bukkitService.runTaskLater(new TimeoutTask(player, message, playerCache), timeout); + limbo.setTimeoutTask(task); + } + } + + /** + * Null-safe method to set the muted flag on a message task. + * + * @param task the task to modify (or null) + * @param isMuted the value to set if task is not null + */ + static void setMuted(MessageTask task, boolean isMuted) { + if (task != null) { + task.setMuted(isMuted); + } + } + + /** + * Returns the appropriate message key according to the registration status and settings. + * + * @param isRegistered whether or not the username is registered + * @return the message key to display to the user + */ + private static MessageKey getMessageKey(boolean isRegistered) { + if (isRegistered) { + return MessageKey.LOGIN_MESSAGE; + } else { + return MessageKey.REGISTER_MESSAGE; + } + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java new file mode 100644 index 00000000..e78ca313 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java @@ -0,0 +1,170 @@ +package fr.xephi.authme.data.limbo; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.persistence.LimboPersistence; +import fr.xephi.authme.settings.Settings; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_ALLOW_FLIGHT; +import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_FLY_SPEED; +import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_WALK_SPEED; + +/** + * Service for managing players that are in "limbo," a temporary state players are + * put in which have joined but not yet logged in yet. + */ +public class LimboService { + + private final Map entries = new ConcurrentHashMap<>(); + + @Inject + private Settings settings; + + @Inject + private LimboPlayerTaskManager taskManager; + + @Inject + private LimboServiceHelper helper; + + @Inject + private LimboPersistence persistence; + + LimboService() { + } + + /** + * Creates a LimboPlayer for the given player and revokes all "limbo data" from the player. + * + * @param player the player to process + * @param isRegistered whether or not the player is registered + */ + public void createLimboPlayer(Player player, boolean isRegistered) { + final String name = player.getName().toLowerCase(); + + LimboPlayer limboFromDisk = persistence.getLimboPlayer(player); + if (limboFromDisk != null) { + ConsoleLogger.debug("LimboPlayer for `{0}` already exists on disk", name); + } + + LimboPlayer existingLimbo = entries.remove(name); + if (existingLimbo != null) { + existingLimbo.clearTasks(); + ConsoleLogger.debug("LimboPlayer for `{0}` already present in memory", name); + } + + LimboPlayer limboPlayer = helper.merge(existingLimbo, limboFromDisk); + limboPlayer = helper.merge(helper.createLimboPlayer(player, isRegistered), limboPlayer); + + taskManager.registerMessageTask(player, limboPlayer, isRegistered); + taskManager.registerTimeoutTask(player, limboPlayer); + helper.revokeLimboStates(player); + entries.put(name, limboPlayer); + persistence.saveLimboPlayer(player, limboPlayer); + } + + /** + * Returns the limbo player for the given name, or null otherwise. + * + * @param name the name to retrieve the data for + * @return the associated limbo player, or null if none available + */ + public LimboPlayer getLimboPlayer(String name) { + return entries.get(name.toLowerCase()); + } + + /** + * Returns whether there is a limbo player for the given name. + * + * @param name the name to check + * @return true if present, false otherwise + */ + public boolean hasLimboPlayer(String name) { + return entries.containsKey(name.toLowerCase()); + } + + /** + * Restores the limbo data and subsequently deletes the entry. + *

    + * Note that teleportation on the player is performed by {@link fr.xephi.authme.service.TeleportationService} and + * changing the permission group is handled by {@link fr.xephi.authme.permission.AuthGroupHandler}. + * + * @param player the player whose data should be restored + */ + public void restoreData(Player player) { + String lowerName = player.getName().toLowerCase(); + LimboPlayer limbo = entries.remove(lowerName); + + if (limbo == null) { + ConsoleLogger.debug("No LimboPlayer found for `{0}` - cannot restore", lowerName); + } else { + player.setOp(limbo.isOperator()); + settings.getProperty(RESTORE_ALLOW_FLIGHT).restoreAllowFlight(player, limbo); + settings.getProperty(RESTORE_FLY_SPEED).restoreFlySpeed(player, limbo); + settings.getProperty(RESTORE_WALK_SPEED).restoreWalkSpeed(player, limbo); + limbo.clearTasks(); + ConsoleLogger.debug("Restored LimboPlayer stats for `{0}`", lowerName); + persistence.removeLimboPlayer(player); + } + } + + /** + * Creates new tasks for the given player and cancels the old ones for a newly registered player. + * This resets his time to log in (TimeoutTask) and updates the message he is shown (MessageTask). + * + * @param player the player to reset the tasks for + */ + public void replaceTasksAfterRegistration(Player player) { + getLimboOrLogError(player, "reset tasks") + .ifPresent(limbo -> { + taskManager.registerTimeoutTask(player, limbo); + taskManager.registerMessageTask(player, limbo, true); + }); + } + + /** + * Resets the message task associated with the player's LimboPlayer. + * + * @param player the player to set a new message task for + * @param isRegistered whether or not the player is registered + */ + public void resetMessageTask(Player player, boolean isRegistered) { + getLimboOrLogError(player, "reset message task") + .ifPresent(limbo -> taskManager.registerMessageTask(player, limbo, isRegistered)); + } + + /** + * @param player the player whose message task should be muted + */ + public void muteMessageTask(Player player) { + getLimboOrLogError(player, "mute message task") + .ifPresent(limbo -> LimboPlayerTaskManager.setMuted(limbo.getMessageTask(), true)); + } + + /** + * @param player the player whose message task should be unmuted + */ + public void unmuteMessageTask(Player player) { + getLimboOrLogError(player, "unmute message task") + .ifPresent(limbo -> LimboPlayerTaskManager.setMuted(limbo.getMessageTask(), false)); + } + + /** + * Returns the limbo player for the given player or logs an error. + * + * @param player the player to retrieve the limbo player for + * @param context the action for which the limbo player is being retrieved (for logging) + * @return Optional with the limbo player + */ + private Optional getLimboOrLogError(Player player, String context) { + LimboPlayer limbo = entries.get(player.getName().toLowerCase()); + if (limbo == null) { + ConsoleLogger.debug("No LimboPlayer found for `{0}`. Action: {1}", player.getName(), context); + } + return Optional.ofNullable(limbo); + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java new file mode 100644 index 00000000..7373212f --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java @@ -0,0 +1,107 @@ +package fr.xephi.authme.data.limbo; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.SpawnLoader; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +/** + * Helper class for the LimboService. + */ +class LimboServiceHelper { + + @Inject + private SpawnLoader spawnLoader; + + @Inject + private PermissionsManager permissionsManager; + + @Inject + private Settings settings; + + /** + * Creates a LimboPlayer with the given player's details. + * + * @param player the player to process + * @param isRegistered whether the player is registered + * @return limbo player with the player's data + */ + LimboPlayer createLimboPlayer(Player player, boolean isRegistered) { + Location location = spawnLoader.getPlayerLocationOrSpawn(player); + // For safety reasons an unregistered player should not have OP status after registration + boolean isOperator = isRegistered && player.isOp(); + boolean flyEnabled = player.getAllowFlight(); + float walkSpeed = player.getWalkSpeed(); + float flySpeed = player.getFlySpeed(); + String playerGroup = permissionsManager.hasGroupSupport() + ? permissionsManager.getPrimaryGroup(player) : ""; + ConsoleLogger.debug("Player `{0}` has primary group `{1}`", player.getName(), playerGroup); + + return new LimboPlayer(location, isOperator, playerGroup, flyEnabled, walkSpeed, flySpeed); + } + + /** + * Removes the data that is saved in a LimboPlayer from the player. + *

    + * Note that teleportation on the player is performed by {@link fr.xephi.authme.service.TeleportationService} and + * changing the permission group is handled by {@link fr.xephi.authme.permission.AuthGroupHandler}. + * + * @param player the player to set defaults to + */ + void revokeLimboStates(Player player) { + player.setOp(false); + player.setAllowFlight(false); + + if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT) + && settings.getProperty(RestrictionSettings.REMOVE_SPEED)) { + player.setFlySpeed(0.0f); + player.setWalkSpeed(0.0f); + } + } + + /** + * Merges two existing LimboPlayer instances of a player. Merging is done the following way: + *

      + *
    • isOperator, allowFlight: true if either limbo has true
    • + *
    • flySpeed, walkSpeed: maximum value of either limbo player
    • + *
    • group, location: from old limbo if not empty/null, otherwise from new limbo
    • + *
    + * + * @param newLimbo the new limbo player + * @param oldLimbo the old limbo player + * @return merged limbo player if both arguments are not null, otherwise the first non-null argument + */ + LimboPlayer merge(LimboPlayer newLimbo, LimboPlayer oldLimbo) { + if (newLimbo == null) { + return oldLimbo; + } else if (oldLimbo == null) { + return newLimbo; + } + + boolean isOperator = newLimbo.isOperator() || oldLimbo.isOperator(); + boolean canFly = newLimbo.isCanFly() || oldLimbo.isCanFly(); + float flySpeed = Math.max(newLimbo.getFlySpeed(), oldLimbo.getFlySpeed()); + float walkSpeed = Math.max(newLimbo.getWalkSpeed(), oldLimbo.getWalkSpeed()); + String group = firstNotEmpty(newLimbo.getGroup(), oldLimbo.getGroup()); + Location location = firstNotNull(oldLimbo.getLocation(), newLimbo.getLocation()); + + return new LimboPlayer(location, isOperator, group, canFly, walkSpeed, flySpeed); + } + + private static String firstNotEmpty(String newGroup, String oldGroup) { + ConsoleLogger.debug("Limbo merge: new and old perm groups are `{0}` and `{1}`", newGroup, oldGroup); + if ("".equals(oldGroup)) { + return newGroup; + } + return oldGroup; + } + + private static Location firstNotNull(Location first, Location second) { + return first == null ? second : first; + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreType.java b/src/main/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreType.java new file mode 100644 index 00000000..960cdd43 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreType.java @@ -0,0 +1,81 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.entity.Player; + +/** + * Possible types to restore the walk and fly speed from LimboPlayer + * back to Bukkit Player. + */ +public enum WalkFlySpeedRestoreType { + + /** Restores from LimboPlayer to Player. */ + RESTORE { + @Override + public void restoreFlySpeed(Player player, LimboPlayer limbo) { + player.setFlySpeed(limbo.getFlySpeed()); + } + + @Override + public void restoreWalkSpeed(Player player, LimboPlayer limbo) { + player.setWalkSpeed(limbo.getWalkSpeed()); + } + }, + + /** Restores from LimboPlayer, using the default speed if the speed on LimboPlayer is 0. */ + RESTORE_NO_ZERO { + @Override + public void restoreFlySpeed(Player player, LimboPlayer limbo) { + float limboFlySpeed = limbo.getFlySpeed(); + player.setFlySpeed(limboFlySpeed > 0.01f ? limboFlySpeed : LimboPlayer.DEFAULT_FLY_SPEED); + } + + @Override + public void restoreWalkSpeed(Player player, LimboPlayer limbo) { + float limboWalkSpeed = limbo.getWalkSpeed(); + player.setWalkSpeed(limboWalkSpeed > 0.01f ? limboWalkSpeed : LimboPlayer.DEFAULT_WALK_SPEED); + } + }, + + /** Uses the max speed of Player (current speed) and the LimboPlayer. */ + MAX_RESTORE { + @Override + public void restoreFlySpeed(Player player, LimboPlayer limbo) { + player.setFlySpeed(Math.max(player.getFlySpeed(), limbo.getFlySpeed())); + } + + @Override + public void restoreWalkSpeed(Player player, LimboPlayer limbo) { + player.setWalkSpeed(Math.max(player.getWalkSpeed(), limbo.getWalkSpeed())); + } + }, + + /** Always sets the default speed to the player. */ + DEFAULT { + @Override + public void restoreFlySpeed(Player player, LimboPlayer limbo) { + player.setFlySpeed(LimboPlayer.DEFAULT_FLY_SPEED); + } + + @Override + public void restoreWalkSpeed(Player player, LimboPlayer limbo) { + player.setWalkSpeed(LimboPlayer.DEFAULT_WALK_SPEED); + } + }; + + /** + * Restores the fly speed from Limbo to Player according to the restoration type. + * + * @param player the player to modify + * @param limbo the limbo player to read from + */ + public abstract void restoreFlySpeed(Player player, LimboPlayer limbo); + + /** + * Restores the walk speed from Limbo to Player according to the restoration type. + * + * @param player the player to modify + * @param limbo the limbo player to read from + */ + public abstract void restoreWalkSpeed(Player player, LimboPlayer limbo); + +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java new file mode 100644 index 00000000..f07e82cd --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java @@ -0,0 +1,79 @@ +package fr.xephi.authme.data.limbo.persistence; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.initialization.SettingsDependent; +import fr.xephi.authme.initialization.factory.Factory; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.LimboSettings; +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +/** + * Handles the persistence of LimboPlayers. + */ +public class LimboPersistence implements SettingsDependent { + + private final Factory handlerFactory; + + private LimboPersistenceHandler handler; + + @Inject + LimboPersistence(Settings settings, Factory handlerFactory) { + this.handlerFactory = handlerFactory; + reload(settings); + } + + /** + * Retrieves the LimboPlayer for the given player if available. + * + * @param player the player to retrieve the LimboPlayer for + * @return the player's limbo player, or null if not available + */ + public LimboPlayer getLimboPlayer(Player player) { + try { + return handler.getLimboPlayer(player); + } catch (Exception e) { + ConsoleLogger.logException("Could not get LimboPlayer for '" + player.getName() + "'", e); + } + return null; + } + + /** + * Saves the given LimboPlayer for the provided player. + * + * @param player the player to save the LimboPlayer for + * @param limbo the limbo player to save + */ + public void saveLimboPlayer(Player player, LimboPlayer limbo) { + try { + handler.saveLimboPlayer(player, limbo); + } catch (Exception e) { + ConsoleLogger.logException("Could not save LimboPlayer for '" + player.getName() + "'", e); + } + } + + /** + * Removes the LimboPlayer for the given player. + * + * @param player the player whose LimboPlayer should be removed + */ + public void removeLimboPlayer(Player player) { + try { + handler.removeLimboPlayer(player); + } catch (Exception e) { + ConsoleLogger.logException("Could not remove LimboPlayer for '" + player.getName() + "'", e); + } + } + + @Override + public void reload(Settings settings) { + LimboPersistenceType persistenceType = settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE); + // If we're changing from an existing handler, output a quick hint that nothing is converted. + if (handler != null && handler.getType() != persistenceType) { + ConsoleLogger.info("Limbo persistence type has changed! Note that the data is not converted."); + } + handler = handlerFactory.newInstance(persistenceType.getImplementationClass()); + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceHandler.java new file mode 100644 index 00000000..95e88aad --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceHandler.java @@ -0,0 +1,39 @@ +package fr.xephi.authme.data.limbo.persistence; + +import fr.xephi.authme.data.limbo.LimboPlayer; +import org.bukkit.entity.Player; + +/** + * Handles I/O for storing LimboPlayer objects. + */ +interface LimboPersistenceHandler { + + /** + * Returns the limbo player for the given player if it exists. + * + * @param player the player + * @return the stored limbo player, or null if not available + */ + LimboPlayer getLimboPlayer(Player player); + + /** + * Saves the given limbo player for the given player to the disk. + * + * @param player the player to save the limbo player for + * @param limbo the limbo player to save + */ + void saveLimboPlayer(Player player, LimboPlayer limbo); + + /** + * Removes the limbo player from the disk. + * + * @param player the player whose limbo player should be removed + */ + void removeLimboPlayer(Player player); + + /** + * @return the type of the limbo persistence implementation + */ + LimboPersistenceType getType(); + +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java new file mode 100644 index 00000000..68b4611b --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java @@ -0,0 +1,37 @@ +package fr.xephi.authme.data.limbo.persistence; + +/** + * Types of persistence for LimboPlayer objects. + */ +public enum LimboPersistenceType { + + /** Store each LimboPlayer in a separate file. */ + INDIVIDUAL_FILES(SeparateFilePersistenceHandler.class), + + /** Store all LimboPlayers in the same file. */ + SINGLE_FILE(SingleFilePersistenceHandler.class), + + /** Distribute LimboPlayers by segments into a set number of files. */ + SEGMENT_FILES(SegmentFilesPersistenceHolder.class), + + /** No persistence to disk. */ + DISABLED(NoOpPersistenceHandler.class); + + private final Class implementationClass; + + /** + * Constructor. + * + * @param implementationClass the implementation class + */ + LimboPersistenceType(Class implementationClass) { + this.implementationClass= implementationClass; + } + + /** + * @return class implementing the persistence type + */ + public Class getImplementationClass() { + return implementationClass; + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java new file mode 100644 index 00000000..94e1950b --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java @@ -0,0 +1,115 @@ +package fr.xephi.authme.data.limbo.persistence; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.service.BukkitService; +import org.bukkit.Location; +import org.bukkit.World; + +import java.lang.reflect.Type; +import java.util.function.Function; + +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.CAN_FLY; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.FLY_SPEED; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.GROUP; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.IS_OP; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOCATION; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_PITCH; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_WORLD; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_X; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_Y; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_YAW; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_Z; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.WALK_SPEED; + +/** + * Converts a JsonElement to a LimboPlayer. + */ +class LimboPlayerDeserializer implements JsonDeserializer { + + private BukkitService bukkitService; + + LimboPlayerDeserializer(BukkitService bukkitService) { + this.bukkitService = bukkitService; + } + + @Override + public LimboPlayer deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + if (jsonObject == null) { + return null; + } + + Location loc = deserializeLocation(jsonObject); + boolean operator = getBoolean(jsonObject, IS_OP); + String group = getString(jsonObject, GROUP); + boolean canFly = getBoolean(jsonObject, CAN_FLY); + float walkSpeed = getFloat(jsonObject, WALK_SPEED, LimboPlayer.DEFAULT_WALK_SPEED); + float flySpeed = getFloat(jsonObject, FLY_SPEED, LimboPlayer.DEFAULT_FLY_SPEED); + + return new LimboPlayer(loc, operator, group, canFly, walkSpeed, flySpeed); + } + + private Location deserializeLocation(JsonObject jsonObject) { + JsonElement e; + if ((e = jsonObject.getAsJsonObject(LOCATION)) != null) { + JsonObject locationObject = e.getAsJsonObject(); + World world = bukkitService.getWorld(getString(locationObject, LOC_WORLD)); + if (world != null) { + double x = getDouble(locationObject, LOC_X); + double y = getDouble(locationObject, LOC_Y); + double z = getDouble(locationObject, LOC_Z); + float yaw = getFloat(locationObject, LOC_YAW); + float pitch = getFloat(locationObject, LOC_PITCH); + return new Location(world, x, y, z, yaw, pitch); + } + } + return null; + } + + private static String getString(JsonObject jsonObject, String memberName) { + JsonElement element = jsonObject.get(memberName); + return element != null ? element.getAsString() : ""; + } + + private static boolean getBoolean(JsonObject jsonObject, String memberName) { + JsonElement element = jsonObject.get(memberName); + return element != null && element.getAsBoolean(); + } + + private static float getFloat(JsonObject jsonObject, String memberName) { + return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsFloat, 0.0f); + } + + private static float getFloat(JsonObject jsonObject, String memberName, float defaultValue) { + return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsFloat, defaultValue); + } + + private static double getDouble(JsonObject jsonObject, String memberName) { + return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsDouble, 0.0); + } + + /** + * Gets a number from the given JsonElement safely. + * + * @param jsonElement the element to retrieve the number from + * @param numberFunction the function to get the number from the element + * @param defaultValue the value to return if the element is null or the number cannot be retrieved + * @param the number type + * @return the number from the given JSON element, or the default value + */ + private static N getNumberFromElement(JsonElement jsonElement, + Function numberFunction, + N defaultValue) { + if (jsonElement != null) { + try { + return numberFunction.apply(jsonElement); + } catch (NumberFormatException ignore) { + } + } + return defaultValue; + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java new file mode 100644 index 00000000..aeae3b65 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java @@ -0,0 +1,52 @@ +package fr.xephi.authme.data.limbo.persistence; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import fr.xephi.authme.data.limbo.LimboPlayer; +import org.bukkit.Location; + +import java.lang.reflect.Type; + +/** + * Converts a LimboPlayer to a JsonElement. + */ +class LimboPlayerSerializer implements JsonSerializer { + + static final String LOCATION = "location"; + static final String LOC_WORLD = "world"; + static final String LOC_X = "x"; + static final String LOC_Y = "y"; + static final String LOC_Z = "z"; + static final String LOC_YAW = "yaw"; + static final String LOC_PITCH = "pitch"; + + static final String GROUP = "group"; + static final String IS_OP = "operator"; + static final String CAN_FLY = "can-fly"; + static final String WALK_SPEED = "walk-speed"; + static final String FLY_SPEED = "fly-speed"; + + + @Override + public JsonElement serialize(LimboPlayer limboPlayer, Type type, JsonSerializationContext context) { + Location loc = limboPlayer.getLocation(); + JsonObject locationObject = new JsonObject(); + locationObject.addProperty(LOC_WORLD, loc.getWorld().getName()); + locationObject.addProperty(LOC_X, loc.getX()); + locationObject.addProperty(LOC_Y, loc.getY()); + locationObject.addProperty(LOC_Z, loc.getZ()); + locationObject.addProperty(LOC_YAW, loc.getYaw()); + locationObject.addProperty(LOC_PITCH, loc.getPitch()); + + JsonObject obj = new JsonObject(); + obj.add(LOCATION, locationObject); + obj.addProperty(GROUP, limboPlayer.getGroup()); + obj.addProperty(IS_OP, limboPlayer.isOperator()); + obj.addProperty(CAN_FLY, limboPlayer.isCanFly()); + obj.addProperty(WALK_SPEED, limboPlayer.getWalkSpeed()); + obj.addProperty(FLY_SPEED, limboPlayer.getFlySpeed()); + return obj; + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/NoOpPersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/NoOpPersistenceHandler.java new file mode 100644 index 00000000..ac6ff9b3 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/NoOpPersistenceHandler.java @@ -0,0 +1,30 @@ +package fr.xephi.authme.data.limbo.persistence; + +import fr.xephi.authme.data.limbo.LimboPlayer; +import org.bukkit.entity.Player; + +/** + * Limbo player persistence implementation that does nothing. + */ +class NoOpPersistenceHandler implements LimboPersistenceHandler { + + @Override + public LimboPlayer getLimboPlayer(Player player) { + return null; + } + + @Override + public void saveLimboPlayer(Player player, LimboPlayer limbo) { + // noop + } + + @Override + public void removeLimboPlayer(Player player) { + // noop + } + + @Override + public LimboPersistenceType getType() { + return LimboPersistenceType.DISABLED; + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java new file mode 100644 index 00000000..5053ba52 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java @@ -0,0 +1,94 @@ +package fr.xephi.authme.data.limbo.persistence; + +/** + * Configuration for the total number of segments to use. + *

    + * The {@link SegmentFilesPersistenceHolder} reduces the number of files by assigning each UUID + * to a segment. This enum allows to define how many segments the UUIDs should be distributed in. + *

    + * Segments are defined by a distribution and a length. The distribution defines + * to how many outputs a single hexadecimal characters should be mapped. So e.g. a distribution + * of 3 means that all hexadecimal characters 0-f should be distributed over three different + * outputs evenly. The {@link SegmentNameBuilder} simply uses hexadecimal characters as outputs, + * so e.g. with a distribution of 3 all hex characters 0-f are mapped to 0, 1, or 2. + *

    + * To ensure an even distribution the segments must be powers of 2. Trivially, to implement a + * distribution of 16, the same character may be returned as was input (since 0-f make up 16 + * characters). A distribution of 1, on the other hand, means that the same output is returned + * regardless of the input character. + *

    + * The length parameter defines how many characters of a player's UUID should be used to + * create the segment ID. In other words, with a distribution of 2 and a length of 3, the first + * three characters of the UUID are taken into consideration, each mapped to one of two possible + * characters. For instance, a UUID starting with "0f5c9321" may yield the segment ID "010." + * Such a segment ID defines in which file the given UUID can be found and stored. + *

    + * The number of segments such a configuration yields is computed as {@code distribution ^ length}, + * since distribution defines how many outputs there are per digit, and length defines the number + * of digits. For instance, a distribution of 2 and a length of 3 will yield segment IDs 000, 001, + * 010, 011, 100, 101, 110 and 111 (i.e. all binary numbers from 0 to 7). + *

    + * There are multiple possibilities to achieve certain segment totals, e.g. 8 different segments + * may be created by setting distribution to 8 and length to 1, or distr. to 2 and length to 3. + * Where possible, prefer a length of 1 (no string concatenation required) or a distribution of + * 16 (no remapping of the characters required). + */ +public enum SegmentConfiguration { + + /** 1. */ + ONE(1, 1), + + ///** 2. */ + //TWO(2, 1), + + /** 4. */ + FOUR(4, 1), + + /** 8. */ + EIGHT(8, 1), + + /** 16. */ + SIXTEEN(16, 1), + + /** 32. */ + THIRTY_TWO(2, 5), + + /** 64. */ + SIXTY_FOUR(4, 3), + + /** 128. */ + ONE_TWENTY(2, 7), + + /** 256. */ + TWO_FIFTY(16, 2); + + private final int distribution; + private final int length; + + SegmentConfiguration(int distribution, int length) { + this.distribution = distribution; + this.length = length; + } + + /** + * @return the distribution size per character, i.e. how many possible outputs there are + * for any hexadecimal character + */ + public int getDistribution() { + return distribution; + } + + /** + * @return number of characters from a UUID that should be used to create a segment ID + */ + public int getLength() { + return length; + } + + /** + * @return number of segments to which this configuration will distribute UUIDs + */ + public int getTotalSegments() { + return (int) Math.pow(distribution, length); + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java new file mode 100644 index 00000000..e786ca48 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java @@ -0,0 +1,226 @@ +package fr.xephi.authme.data.limbo.persistence; + +import com.google.common.io.Files; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.LimboSettings; +import fr.xephi.authme.util.FileUtils; +import fr.xephi.authme.util.PlayerUtils; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.io.File; +import java.io.FileWriter; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Persistence handler for LimboPlayer objects by distributing the objects to store + * in various segments (buckets) based on the start of the player's UUID. + */ +class SegmentFilesPersistenceHolder implements LimboPersistenceHandler { + + private static final Type LIMBO_MAP_TYPE = new TypeToken>(){}.getType(); + + private final File cacheFolder; + private final Gson gson; + private final SegmentNameBuilder segmentNameBuilder; + + @Inject + SegmentFilesPersistenceHolder(@DataFolder File dataFolder, BukkitService bukkitService, Settings settings) { + cacheFolder = new File(dataFolder, "playerdata"); + if (!cacheFolder.exists()) { + // TODO ljacqu 20170313: Create FileUtils#mkdirs + cacheFolder.mkdirs(); + } + + gson = new GsonBuilder() + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer()) + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService)) + .setPrettyPrinting() + .create(); + + segmentNameBuilder = new SegmentNameBuilder(settings.getProperty(LimboSettings.SEGMENT_DISTRIBUTION)); + + convertOldDataToCurrentSegmentScheme(); + deleteEmptyFiles(); + } + + @Override + public LimboPlayer getLimboPlayer(Player player) { + String uuid = PlayerUtils.getUUIDorName(player); + File file = getPlayerSegmentFile(uuid); + Map entries = readLimboPlayers(file); + return entries == null ? null : entries.get(uuid); + } + + @Override + public void saveLimboPlayer(Player player, LimboPlayer limbo) { + String uuid = PlayerUtils.getUUIDorName(player); + File file = getPlayerSegmentFile(uuid); + + Map entries = null; + if (file.exists()) { + entries = readLimboPlayers(file); + } else { + FileUtils.create(file); + } + /* intentionally separate if */ + if (entries == null) { + entries = new HashMap<>(); + } + + entries.put(PlayerUtils.getUUIDorName(player), limbo); + saveEntries(entries, file); + } + + @Override + public void removeLimboPlayer(Player player) { + String uuid = PlayerUtils.getUUIDorName(player); + File file = getPlayerSegmentFile(uuid); + if (file.exists()) { + Map entries = readLimboPlayers(file); + if (entries != null && entries.remove(PlayerUtils.getUUIDorName(player)) != null) { + saveEntries(entries, file); + } + } + } + + @Override + public LimboPersistenceType getType() { + return LimboPersistenceType.SEGMENT_FILES; + } + + private void saveEntries(Map entries, File file) { + try (FileWriter fw = new FileWriter(file)) { + gson.toJson(entries, fw); + } catch (Exception e) { + ConsoleLogger.logException("Could not write to '" + file + "':", e); + } + } + + private Map readLimboPlayers(File file) { + if (!file.exists()) { + return null; + } + + try { + return gson.fromJson(Files.toString(file, StandardCharsets.UTF_8), LIMBO_MAP_TYPE); + } catch (Exception e) { + ConsoleLogger.logException("Failed reading '" + file + "':", e); + } + return null; + } + + private File getPlayerSegmentFile(String uuid) { + String segment = segmentNameBuilder.createSegmentName(uuid); + return new File(cacheFolder, segment + "-limbo.json"); + } + + /** + * Loads segment files in the cache folder that don't correspond to the current segmenting scheme + * and migrates the data into files of the current segments. This allows a player to change the + * segment size without any loss of data. + */ + private void convertOldDataToCurrentSegmentScheme() { + String currentPrefix = segmentNameBuilder.getPrefix(); + File[] files = listFiles(cacheFolder); + Map allLimboPlayers = new HashMap<>(); + List migratedFiles = new ArrayList<>(); + + for (File file : files) { + if (isLimboJsonFile(file) && !file.getName().startsWith(currentPrefix)) { + Map data = readLimboPlayers(file); + if (data != null) { + allLimboPlayers.putAll(data); + migratedFiles.add(file); + } + } + } + + if (!allLimboPlayers.isEmpty()) { + saveToNewSegments(allLimboPlayers); + migratedFiles.forEach(FileUtils::delete); + } + } + + /** + * Saves the LimboPlayer data read from old segmenting schemes into the current segmenting scheme. + * + * @param limbosFromOldSegments the limbo players to store into the current segment files + */ + private void saveToNewSegments(Map limbosFromOldSegments) { + Map> limboBySegment = groupBySegment(limbosFromOldSegments); + + ConsoleLogger.info("Saving " + limbosFromOldSegments.size() + " LimboPlayers from old segments into " + + limboBySegment.size() + " current segments"); + for (Map.Entry> entry : limboBySegment.entrySet()) { + File file = new File(cacheFolder, entry.getKey() + "-limbo.json"); + Map limbosToSave = Optional.ofNullable(readLimboPlayers(file)) + .orElseGet(HashMap::new); + limbosToSave.putAll(entry.getValue()); + saveEntries(limbosToSave, file); + } + } + + /** + * Converts a Map of UUID to LimboPlayers to a 2-dimensional Map of LimboPlayers by segment ID and UUID. + * {@code Map(uuid -> LimboPlayer) to Map(segment -> Map(uuid -> LimboPlayer))} + * + * @param readLimboPlayers the limbo players to order by segment + * @return limbo players ordered by segment ID and associated player UUID + */ + private Map> groupBySegment(Map readLimboPlayers) { + Map> limboBySegment = new HashMap<>(); + for (Map.Entry entry : readLimboPlayers.entrySet()) { + String segmentId = segmentNameBuilder.createSegmentName(entry.getKey()); + limboBySegment.computeIfAbsent(segmentId, s -> new HashMap<>()) + .put(entry.getKey(), entry.getValue()); + } + return limboBySegment; + } + + /** + * Deletes files from the current segmenting scheme that are empty. + */ + private void deleteEmptyFiles() { + File[] files = listFiles(cacheFolder); + + long deletedFiles = Arrays.stream(files) + // typically the size is 2 because there's an empty JSON map: {} + .filter(f -> isLimboJsonFile(f) && f.length() < 3) + .peek(FileUtils::delete) + .count(); + ConsoleLogger.debug("Limbo: Deleted {0} empty segment files", deletedFiles); + } + + /** + * @param file the file to check + * @return true if it is a segment file storing Limbo data, false otherwise + */ + private static boolean isLimboJsonFile(File file) { + String name = file.getName(); + return name.startsWith("seg") && name.endsWith("-limbo.json"); + } + + private static File[] listFiles(File folder) { + File[] files = folder.listFiles(); + if (files == null) { + ConsoleLogger.warning("Could not get files of '" + folder + "'"); + return new File[0]; + } + return files; + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java new file mode 100644 index 00000000..52e1141b --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java @@ -0,0 +1,73 @@ +package fr.xephi.authme.data.limbo.persistence; + +import java.util.HashMap; +import java.util.Map; + +/** + * Creates segment names for {@link SegmentFilesPersistenceHolder}. + */ +class SegmentNameBuilder { + + private final int length; + private final int distribution; + private final String prefix; + private final Map charToSegmentChar; + + /** + * Constructor. + * + * @param partition the segment configuration + */ + SegmentNameBuilder(SegmentConfiguration partition) { + this.length = partition.getLength(); + this.distribution = partition.getDistribution(); + this.prefix = "seg" + partition.getTotalSegments() + "-"; + this.charToSegmentChar = buildCharMap(distribution); + } + + /** + * Returns the segment ID for the given UUID. + * + * @param uuid the player's uuid to get the segment for + * @return id the uuid belongs to + */ + String createSegmentName(String uuid) { + if (distribution == 16) { + return prefix + uuid.substring(0, length); + } else { + return prefix + buildSegmentName(uuid.substring(0, length).toCharArray()); + } + } + + /** + * @return the prefix used for the current segment configuration + */ + String getPrefix() { + return prefix; + } + + private String buildSegmentName(char[] chars) { + if (chars.length == 1) { + return String.valueOf(charToSegmentChar.get(chars[0])); + } + + StringBuilder sb = new StringBuilder(chars.length); + for (char chr : chars) { + sb.append(charToSegmentChar.get(chr)); + } + return sb.toString(); + } + + private static Map buildCharMap(int distribution) { + final char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + final int divisor = 16 / distribution; + + Map charToSegmentChar = new HashMap<>(); + for (int i = 0; i < hexChars.length; ++i) { + int mappedChar = i / divisor; + charToSegmentChar.put(hexChars[i], hexChars[mappedChar]); + } + return charToSegmentChar; + } + +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java new file mode 100644 index 00000000..438bce69 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java @@ -0,0 +1,90 @@ +package fr.xephi.authme.data.limbo.persistence; + +import com.google.common.io.Files; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.util.FileUtils; +import fr.xephi.authme.util.PlayerUtils; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Saves LimboPlayer objects as JSON into individual files. + */ +class SeparateFilePersistenceHandler implements LimboPersistenceHandler { + + private final Gson gson; + private final File cacheDir; + + @Inject + SeparateFilePersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService) { + cacheDir = new File(dataFolder, "playerdata"); + if (!cacheDir.exists() && !cacheDir.isDirectory() && !cacheDir.mkdir()) { + ConsoleLogger.warning("Failed to create playerdata directory '" + cacheDir + "'"); + } + gson = new GsonBuilder() + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer()) + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService)) + .setPrettyPrinting() + .create(); + } + + @Override + public LimboPlayer getLimboPlayer(Player player) { + String id = PlayerUtils.getUUIDorName(player); + File file = new File(cacheDir, id + File.separator + "data.json"); + if (!file.exists()) { + return null; + } + + try { + String str = Files.toString(file, StandardCharsets.UTF_8); + return gson.fromJson(str, LimboPlayer.class); + } catch (IOException e) { + ConsoleLogger.logException("Could not read player data on disk for '" + player.getName() + "'", e); + return null; + } + } + + @Override + public void saveLimboPlayer(Player player, LimboPlayer limboPlayer) { + String id = PlayerUtils.getUUIDorName(player); + try { + File file = new File(cacheDir, id + File.separator + "data.json"); + Files.createParentDirs(file); + Files.touch(file); + Files.write(gson.toJson(limboPlayer), file, StandardCharsets.UTF_8); + } catch (IOException e) { + ConsoleLogger.logException("Failed to write " + player.getName() + " data:", e); + } + } + + /** + * Removes the LimboPlayer. This will delete the + * "playerdata/<uuid or name>/" folder from disk. + * + * @param player player to remove + */ + @Override + public void removeLimboPlayer(Player player) { + String id = PlayerUtils.getUUIDorName(player); + File file = new File(cacheDir, id); + if (file.exists()) { + FileUtils.purgeDirectory(file); + FileUtils.delete(file); + } + } + + @Override + public LimboPersistenceType getType() { + return LimboPersistenceType.INDIVIDUAL_FILES; + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SingleFilePersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SingleFilePersistenceHandler.java new file mode 100644 index 00000000..57aa2dcf --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SingleFilePersistenceHandler.java @@ -0,0 +1,94 @@ +package fr.xephi.authme.data.limbo.persistence; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.util.FileUtils; +import fr.xephi.authme.util.PlayerUtils; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Saves all LimboPlayers in one JSON file and keeps the entries in memory. + */ +class SingleFilePersistenceHandler implements LimboPersistenceHandler { + + private final File cacheFile; + private final Gson gson; + private Map entries; + + @Inject + SingleFilePersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService) { + cacheFile = new File(dataFolder, "limbo.json"); + if (!cacheFile.exists()) { + FileUtils.create(cacheFile); + } + + gson = new GsonBuilder() + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer()) + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService)) + .setPrettyPrinting() + .create(); + + Type type = new TypeToken>(){}.getType(); + try (FileReader fr = new FileReader(cacheFile)) { + entries = gson.fromJson(fr, type); + } catch (IOException e) { + ConsoleLogger.logException("Failed to read from '" + cacheFile + "':", e); + } + + if (entries == null) { + entries = new ConcurrentHashMap<>(); + } + } + + @Override + public LimboPlayer getLimboPlayer(Player player) { + return entries.get(PlayerUtils.getUUIDorName(player)); + } + + @Override + public void saveLimboPlayer(Player player, LimboPlayer limbo) { + entries.put(PlayerUtils.getUUIDorName(player), limbo); + saveEntries("adding '" + player.getName() + "'"); + } + + @Override + public void removeLimboPlayer(Player player) { + LimboPlayer entry = entries.remove(PlayerUtils.getUUIDorName(player)); + if (entry != null) { + saveEntries("removing '" + player.getName() + "'"); + } + } + + /** + * Saves the entries to the disk. + * + * @param action the reason for saving (for logging purposes) + */ + private void saveEntries(String action) { + try (FileWriter fw = new FileWriter(cacheFile)) { + gson.toJson(entries, fw); + } catch (IOException e) { + ConsoleLogger.logException("Failed saving JSON limbo cache after " + action, e); + } + } + + @Override + public LimboPersistenceType getType() { + return LimboPersistenceType.SINGLE_FILE; + } +} diff --git a/src/main/java/fr/xephi/authme/datasource/Columns.java b/src/main/java/fr/xephi/authme/datasource/Columns.java index b6d732cd..f984007e 100644 --- a/src/main/java/fr/xephi/authme/datasource/Columns.java +++ b/src/main/java/fr/xephi/authme/datasource/Columns.java @@ -6,6 +6,8 @@ import fr.xephi.authme.settings.properties.DatabaseSettings; /** * Database column names. */ +// Justification: String is immutable and this class is used to easily access the configurable column names +@SuppressWarnings({"checkstyle:VisibilityModifier", "checkstyle:MemberName", "checkstyle:AbbreviationAsWordInName"}) public final class Columns { public final String NAME; diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index f62b00e1..4e6937d4 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -7,7 +7,7 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.security.crypts.XFBCRYPT; +import fr.xephi.authme.security.crypts.XfBCrypt; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.DatabaseSettings; import fr.xephi.authme.settings.properties.HooksSettings; @@ -45,7 +45,10 @@ public class MySQL implements DataSource { private HikariDataSource ds; private String phpBbPrefix; + private String IPBPrefix; private int phpBbGroup; + private int IPBGroup; + private int XFGroup; private String wordpressPrefix; public MySQL(Settings settings) throws ClassNotFoundException, SQLException { @@ -96,6 +99,9 @@ public class MySQL implements DataSource { this.hashAlgorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH); this.phpBbPrefix = settings.getProperty(HooksSettings.PHPBB_TABLE_PREFIX); this.phpBbGroup = settings.getProperty(HooksSettings.PHPBB_ACTIVATED_GROUP_ID); + this.IPBPrefix = settings.getProperty(HooksSettings.IPB_TABLE_PREFIX); + this.IPBGroup = settings.getProperty(HooksSettings.IPB_ACTIVATED_GROUP_ID); + this.XFGroup = settings.getProperty(HooksSettings.XF_ACTIVATED_GROUP_ID); this.wordpressPrefix = settings.getProperty(HooksSettings.WORDPRESS_TABLE_PREFIX); this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE); if (poolSize == -1) { @@ -286,7 +292,7 @@ public class MySQL implements DataSource { if (rs.next()) { Blob blob = rs.getBlob("data"); byte[] bytes = blob.getBytes(1, (int) blob.length()); - auth.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes))); + auth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes))); } } } @@ -334,8 +340,39 @@ public class MySQL implements DataSource { pst.close(); } } - - if (hashAlgorithm == HashAlgorithm.PHPBB) { + if (hashAlgorithm == HashAlgorithm.IPB4){ + sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; + pst = con.prepareStatement(sql); + pst.setString(1, auth.getNickname()); + rs = pst.executeQuery(); + if (rs.next()){ + // Update player group in core_members + sql = "UPDATE " + IPBPrefix + tableName + " SET "+ tableName + ".member_group_id=? WHERE " + col.NAME + "=?;"; + pst2 = con.prepareStatement(sql); + pst2.setInt(1, IPBGroup); + pst2.setString(2, auth.getNickname()); + pst2.executeUpdate(); + pst2.close(); + // Get current time without ms + long time = System.currentTimeMillis() / 1000; + // update joined date + sql = "UPDATE " + IPBPrefix + tableName + " SET "+ tableName + ".joined=? WHERE " + col.NAME + "=?;"; + pst2 = con.prepareStatement(sql); + pst2.setLong(1, time); + pst2.setString(2, auth.getNickname()); + pst2.executeUpdate(); + pst2.close(); + // Update last_visit + sql = "UPDATE " + IPBPrefix + tableName + " SET " + tableName + ".last_visit=? WHERE " + col.NAME + "=?;"; + pst2 = con.prepareStatement(sql); + pst2.setLong(1, time); + pst2.setString(2, auth.getNickname()); + pst2.executeUpdate(); + pst2.close(); + } + rs.close(); + pst.close(); + } else if (hashAlgorithm == HashAlgorithm.PHPBB) { sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; pst = con.prepareStatement(sql); pst.setString(1, auth.getNickname()); @@ -477,19 +514,53 @@ public class MySQL implements DataSource { pst = con.prepareStatement("SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;"); pst.setString(1, auth.getNickname()); rs = pst.executeQuery(); - if (rs.next()) { + if (rs.next()) { int id = rs.getInt(col.ID); + // Insert player password, salt in xf_user_authenticate sql = "INSERT INTO xf_user_authenticate (user_id, scheme_class, data) VALUES (?,?,?)"; pst2 = con.prepareStatement(sql); pst2.setInt(1, id); - pst2.setString(2, XFBCRYPT.SCHEME_CLASS); - String serializedHash = XFBCRYPT.serializeHash(auth.getPassword().getHash()); + pst2.setString(2, XfBCrypt.SCHEME_CLASS); + String serializedHash = XfBCrypt.serializeHash(auth.getPassword().getHash()); byte[] bytes = serializedHash.getBytes(); Blob blob = con.createBlob(); blob.setBytes(1, bytes); pst2.setBlob(3, blob); pst2.executeUpdate(); pst2.close(); + // Update player group in xf_users + sql = "UPDATE " + tableName + " SET "+ tableName + ".user_group_id=? WHERE " + col.NAME + "=?;"; + pst2 = con.prepareStatement(sql); + pst2.setInt(1, XFGroup); + pst2.setString(2, auth.getNickname()); + pst2.executeUpdate(); + pst2.close(); + // Update player permission combination in xf_users + sql = "UPDATE " + tableName + " SET "+ tableName + ".permission_combination_id=? WHERE " + col.NAME + "=?;"; + pst2 = con.prepareStatement(sql); + pst2.setInt(1, XFGroup); + pst2.setString(2, auth.getNickname()); + pst2.executeUpdate(); + pst2.close(); + // Insert player privacy combination in xf_user_privacy + sql = "INSERT INTO xf_user_privacy (user_id, allow_view_profile, allow_post_profile, allow_send_personal_conversation, allow_view_identities, allow_receive_news_feed) VALUES (?,?,?,?,?,?)"; + pst2 = con.prepareStatement(sql); + pst2.setInt(1, id); + pst2.setString(2, "everyone"); + pst2.setString(3, "members"); + pst2.setString(4, "members"); + pst2.setString(5, "everyone"); + pst2.setString(6, "everyone"); + pst2.executeUpdate(); + pst2.close(); + // Insert player group relation in xf_user_group_relation + sql = "INSERT INTO xf_user_group_relation (user_id, user_group_id, is_primary) VALUES (?,?,?)"; + pst2 = con.prepareStatement(sql); + pst2.setInt(1, id); + pst2.setInt(2, XFGroup); + pst2.setString(3, "1"); + pst2.executeUpdate(); + pst2.close(); } rs.close(); pst.close(); @@ -538,7 +609,7 @@ public class MySQL implements DataSource { // Insert password in the correct table sql = "UPDATE xf_user_authenticate SET data=? WHERE " + col.ID + "=?;"; PreparedStatement pst2 = con.prepareStatement(sql); - String serializedHash = XFBCRYPT.serializeHash(password.getHash()); + String serializedHash = XfBCrypt.serializeHash(password.getHash()); byte[] bytes = serializedHash.getBytes(); Blob blob = con.createBlob(); blob.setBytes(1, bytes); @@ -549,7 +620,7 @@ public class MySQL implements DataSource { // ... sql = "UPDATE xf_user_authenticate SET scheme_class=? WHERE " + col.ID + "=?;"; pst2 = con.prepareStatement(sql); - pst2.setString(1, XFBCRYPT.SCHEME_CLASS); + pst2.setString(1, XfBCrypt.SCHEME_CLASS); pst2.setInt(2, id); pst2.executeUpdate(); pst2.close(); @@ -824,7 +895,7 @@ public class MySQL implements DataSource { if (rs2.next()) { Blob blob = rs2.getBlob("data"); byte[] bytes = blob.getBytes(1, (int) blob.length()); - pAuth.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes))); + pAuth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes))); } rs2.close(); } @@ -856,7 +927,7 @@ public class MySQL implements DataSource { if (rs2.next()) { Blob blob = rs2.getBlob("data"); byte[] bytes = blob.getBytes(1, (int) blob.length()); - pAuth.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes))); + pAuth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes))); } rs2.close(); } diff --git a/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java index eb9d904c..42a8ecf1 100644 --- a/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java +++ b/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java @@ -16,6 +16,7 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; /** @@ -40,19 +41,17 @@ public class RakamakConverter implements Converter { @Override // TODO ljacqu 20151229: Restructure this into smaller portions public void execute(CommandSender sender) { - boolean useIP = settings.getProperty(ConverterSettings.RAKAMAK_USE_IP); + boolean useIp = settings.getProperty(ConverterSettings.RAKAMAK_USE_IP); String fileName = settings.getProperty(ConverterSettings.RAKAMAK_FILE_NAME); String ipFileName = settings.getProperty(ConverterSettings.RAKAMAK_IP_FILE_NAME); File source = new File(pluginFolder, fileName); - File ipfiles = new File(pluginFolder, ipFileName); - HashMap playerIP = new HashMap<>(); - HashMap playerPSW = new HashMap<>(); + File ipFiles = new File(pluginFolder, ipFileName); + Map playerIP = new HashMap<>(); + Map playerPassword = new HashMap<>(); try { - BufferedReader users; - BufferedReader ipFile; - ipFile = new BufferedReader(new FileReader(ipfiles)); + BufferedReader ipFile = new BufferedReader(new FileReader(ipFiles)); String line; - if (useIP) { + if (useIp) { String tempLine; while ((tempLine = ipFile.readLine()) != null) { if (tempLine.contains("=")) { @@ -63,20 +62,20 @@ public class RakamakConverter implements Converter { } ipFile.close(); - users = new BufferedReader(new FileReader(source)); + BufferedReader users = new BufferedReader(new FileReader(source)); while ((line = users.readLine()) != null) { if (line.contains("=")) { String[] arguments = line.split("="); HashedPassword hashedPassword = passwordSecurity.computeHash(arguments[1], arguments[0]); - playerPSW.put(arguments[0], hashedPassword); + playerPassword.put(arguments[0], hashedPassword); } } users.close(); - for (Entry m : playerPSW.entrySet()) { + for (Entry m : playerPassword.entrySet()) { String playerName = m.getKey(); - HashedPassword psw = playerPSW.get(playerName); - String ip = useIP ? playerIP.get(playerName) : "127.0.0.1"; + HashedPassword psw = playerPassword.get(playerName); + String ip = useIp ? playerIP.get(playerName) : "127.0.0.1"; PlayerAuth auth = PlayerAuth.builder() .name(playerName) .realName(playerName) diff --git a/src/main/java/fr/xephi/authme/datasource/converter/vAuthConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/VAuthConverter.java similarity index 95% rename from src/main/java/fr/xephi/authme/datasource/converter/vAuthConverter.java rename to src/main/java/fr/xephi/authme/datasource/converter/VAuthConverter.java index 6d3de8b7..ff88db2d 100644 --- a/src/main/java/fr/xephi/authme/datasource/converter/vAuthConverter.java +++ b/src/main/java/fr/xephi/authme/datasource/converter/VAuthConverter.java @@ -16,13 +16,13 @@ import java.util.UUID; import static fr.xephi.authme.util.FileUtils.makePath; -public class vAuthConverter implements Converter { +public class VAuthConverter implements Converter { private final DataSource dataSource; private final File vAuthPasswordsFile; @Inject - vAuthConverter(@DataFolder File dataFolder, DataSource dataSource) { + VAuthConverter(@DataFolder File dataFolder, DataSource dataSource) { vAuthPasswordsFile = new File(dataFolder.getParent(), makePath("vAuth", "passwords.yml")); this.dataSource = dataSource; } diff --git a/src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/XAuthConverter.java similarity index 98% rename from src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java rename to src/main/java/fr/xephi/authme/datasource/converter/XAuthConverter.java index af4d5beb..97979c6c 100644 --- a/src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java +++ b/src/main/java/fr/xephi/authme/datasource/converter/XAuthConverter.java @@ -21,7 +21,7 @@ import java.util.List; import static fr.xephi.authme.util.FileUtils.makePath; -public class xAuthConverter implements Converter { +public class XAuthConverter implements Converter { @Inject @DataFolder @@ -31,7 +31,7 @@ public class xAuthConverter implements Converter { @Inject private PluginManager pluginManager; - xAuthConverter() { + XAuthConverter() { } @Override diff --git a/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java b/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java index 11e9d6af..e22eb9c5 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java +++ b/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java @@ -2,15 +2,14 @@ package fr.xephi.authme.initialization; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboPlayerStorage; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.PluginHookService; +import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.service.BukkitService; -import fr.xephi.authme.service.ValidationService; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -28,17 +27,15 @@ public class OnShutdownPlayerSaver { @Inject private ValidationService validationService; @Inject - private LimboCache limboCache; - @Inject private DataSource dataSource; @Inject - private LimboPlayerStorage limboPlayerStorage; - @Inject private SpawnLoader spawnLoader; @Inject private PluginHookService pluginHookService; @Inject private PlayerCache playerCache; + @Inject + private LimboService limboService; OnShutdownPlayerSaver() { } @@ -57,9 +54,8 @@ public class OnShutdownPlayerSaver { if (pluginHookService.isNpc(player) || validationService.isUnrestricted(name)) { return; } - if (limboCache.hasPlayerData(name)) { - limboCache.restoreData(player); - limboCache.removeFromCache(player); + if (limboService.hasLimboPlayer(name)) { + limboService.restoreData(player); } else { saveLoggedinPlayer(player); } @@ -75,9 +71,5 @@ public class OnShutdownPlayerSaver { .location(loc).build(); dataSource.updateQuitLoc(auth); } - if (settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN) - && !settings.getProperty(RestrictionSettings.NO_TELEPORT) && !limboPlayerStorage.hasData(player)) { - limboPlayerStorage.saveData(player); - } } } diff --git a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java index 34996508..8767e88a 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java +++ b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java @@ -45,6 +45,12 @@ public class OnStartupTasks { OnStartupTasks() { } + /** + * Sends bstats metrics. + * + * @param plugin the plugin instance + * @param settings the settings + */ public static void sendMetrics(AuthMe plugin, Settings settings) { final Metrics metrics = new Metrics(plugin); diff --git a/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java b/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java index 04c11c68..e063d149 100644 --- a/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java +++ b/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java @@ -16,8 +16,8 @@ public class FactoryDependencyHandler implements DependencyHandler { if (dependencyDescription.getType() == Factory.class) { Class genericType = ReflectionUtils.getGenericType(dependencyDescription.getGenericType()); if (genericType == null) { - throw new IllegalStateException("Factory fields must have concrete generic type. " + - "Cannot get generic type for field in '" + context.getMappedClass() + "'"); + throw new IllegalStateException("Factory fields must have concrete generic type. " + + "Cannot get generic type for field in '" + context.getMappedClass() + "'"); } return new FactoryImpl<>(genericType, context.getInjector()); diff --git a/src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java new file mode 100644 index 00000000..13a5cbb5 --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java @@ -0,0 +1,37 @@ +package fr.xephi.authme.initialization.factory; + +import java.util.Collection; + +/** + * Injectable object to retrieve and create singletons of a common parent. + * + * @param

    the parent class to which this store is limited to + */ +public interface SingletonStore

    { + + /** + * Returns the singleton of the given type, creating it if it hasn't been yet created. + * + * @param clazz the class to get the singleton for + * @param type of the singleton + * @return the singleton of type {@code C} + */ + C getSingleton(Class clazz); + + /** + * Returns all existing singletons of this store's type. + * + * @return all registered singletons of type {@code P} + */ + Collection

    retrieveAllOfType(); + + /** + * Returns all existing singletons of the given type. + * + * @param clazz the type to get singletons for + * @param class type + * @return all registered singletons of type {@code C} + */ + Collection retrieveAllOfType(Class clazz); + +} diff --git a/src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java new file mode 100644 index 00000000..09a198c7 --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java @@ -0,0 +1,61 @@ +package fr.xephi.authme.initialization.factory; + +import ch.jalu.injector.Injector; +import ch.jalu.injector.context.ResolvedInstantiationContext; +import ch.jalu.injector.handlers.dependency.DependencyHandler; +import ch.jalu.injector.handlers.instantiation.DependencyDescription; +import ch.jalu.injector.utils.ReflectionUtils; + +import java.util.Collection; + +/** + * Dependency handler that builds {@link SingletonStore} objects. + */ +public class SingletonStoreDependencyHandler implements DependencyHandler { + + @Override + public Object resolveValue(ResolvedInstantiationContext context, DependencyDescription dependencyDescription) { + if (dependencyDescription.getType() == SingletonStore.class) { + Class genericType = ReflectionUtils.getGenericType(dependencyDescription.getGenericType()); + if (genericType == null) { + throw new IllegalStateException("Singleton store fields must have concrete generic type. " + + "Cannot get generic type for field in '" + context.getMappedClass() + "'"); + } + + return new SingletonStoreImpl<>(genericType, context.getInjector()); + } + return null; + } + + private static final class SingletonStoreImpl

    implements SingletonStore

    { + + private final Injector injector; + private final Class

    parentClass; + + SingletonStoreImpl(Class

    parentClass, Injector injector) { + this.parentClass = parentClass; + this.injector = injector; + } + + @Override + public C getSingleton(Class clazz) { + if (parentClass.isAssignableFrom(clazz)) { + return injector.getSingleton(clazz); + } + throw new IllegalArgumentException(clazz + " not child of " + parentClass); + } + + @Override + public Collection

    retrieveAllOfType() { + return retrieveAllOfType(parentClass); + } + + @Override + public Collection retrieveAllOfType(Class clazz) { + if (parentClass.isAssignableFrom(clazz)) { + return injector.retrieveAllOfType(clazz); + } + throw new IllegalArgumentException(clazz + " not child of " + parentClass); + } + } +} diff --git a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java index e3ff4ec5..06a8761a 100644 --- a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java +++ b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java @@ -111,7 +111,7 @@ class OnJoinVerifier implements Reloadable { * @param event the login event to verify * * @return true if the player's connection should be refused (i.e. the event does not need to be processed - * further), false if the player is not refused + * further), false if the player is not refused */ public boolean refusePlayerForFullServer(PlayerLoginEvent event) { final Player player = event.getPlayer(); diff --git a/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java b/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java index 48cf1868..05150765 100644 --- a/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java +++ b/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java @@ -14,6 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package fr.xephi.authme.listener.protocollib; import com.comphenix.protocol.PacketType; @@ -46,7 +47,7 @@ class InventoryPacketAdapter extends PacketAdapter { private static final int MAIN_SIZE = 27; private static final int HOTBAR_SIZE = 9; - public InventoryPacketAdapter(AuthMe plugin) { + InventoryPacketAdapter(AuthMe plugin) { super(plugin, PacketType.Play.Server.SET_SLOT, PacketType.Play.Server.WINDOW_ITEMS); } diff --git a/src/main/java/fr/xephi/authme/listener/protocollib/TabCompletePacketAdapter.java b/src/main/java/fr/xephi/authme/listener/protocollib/TabCompletePacketAdapter.java index 4476f80a..daf68638 100644 --- a/src/main/java/fr/xephi/authme/listener/protocollib/TabCompletePacketAdapter.java +++ b/src/main/java/fr/xephi/authme/listener/protocollib/TabCompletePacketAdapter.java @@ -12,7 +12,7 @@ import fr.xephi.authme.data.auth.PlayerCache; class TabCompletePacketAdapter extends PacketAdapter { - public TabCompletePacketAdapter(AuthMe plugin) { + TabCompletePacketAdapter(AuthMe plugin) { super(plugin, ListenerPriority.NORMAL, PacketType.Play.Client.TAB_COMPLETE); } diff --git a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java index 344c39c9..ece40900 100644 --- a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java +++ b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java @@ -1,8 +1,10 @@ package fr.xephi.authme.mail; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.output.LogLevel; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.util.StringUtils; import org.apache.commons.mail.EmailConstants; import org.apache.commons.mail.EmailException; @@ -56,6 +58,9 @@ public class SendMailSSL { email.setFrom(senderMail, senderName); email.setSubject(settings.getProperty(EmailSettings.RECOVERY_MAIL_SUBJECT)); email.setAuthentication(settings.getProperty(EmailSettings.MAIL_ACCOUNT), mailPassword); + if (settings.getProperty(PluginSettings.LOG_LEVEL).includes(LogLevel.DEBUG)) { + email.setDebug(true); + } setPropertiesForPort(email, port); return email; diff --git a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java index c7a3b343..5019d316 100644 --- a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java +++ b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java @@ -1,8 +1,8 @@ package fr.xephi.authme.permission; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.PluginSettings; @@ -33,7 +33,7 @@ public class AuthGroupHandler implements Reloadable { private Settings settings; @Inject - private LimboCache limboCache; + private LimboService limboService; private String unregisteredGroup; private String registeredGroup; @@ -53,7 +53,7 @@ public class AuthGroupHandler implements Reloadable { } String primaryGroup = Optional - .ofNullable(limboCache.getPlayerData(player.getName())) + .ofNullable(limboService.getLimboPlayer(player.getName())) .map(LimboPlayer::getGroup) .orElse(""); diff --git a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java index b7773e7a..acae466c 100644 --- a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java +++ b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java @@ -31,7 +31,8 @@ public class PermissionsBukkitHandler implements PermissionHandler { @Override public boolean addToGroup(Player player, String group) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player addgroup " + player.getName() + " " + group); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "permissions player addgroup " + player.getName() + " " + group); } @Override @@ -46,17 +47,19 @@ public class PermissionsBukkitHandler implements PermissionHandler { @Override public boolean removeFromGroup(Player player, String group) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player removegroup " + player.getName() + " " + group); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "permissions player removegroup " + player.getName() + " " + group); } @Override public boolean setGroup(Player player, String group) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player setgroup " + player.getName() + " " + group); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "permissions player setgroup " + player.getName() + " " + group); } @Override public List getGroups(Player player) { - List groups = new ArrayList(); + List groups = new ArrayList<>(); for (Group group : permissionsBukkitInstance.getGroups(player.getUniqueId())) { groups.add(group.getName()); } diff --git a/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java index 1de19f1d..c86863f7 100644 --- a/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java +++ b/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java @@ -30,7 +30,8 @@ public class ZPermissionsHandler implements PermissionHandler { @Override public boolean addToGroup(Player player, String group) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " addgroup " + group); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "permissions player " + player.getName() + " addgroup " + group); } @Override @@ -41,20 +42,23 @@ public class ZPermissionsHandler implements PermissionHandler { @Override public boolean hasPermissionOffline(String name, PermissionNode node) { Map perms = zPermissionsService.getPlayerPermissions(null, null, name); - if (perms.containsKey(node.getNode())) + if (perms.containsKey(node.getNode())) { return perms.get(node.getNode()); - else + } else { return false; + } } @Override public boolean removeFromGroup(Player player, String group) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " removegroup " + group); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "permissions player " + player.getName() + " removegroup " + group); } @Override public boolean setGroup(Player player, String group) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " setgroup " + group); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "permissions player " + player.getName() + " setgroup " + group); } @Override diff --git a/src/main/java/fr/xephi/authme/process/Management.java b/src/main/java/fr/xephi/authme/process/Management.java index 8aea8341..fee94957 100644 --- a/src/main/java/fr/xephi/authme/process/Management.java +++ b/src/main/java/fr/xephi/authme/process/Management.java @@ -8,7 +8,8 @@ import fr.xephi.authme.process.login.AsynchronousLogin; import fr.xephi.authme.process.logout.AsynchronousLogout; import fr.xephi.authme.process.quit.AsynchronousQuit; import fr.xephi.authme.process.register.AsyncRegister; -import fr.xephi.authme.process.register.executors.RegistrationExecutor; +import fr.xephi.authme.process.register.executors.RegistrationMethod; +import fr.xephi.authme.process.register.executors.RegistrationParameters; import fr.xephi.authme.process.unregister.AsynchronousUnregister; import fr.xephi.authme.service.BukkitService; import org.bukkit.command.CommandSender; @@ -60,8 +61,8 @@ public class Management { runTask(() -> asynchronousLogout.logout(player)); } - public void performRegister(Player player, RegistrationExecutor registrationExecutor) { - runTask(() -> asyncRegister.register(player, registrationExecutor)); + public

    void performRegister(RegistrationMethod

    variant, P parameters) { + runTask(() -> asyncRegister.register(variant, parameters)); } public void performUnregister(Player player, String password) { diff --git a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java index 3dcfe8e0..53f4d8fd 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -4,7 +4,7 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.SessionManager; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.ProtectInventoryEvent; import fr.xephi.authme.message.MessageKey; @@ -15,12 +15,12 @@ import fr.xephi.authme.process.login.AsynchronousLogin; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.PluginHookService; +import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.GameMode; import org.bukkit.Server; @@ -51,7 +51,7 @@ public class AsynchronousJoin implements AsynchronousProcess { private PlayerCache playerCache; @Inject - private LimboCache limboCache; + private LimboService limboService; @Inject private SessionManager sessionManager; @@ -62,15 +62,15 @@ public class AsynchronousJoin implements AsynchronousProcess { @Inject private BukkitService bukkitService; - @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; - @Inject private AsynchronousLogin asynchronousLogin; @Inject private CommandManager commandManager; + @Inject + private ValidationService validationService; + AsynchronousJoin() { } @@ -91,7 +91,7 @@ public class AsynchronousJoin implements AsynchronousProcess { pluginHookService.setEssentialsSocialSpyStatus(player, false); } - if (isNameRestricted(name, ip, player.getAddress().getHostName())) { + if (!validationService.fulfillsNameRestrictions(player)) { bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() { @Override public void run() { @@ -111,7 +111,6 @@ public class AsynchronousJoin implements AsynchronousProcess { final boolean isAuthAvailable = database.isAuthAvailable(name); if (isAuthAvailable) { - limboCache.addPlayerData(player); service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED); // Protect inventory @@ -141,10 +140,6 @@ public class AsynchronousJoin implements AsynchronousProcess { } } } else { - // Not Registered. Delete old data, load default one. - limboCache.deletePlayerData(player); - limboCache.addPlayerData(player); - // Groups logic service.setGroup(player, AuthGroupType.UNREGISTERED); @@ -157,12 +152,8 @@ public class AsynchronousJoin implements AsynchronousProcess { final int registrationTimeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> { - player.setOp(false); - if (!service.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT) - && service.getProperty(RestrictionSettings.REMOVE_SPEED)) { - player.setFlySpeed(0.0f); - player.setWalkSpeed(0.0f); - } + limboService.createLimboPlayer(player, isAuthAvailable); + player.setNoDamageTicks(registrationTimeout); if (pluginHookService.isEssentialsAvailable() && service.getProperty(HooksSettings.USE_ESSENTIALS_MOTD)) { player.performCommand("motd"); @@ -174,40 +165,6 @@ public class AsynchronousJoin implements AsynchronousProcess { } commandManager.runCommandsOnJoin(player); }); - - // Timeout and message task - limboPlayerTaskManager.registerTimeoutTask(player); - limboPlayerTaskManager.registerMessageTask(name, isAuthAvailable); - } - - /** - * Returns whether the name is restricted based on the restriction settings. - * - * @param name The name to check - * @param ip The IP address of the player - * @param domain The hostname of the IP address - * - * @return True if the name is restricted (IP/domain is not allowed for the given name), - * false if the restrictions are met or if the name has no restrictions to it - */ - private boolean isNameRestricted(String name, String ip, String domain) { - if (!service.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)) { - return false; - } - - boolean nameFound = false; - for (String entry : service.getProperty(RestrictionSettings.ALLOWED_RESTRICTED_USERS)) { - String[] args = entry.split(";"); - String testName = args[0]; - String testIp = args[1]; - if (testName.equalsIgnoreCase(name)) { - nameFound = true; - if ((ip != null && testIp.equals(ip)) || (domain != null && testIp.equalsIgnoreCase(domain))) { - return false; - } - } - } - return nameFound; } /** diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index 6fb8ff85..b246bf59 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -6,26 +6,23 @@ import fr.xephi.authme.data.CaptchaManager; import fr.xephi.authme.data.TempbanManager; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; -import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AdminPermission; -import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.process.AsynchronousProcess; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.process.SyncProcessManager; import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.properties.DatabaseSettings; import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.task.LimboPlayerTaskManager; -import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.StringUtils; import org.bukkit.ChatColor; @@ -46,15 +43,9 @@ public class AsynchronousLogin implements AsynchronousProcess { @Inject private CommonService service; - @Inject - private PermissionsManager permissionsManager; - @Inject private PlayerCache playerCache; - @Inject - private LimboCache limboCache; - @Inject private SyncProcessManager syncProcessManager; @@ -71,7 +62,7 @@ public class AsynchronousLogin implements AsynchronousProcess { private TempbanManager tempbanManager; @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; AsynchronousLogin() { } @@ -119,8 +110,7 @@ public class AsynchronousLogin implements AsynchronousProcess { if (auth == null) { service.send(player, MessageKey.UNKNOWN_USER); // Recreate the message task to immediately send the message again as response - // and to make sure we send the right register message (password vs. email registration) - limboPlayerTaskManager.registerMessageTask(name, false); + limboService.resetMessageTask(player, false); return null; } @@ -195,7 +185,7 @@ public class AsynchronousLogin implements AsynchronousProcess { // If the authentication fails check if Captcha is required and send a message to the player if (captchaManager.isCaptchaRequired(player.getName())) { - limboCache.getPlayerData(player.getName()).getMessageTask().setMuted(true); + limboService.muteMessageTask(player); service.send(player, MessageKey.USAGE_CAPTCHA, captchaManager.getCaptchaCodeOrGenerateNew(player.getName())); } @@ -246,10 +236,6 @@ public class AsynchronousLogin implements AsynchronousProcess { // task, we schedule it in the end // so that we can be sure, and have not to care if it might be // processed in other order. - LimboPlayer limboPlayer = limboCache.getPlayerData(name); - if (limboPlayer != null) { - limboPlayer.clearTasks(); - } syncProcessManager.processSyncPlayerLogin(player); } else { ConsoleLogger.warning("Player '" + player.getName() + "' wasn't online during login process, aborted..."); @@ -260,7 +246,7 @@ public class AsynchronousLogin implements AsynchronousProcess { int threshold = service.getProperty(RestrictionSettings.OTHER_ACCOUNTS_CMD_THRESHOLD); String command = service.getProperty(RestrictionSettings.OTHER_ACCOUNTS_CMD); - if(threshold < 2 || command.isEmpty()) { + if (threshold < 2 || command.isEmpty()) { return; } @@ -300,10 +286,10 @@ public class AsynchronousLogin implements AsynchronousProcess { for (Player onlinePlayer : bukkitService.getOnlinePlayers()) { if (onlinePlayer.getName().equalsIgnoreCase(player.getName()) - && permissionsManager.hasPermission(onlinePlayer, PlayerPermission.SEE_OWN_ACCOUNTS)) { + && service.hasPermission(onlinePlayer, PlayerPermission.SEE_OWN_ACCOUNTS)) { service.send(onlinePlayer, MessageKey.ACCOUNTS_OWNED_SELF, Integer.toString(auths.size())); onlinePlayer.sendMessage(message); - } else if (permissionsManager.hasPermission(onlinePlayer, AdminPermission.SEE_OTHER_ACCOUNTS)) { + } else if (service.hasPermission(onlinePlayer, AdminPermission.SEE_OTHER_ACCOUNTS)) { service.send(onlinePlayer, MessageKey.ACCOUNTS_OWNED_OTHER, player.getName(), Integer.toString(auths.size())); onlinePlayer.sendMessage(message); @@ -323,7 +309,7 @@ public class AsynchronousLogin implements AsynchronousProcess { boolean hasReachedMaxLoggedInPlayersForIp(Player player, String ip) { // Do not perform the check if player has multiple accounts permission or if IP is localhost if (service.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP) <= 0 - || permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS) + || service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS) || "127.0.0.1".equalsIgnoreCase(ip) || "localhost".equalsIgnoreCase(ip)) { return false; 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 70402e53..206a55fa 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java @@ -1,8 +1,8 @@ package fr.xephi.authme.process.login; import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboPlayer; +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; @@ -31,7 +31,7 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { private BungeeService bungeeService; @Inject - private LimboCache limboCache; + private LimboService limboService; @Inject private BukkitService bukkitService; @@ -65,15 +65,12 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { public void processPlayerLogin(Player player) { final String name = player.getName().toLowerCase(); - final LimboPlayer limbo = limboCache.getPlayerData(name); + commonService.setGroup(player, AuthGroupType.LOGGED_IN); + final LimboPlayer limbo = limboService.getLimboPlayer(name); // Limbo contains the State of the Player before /login if (limbo != null) { - limboCache.restoreData(player); - limboCache.deletePlayerData(player); - // do we really need to use location from database for now? - // because LimboCache#restoreData teleport player to last location. + limboService.restoreData(player); } - commonService.setGroup(player, AuthGroupType.LOGGED_IN); if (commonService.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) { restoreInventory(player); diff --git a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java index efd3066a..1f59d965 100644 --- a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java @@ -2,12 +2,11 @@ package fr.xephi.authme.process.logout; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.AsynchronousProcess; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.process.SyncProcessManager; +import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.entity.Player; @@ -24,9 +23,6 @@ public class AsynchronousLogout implements AsynchronousProcess { @Inject private PlayerCache playerCache; - @Inject - private LimboCache limboCache; - @Inject private SyncProcessManager syncProcessManager; @@ -47,7 +43,6 @@ public class AsynchronousLogout implements AsynchronousProcess { database.updateQuitLoc(auth); } - limboCache.addPlayerData(player); playerCache.removePlayer(name); database.setUnlogged(name); syncProcessManager.processSyncPlayerLogout(player); diff --git a/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java b/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java index 18229146..02878158 100644 --- a/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java @@ -2,17 +2,17 @@ package fr.xephi.authme.process.logout; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.SessionManager; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.events.LogoutEvent; import fr.xephi.authme.listener.protocollib.ProtocolLibService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AuthGroupType; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.process.SynchronousProcess; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.task.LimboPlayerTaskManager; -import fr.xephi.authme.service.BukkitService; -import fr.xephi.authme.service.TeleportationService; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; @@ -34,7 +34,7 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess { private ProtocolLibService protocolLibService; @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; @Inject private SessionManager sessionManager; @@ -53,9 +53,6 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess { protocolLibService.sendBlankInventoryPacket(player); } - limboPlayerTaskManager.registerTimeoutTask(player); - limboPlayerTaskManager.registerMessageTask(name, true); - applyLogoutEffect(player); // Player is now logout... Time to fire event ! @@ -71,21 +68,14 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess { teleportationService.teleportOnJoin(player); // Apply Blindness effect - final int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; if (service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { + int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeout, 2)); } // Set player's data to unauthenticated + limboService.createLimboPlayer(player, true); service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED); - player.setOp(false); - player.setAllowFlight(false); - // Remove speed - if (!service.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT) - && service.getProperty(RestrictionSettings.REMOVE_SPEED)) { - player.setFlySpeed(0.0f); - player.setWalkSpeed(0.0f); - } } } diff --git a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java index 47bfb912..c5f633e9 100644 --- a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java @@ -1,7 +1,6 @@ package fr.xephi.authme.process.quit; -import fr.xephi.authme.data.limbo.LimboPlayerStorage; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.process.SynchronousProcess; import org.bukkit.entity.Player; @@ -11,22 +10,10 @@ import javax.inject.Inject; public class ProcessSyncronousPlayerQuit implements SynchronousProcess { @Inject - private LimboPlayerStorage limboPlayerStorage; - - @Inject - private LimboCache limboCache; + private LimboService limboService; public void processSyncQuit(Player player) { - if (limboCache.hasPlayerData(player.getName())) { // it mean player is not authenticated - limboCache.restoreData(player); - limboCache.removeFromCache(player); - } else { - // Save player's data, so we could retrieve it later on player next join - if (!limboPlayerStorage.hasData(player)) { - limboPlayerStorage.saveData(player); - } - } - + limboService.restoreData(player); player.leaveVehicle(); } } 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 d32f6ead..83e5a82c 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -3,10 +3,13 @@ package fr.xephi.authme.process.register; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.initialization.factory.SingletonStore; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.register.executors.RegistrationExecutor; +import fr.xephi.authme.process.register.executors.RegistrationParameters; +import fr.xephi.authme.process.register.executors.RegistrationMethod; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; @@ -31,6 +34,8 @@ public class AsyncRegister implements AsynchronousProcess { private CommonService service; @Inject private PermissionsManager permissionsManager; + @Inject + private SingletonStore registrationExecutorFactory; AsyncRegister() { } @@ -38,12 +43,16 @@ public class AsyncRegister implements AsynchronousProcess { /** * Performs the registration process for the given player. * - * @param player the player to register - * @param executor the registration executor to perform the registration with + * @param variant the registration method + * @param parameters the parameters + * @param

    parameters type */ - public void register(Player player, RegistrationExecutor executor) { - if (preRegisterCheck(player) && executor.isRegistrationAdmitted()) { - executeRegistration(player, executor); + public

    void register(RegistrationMethod

    variant, P parameters) { + if (preRegisterCheck(parameters.getPlayer())) { + RegistrationExecutor

    executor = registrationExecutorFactory.getSingleton(variant.getExecutorClass()); + if (executor.isRegistrationAdmitted(parameters)) { + executeRegistration(parameters, executor); + } } } @@ -66,15 +75,17 @@ public class AsyncRegister implements AsynchronousProcess { /** * Executes the registration. * - * @param player the player to register + * @param parameters the registration parameters * @param executor the executor to perform the registration process with + * @param

    registration params type */ - private void executeRegistration(Player player, RegistrationExecutor executor) { - PlayerAuth auth = executor.buildPlayerAuth(); + private

    + void executeRegistration(P parameters, RegistrationExecutor

    executor) { + PlayerAuth auth = executor.buildPlayerAuth(parameters); if (database.saveAuth(auth)) { - executor.executePostPersistAction(); + executor.executePostPersistAction(parameters); } else { - service.send(player, MessageKey.ERROR); + service.send(parameters.getPlayer(), MessageKey.ERROR); } } diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java index cebbcdcb..4e57b973 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java @@ -1,11 +1,11 @@ package fr.xephi.authme.process.register; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.service.CommonService; -import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.entity.Player; @@ -18,7 +18,7 @@ public class ProcessSyncEmailRegister implements SynchronousProcess { private CommonService service; @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; ProcessSyncEmailRegister() { } @@ -27,9 +27,7 @@ public class ProcessSyncEmailRegister implements SynchronousProcess { service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED); service.send(player, MessageKey.ACCOUNT_NOT_ACTIVATED); - final String name = player.getName().toLowerCase(); - limboPlayerTaskManager.registerTimeoutTask(player); - limboPlayerTaskManager.registerMessageTask(name, true); + limboService.replaceTasksAfterRegistration(player); player.saveData(); ConsoleLogger.fine(player.getName() + " registered " + PlayerUtils.getPlayerIp(player)); diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java index 0b06df61..173cc0d8 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java @@ -1,7 +1,7 @@ package fr.xephi.authme.process.register; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.process.SynchronousProcess; @@ -10,7 +10,6 @@ import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; -import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.entity.Player; @@ -27,10 +26,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess { private CommonService service; @Inject - private LimboCache limboCache; - - @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; @Inject private CommandManager commandManager; @@ -44,10 +40,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess { * @param player the player */ private void requestLogin(Player player) { - final String name = player.getName().toLowerCase(); - limboCache.updatePlayerData(player); - limboPlayerTaskManager.registerTimeoutTask(player); - limboPlayerTaskManager.registerMessageTask(name, true); + limboService.replaceTasksAfterRegistration(player); if (player.isInsideVehicle() && player.getVehicle() != null) { player.getVehicle().eject(); diff --git a/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java new file mode 100644 index 00000000..9f0ee724 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java @@ -0,0 +1,97 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.process.SyncProcessManager; +import fr.xephi.authme.process.login.AsynchronousLogin; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.ValidationService; +import fr.xephi.authme.settings.properties.PluginSettings; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +/** + * Registration executor for registration methods where the password + * is supplied by the user. + */ +abstract class AbstractPasswordRegisterExecutor

    + implements RegistrationExecutor

    { + + /** + * Number of ticks to wait before running the login action when it is run synchronously. + * A small delay is necessary or the database won't return the newly saved PlayerAuth object + * and the login process thinks the user is not registered. + */ + private static final int SYNC_LOGIN_DELAY = 5; + + @Inject + private ValidationService validationService; + + @Inject + private CommonService commonService; + + @Inject + private PasswordSecurity passwordSecurity; + + @Inject + private BukkitService bukkitService; + + @Inject + private SyncProcessManager syncProcessManager; + + @Inject + private AsynchronousLogin asynchronousLogin; + + @Override + public boolean isRegistrationAdmitted(P params) { + ValidationService.ValidationResult passwordValidation = validationService.validatePassword( + params.getPassword(), params.getPlayer().getName()); + if (passwordValidation.hasError()) { + commonService.send(params.getPlayer(), passwordValidation.getMessageKey(), passwordValidation.getArgs()); + return false; + } + return true; + } + + @Override + public PlayerAuth buildPlayerAuth(P params) { + HashedPassword hashedPassword = passwordSecurity.computeHash(params.getPassword(), params.getPlayerName()); + params.setHashedPassword(hashedPassword); + return createPlayerAuthObject(params); + } + + /** + * Creates the PlayerAuth object to store into the database, based on the registration parameters. + * + * @param params the parameters + * @return the PlayerAuth representing the new account to register + */ + protected abstract PlayerAuth createPlayerAuthObject(P params); + + /** + * Returns whether the player should be automatically logged in after registration. + * + * @param params the registration parameters + * @return true if the player should be logged in, false otherwise + */ + protected boolean performLoginAfterRegister(P params) { + return !commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER); + } + + @Override + public void executePostPersistAction(P params) { + final Player player = params.getPlayer(); + if (performLoginAfterRegister(params)) { + if (commonService.getProperty(PluginSettings.USE_ASYNC_TASKS)) { + bukkitService.runTaskAsynchronously(() -> asynchronousLogin.forceLogin(player)); + } else { + bukkitService.scheduleSyncDelayedTask(() -> asynchronousLogin.forceLogin(player), SYNC_LOGIN_DELAY); + } + } + syncProcessManager.processSyncPasswordRegister(player); + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterParams.java new file mode 100644 index 00000000..0c7a1d51 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterParams.java @@ -0,0 +1,48 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.security.crypts.HashedPassword; +import org.bukkit.entity.Player; + +/** + * Common params type for implementors of {@link AbstractPasswordRegisterExecutor}. + * Password must be supplied on creation and cannot be changed later on. The {@link HashedPassword} + * is stored on the params object for later use. + */ +public abstract class AbstractPasswordRegisterParams extends RegistrationParameters { + + private final String password; + private HashedPassword hashedPassword; + + /** + * Constructor. + * + * @param player the player to register + * @param password the password to use + */ + public AbstractPasswordRegisterParams(Player player, String password) { + super(player); + this.password = password; + } + + /** + * Constructor with no defined password. Use for registration methods which + * have no implicit password (like two factor authentication). + * + * @param player the player to register + */ + public AbstractPasswordRegisterParams(Player player) { + this(player, null); + } + + public String getPassword() { + return password; + } + + void setHashedPassword(HashedPassword hashedPassword) { + this.hashedPassword = hashedPassword; + } + + HashedPassword getHashedPassword() { + return hashedPassword; + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterExecutor.java new file mode 100644 index 00000000..6005c8e4 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterExecutor.java @@ -0,0 +1,20 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; + +/** + * Executor for password registration via API call. + */ +class ApiPasswordRegisterExecutor extends AbstractPasswordRegisterExecutor { + + @Override + protected PlayerAuth createPlayerAuthObject(ApiPasswordRegisterParams params) { + return PlayerAuthBuilderHelper + .createPlayerAuth(params.getPlayer(), params.getHashedPassword(), null); + } + + @Override + protected boolean performLoginAfterRegister(ApiPasswordRegisterParams params) { + return params.getLoginAfterRegister(); + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterParams.java new file mode 100644 index 00000000..357d32c2 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterParams.java @@ -0,0 +1,35 @@ +package fr.xephi.authme.process.register.executors; + +import org.bukkit.entity.Player; + +/** + * Parameters for {@link ApiPasswordRegisterExecutor}. + */ +public class ApiPasswordRegisterParams extends PasswordRegisterParams { + + private final boolean loginAfterRegister; + + protected ApiPasswordRegisterParams(Player player, String password, boolean loginAfterRegister) { + super(player, password, null); + this.loginAfterRegister = loginAfterRegister; + } + + /** + * Creates a parameters object. + * + * @param player the player to register + * @param password the password to register with + * @param loginAfterRegister whether the player should be logged in after registration + * @return params object with the given data + */ + public static ApiPasswordRegisterParams of(Player player, String password, boolean loginAfterRegister) { + return new ApiPasswordRegisterParams(player, password, loginAfterRegister); + } + + /** + * @return true if the player should be logged in after being registered, false otherwise + */ + public boolean getLoginAfterRegister() { + return loginAfterRegister; + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutor.java new file mode 100644 index 00000000..0f4153b1 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutor.java @@ -0,0 +1,80 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.mail.EmailService; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.process.SyncProcessManager; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.util.RandomStringUtils; +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +import static fr.xephi.authme.permission.PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS; +import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth; +import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH; + +/** + * Executor for email registration: the player only provides his email address, + * to which a generated password is sent. + */ +class EmailRegisterExecutor implements RegistrationExecutor { + + @Inject + private PermissionsManager permissionsManager; + + @Inject + private DataSource dataSource; + + @Inject + private CommonService commonService; + + @Inject + private EmailService emailService; + + @Inject + private SyncProcessManager syncProcessManager; + + @Inject + private PasswordSecurity passwordSecurity; + + @Override + public boolean isRegistrationAdmitted(EmailRegisterParams params) { + final int maxRegPerEmail = commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL); + if (maxRegPerEmail > 0 && !permissionsManager.hasPermission(params.getPlayer(), ALLOW_MULTIPLE_ACCOUNTS)) { + int otherAccounts = dataSource.countAuthsByEmail(params.getEmail()); + if (otherAccounts >= maxRegPerEmail) { + commonService.send(params.getPlayer(), MessageKey.MAX_REGISTER_EXCEEDED, + Integer.toString(maxRegPerEmail), Integer.toString(otherAccounts), "@"); + return false; + } + } + return true; + } + + @Override + public PlayerAuth buildPlayerAuth(EmailRegisterParams params) { + String password = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH)); + HashedPassword hashedPassword = passwordSecurity.computeHash(password, params.getPlayer().getName()); + params.setPassword(password); + return createPlayerAuth(params.getPlayer(), hashedPassword, params.getEmail()); + } + + @Override + public void executePostPersistAction(EmailRegisterParams params) { + Player player = params.getPlayer(); + boolean couldSendMail = emailService.sendPasswordMail( + player.getName(), params.getEmail(), params.getPassword()); + if (couldSendMail) { + syncProcessManager.processSyncEmailRegister(player); + } else { + commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); + } + } + +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java deleted file mode 100644 index 7a6e702d..00000000 --- a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java +++ /dev/null @@ -1,91 +0,0 @@ -package fr.xephi.authme.process.register.executors; - -import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.mail.EmailService; -import fr.xephi.authme.message.MessageKey; -import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.process.SyncProcessManager; -import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.service.CommonService; -import fr.xephi.authme.settings.properties.EmailSettings; -import fr.xephi.authme.util.RandomStringUtils; -import org.bukkit.entity.Player; - -import javax.inject.Inject; - -import static fr.xephi.authme.permission.PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS; -import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth; -import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH; - -/** - * Provides a registration executor for email registration. - */ -class EmailRegisterExecutorProvider { - - @Inject - private PermissionsManager permissionsManager; - - @Inject - private DataSource dataSource; - - @Inject - private CommonService commonService; - - @Inject - private EmailService emailService; - - @Inject - private SyncProcessManager syncProcessManager; - - @Inject - private PasswordSecurity passwordSecurity; - - EmailRegisterExecutorProvider() { - } - - /** Registration executor implementation for email registration. */ - class EmailRegisterExecutor implements RegistrationExecutor { - - private final Player player; - private final String email; - private String password; - - EmailRegisterExecutor(Player player, String email) { - this.player = player; - this.email = email; - } - - @Override - public boolean isRegistrationAdmitted() { - final int maxRegPerEmail = commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL); - if (maxRegPerEmail > 0 && !permissionsManager.hasPermission(player, ALLOW_MULTIPLE_ACCOUNTS)) { - int otherAccounts = dataSource.countAuthsByEmail(email); - if (otherAccounts >= maxRegPerEmail) { - commonService.send(player, MessageKey.MAX_REGISTER_EXCEEDED, Integer.toString(maxRegPerEmail), - Integer.toString(otherAccounts), "@"); - return false; - } - } - return true; - } - - @Override - public PlayerAuth buildPlayerAuth() { - password = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH)); - HashedPassword hashedPassword = passwordSecurity.computeHash(password, player.getName()); - return createPlayerAuth(player, hashedPassword, email); - } - - @Override - public void executePostPersistAction() { - boolean couldSendMail = emailService.sendPasswordMail(player.getName(), email, password); - if (couldSendMail) { - syncProcessManager.processSyncEmailRegister(player); - } else { - commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); - } - } - } -} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterParams.java new file mode 100644 index 00000000..94d03acc --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterParams.java @@ -0,0 +1,43 @@ +package fr.xephi.authme.process.register.executors; + +import org.bukkit.entity.Player; + +/** + * Parameters for email registration. + */ +public class EmailRegisterParams extends RegistrationParameters { + + private final String email; + private String password; + + protected EmailRegisterParams(Player player, String email) { + super(player); + this.email = email; + } + + /** + * Creates a params object for email registration. + * + * @param player the player to register + * @param email the player's email + * @return params object with the given data + */ + public static EmailRegisterParams of(Player player, String email) { + return new EmailRegisterParams(player, email); + } + + public String getEmail() { + return email; + } + + void setPassword(String password) { + this.password = password; + } + + /** + * @return the password generated for the player + */ + String getPassword() { + return password; + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutor.java new file mode 100644 index 00000000..167bee29 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutor.java @@ -0,0 +1,17 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; + +import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth; + +/** + * Registration executor for password registration. + */ +class PasswordRegisterExecutor extends AbstractPasswordRegisterExecutor { + + @Override + public PlayerAuth createPlayerAuthObject(PasswordRegisterParams params) { + return createPlayerAuth(params.getPlayer(), params.getHashedPassword(), params.getEmail()); + } + +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java deleted file mode 100644 index a79c63c8..00000000 --- a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java +++ /dev/null @@ -1,155 +0,0 @@ -package fr.xephi.authme.process.register.executors; - -import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.message.MessageKey; -import fr.xephi.authme.process.SyncProcessManager; -import fr.xephi.authme.process.login.AsynchronousLogin; -import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.security.crypts.TwoFactor; -import fr.xephi.authme.service.BukkitService; -import fr.xephi.authme.service.CommonService; -import fr.xephi.authme.service.ValidationService; -import fr.xephi.authme.service.ValidationService.ValidationResult; -import fr.xephi.authme.settings.properties.PluginSettings; -import fr.xephi.authme.settings.properties.RegistrationSettings; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import javax.inject.Inject; - -import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth; - -/** - * Provides registration executors for password-based registration variants. - */ -class PasswordRegisterExecutorProvider { - - /** - * Number of ticks to wait before running the login action when it is run synchronously. - * A small delay is necessary or the database won't return the newly saved PlayerAuth object - * and the login process thinks the user is not registered. - */ - private static final int SYNC_LOGIN_DELAY = 5; - - @Inject - private ValidationService validationService; - - @Inject - private CommonService commonService; - - @Inject - private PasswordSecurity passwordSecurity; - - @Inject - private BukkitService bukkitService; - - @Inject - private SyncProcessManager syncProcessManager; - - @Inject - private AsynchronousLogin asynchronousLogin; - - PasswordRegisterExecutorProvider() { - } - - /** Registration executor for password registration. */ - class PasswordRegisterExecutor implements RegistrationExecutor { - - protected final Player player; - private final String password; - private final String email; - protected HashedPassword hashedPassword; - - /** - * Constructor. - * - * @param player the player to register - * @param password the password to register with - * @param email the email of the player (may be null) - */ - PasswordRegisterExecutor(Player player, String password, String email) { - this.player = player; - this.password = password; - this.email = email; - } - - @Override - public boolean isRegistrationAdmitted() { - ValidationResult passwordValidation = validationService.validatePassword(password, player.getName()); - if (passwordValidation.hasError()) { - commonService.send(player, passwordValidation.getMessageKey(), passwordValidation.getArgs()); - return false; - } - return true; - } - - @Override - public PlayerAuth buildPlayerAuth() { - hashedPassword = passwordSecurity.computeHash(password, player.getName().toLowerCase()); - return createPlayerAuth(player, hashedPassword, email); - } - - protected boolean performLoginAfterRegister() { - return !commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER); - } - - @Override - public void executePostPersistAction() { - if (performLoginAfterRegister()) { - if (commonService.getProperty(PluginSettings.USE_ASYNC_TASKS)) { - bukkitService.runTaskAsynchronously(() -> asynchronousLogin.forceLogin(player)); - } else { - bukkitService.scheduleSyncDelayedTask(() -> asynchronousLogin.forceLogin(player), SYNC_LOGIN_DELAY); - } - } - syncProcessManager.processSyncPasswordRegister(player); - } - } - - /** Executor for password registration via API call. */ - class ApiPasswordRegisterExecutor extends PasswordRegisterExecutor { - - private final boolean loginAfterRegister; - - /** - * Constructor. - * - * @param player the player to register - * @param password the password to register with - * @param loginAfterRegister whether the user should be automatically logged in after registration - */ - ApiPasswordRegisterExecutor(Player player, String password, boolean loginAfterRegister) { - super(player, password, null); - this.loginAfterRegister = loginAfterRegister; - } - - @Override - protected boolean performLoginAfterRegister() { - return loginAfterRegister; - } - } - - /** Executor for two factor registration. */ - class TwoFactorRegisterExecutor extends PasswordRegisterExecutor { - - TwoFactorRegisterExecutor(Player player) { - super(player, "", null); - } - - @Override - public boolean isRegistrationAdmitted() { - // nothing to check - return true; - } - - @Override - public void executePostPersistAction() { - super.executePostPersistAction(); - - String qrCodeUrl = TwoFactor.getQRBarcodeURL(player.getName(), Bukkit.getIp(), hashedPassword.getHash()); - commonService.send(player, MessageKey.TWO_FACTOR_CREATE, hashedPassword.getHash(), qrCodeUrl); - } - - } -} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterParams.java new file mode 100644 index 00000000..f21861bf --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterParams.java @@ -0,0 +1,32 @@ +package fr.xephi.authme.process.register.executors; + +import org.bukkit.entity.Player; + +/** + * Parameters for registration with a given password, and optionally an email address. + */ +public class PasswordRegisterParams extends AbstractPasswordRegisterParams { + + private final String email; + + protected PasswordRegisterParams(Player player, String password, String email) { + super(player, password); + this.email = email; + } + + /** + * Creates a params object. + * + * @param player the player to register + * @param password the password to register with + * @param email the email of the player (may be null) + * @return params object with the given data + */ + public static PasswordRegisterParams of(Player player, String password, String email) { + return new PasswordRegisterParams(player, password, email); + } + + public String getEmail() { + return email; + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java b/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java index 1943b5d5..5d9ab865 100644 --- a/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java +++ b/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java @@ -13,6 +13,14 @@ final class PlayerAuthBuilderHelper { private PlayerAuthBuilderHelper() { } + /** + * Creates a {@link PlayerAuth} object with the given data. + * + * @param player the player to create a PlayerAuth for + * @param hashedPassword the hashed password + * @param email the email address (nullable) + * @return the generated PlayerAuth object + */ static PlayerAuth createPlayerAuth(Player player, HashedPassword hashedPassword, String email) { return PlayerAuth.builder() .name(player.getName().toLowerCase()) diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java index cceb5c18..ad18bf92 100644 --- a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java +++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java @@ -5,7 +5,7 @@ import fr.xephi.authme.data.auth.PlayerAuth; /** * Performs the registration action. */ -public interface RegistrationExecutor { +public interface RegistrationExecutor

    { /** * Returns whether the registration may take place. Use this method to execute @@ -14,20 +14,24 @@ public interface RegistrationExecutor { * If this method returns {@code false}, it is expected that the executor inform * the player about the error within this method call. * + * @param params the parameters for the registration * @return true if registration may be performed, false otherwise */ - boolean isRegistrationAdmitted(); + boolean isRegistrationAdmitted(P params); /** * Constructs the PlayerAuth object to persist into the database. * + * @param params the parameters for the registration * @return the player auth to register in the data source */ - PlayerAuth buildPlayerAuth(); + PlayerAuth buildPlayerAuth(P params); /** * Follow-up method called after the player auth could be added into the database. + * + * @param params the parameters for the registration */ - void executePostPersistAction(); + void executePostPersistAction(P params); } diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java deleted file mode 100644 index 286a5163..00000000 --- a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java +++ /dev/null @@ -1,36 +0,0 @@ -package fr.xephi.authme.process.register.executors; - -import org.bukkit.entity.Player; - -import javax.inject.Inject; - -/** - * Provides a {@link RegistrationExecutor} for various registration methods. - */ -public class RegistrationExecutorProvider { - - @Inject - private PasswordRegisterExecutorProvider passwordRegisterExecutorProvider; - - @Inject - private EmailRegisterExecutorProvider emailRegisterExecutorProvider; - - RegistrationExecutorProvider() { - } - - public RegistrationExecutor getPasswordRegisterExecutor(Player player, String password, String email) { - return passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, password, email); - } - - public RegistrationExecutor getPasswordRegisterExecutor(Player player, String password, boolean loginAfterRegister) { - return passwordRegisterExecutorProvider.new ApiPasswordRegisterExecutor(player, password, loginAfterRegister); - } - - public RegistrationExecutor getTwoFactorRegisterExecutor(Player player) { - return passwordRegisterExecutorProvider.new TwoFactorRegisterExecutor(player); - } - - public RegistrationExecutor getEmailRegisterExecutor(Player player, String email) { - return emailRegisterExecutorProvider.new EmailRegisterExecutor(player, email); - } -} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java new file mode 100644 index 00000000..9232332e --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java @@ -0,0 +1,51 @@ +package fr.xephi.authme.process.register.executors; + +/** + * Methods with which a player can be registered. + *

    + * These constants each define a different way of registering a player and define the + * {@link RegistrationParameters parameters} and {@link RegistrationExecutor executor} + * classes which perform this registration method. This is essentially a typed enum + * as passing a constant of this class along with a parameters object to a method can + * be restricted to the correct parameters type. + */ +public final class RegistrationMethod

    { + + /** + * Password registration. + */ + public static final RegistrationMethod PASSWORD_REGISTRATION = + new RegistrationMethod<>(PasswordRegisterExecutor.class); + + /** + * Registration with two-factor authentication as login means. + */ + public static final RegistrationMethod TWO_FACTOR_REGISTRATION = + new RegistrationMethod<>(TwoFactorRegisterExecutor.class); + + /** + * Email registration: an email address is provided, to which a generated password is sent. + */ + public static final RegistrationMethod EMAIL_REGISTRATION = + new RegistrationMethod<>(EmailRegisterExecutor.class); + + /** + * API registration: player and password are provided via an API method. + */ + public static final RegistrationMethod API_REGISTRATION = + new RegistrationMethod<>(ApiPasswordRegisterExecutor.class); + + + private final Class> executorClass; + + private RegistrationMethod(Class> executorClass) { + this.executorClass = executorClass; + } + + /** + * @return the executor class to perform the registration method + */ + public Class> getExecutorClass() { + return executorClass; + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationParameters.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationParameters.java new file mode 100644 index 00000000..c92d57ff --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationParameters.java @@ -0,0 +1,28 @@ +package fr.xephi.authme.process.register.executors; + +import org.bukkit.entity.Player; + +/** + * Parent of all registration parameters. + */ +public abstract class RegistrationParameters { + + private final Player player; + + /** + * Constructor. + * + * @param player the player to perform the registration for + */ + public RegistrationParameters(Player player) { + this.player = player; + } + + public Player getPlayer() { + return player; + } + + public String getPlayerName() { + return player.getName(); + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterExecutor.java new file mode 100644 index 00000000..027a5fa6 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterExecutor.java @@ -0,0 +1,43 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.security.crypts.TwoFactor; +import fr.xephi.authme.service.CommonService; +import org.bukkit.Bukkit; + +import javax.inject.Inject; + +import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth; + +/** + * Executor for two-factor registration. + */ +class TwoFactorRegisterExecutor extends AbstractPasswordRegisterExecutor { + + @Inject + private CommonService commonService; + + @Override + public boolean isRegistrationAdmitted(TwoFactorRegisterParams params) { + // nothing to check + return true; + } + + @Override + protected PlayerAuth createPlayerAuthObject(TwoFactorRegisterParams params) { + return createPlayerAuth(params.getPlayer(), params.getHashedPassword(), null); + } + + @Override + public void executePostPersistAction(TwoFactorRegisterParams params) { + super.executePostPersistAction(params); + + // Note ljacqu 20170317: This two-factor registration type is only invoked when the password hash is configured + // to two-factor authentication. Therefore, the hashed password is the result of the TwoFactor EncryptionMethod + // implementation (contains the TOTP secret). + String hash = params.getHashedPassword().getHash(); + String qrCodeUrl = TwoFactor.getQRBarcodeURL(params.getPlayerName(), Bukkit.getIp(), hash); + commonService.send(params.getPlayer(), MessageKey.TWO_FACTOR_CREATE, hash, qrCodeUrl); + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterParams.java new file mode 100644 index 00000000..a7a75875 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterParams.java @@ -0,0 +1,23 @@ +package fr.xephi.authme.process.register.executors; + +import org.bukkit.entity.Player; + +/** + * Parameters for registration with two-factor authentication. + */ +public class TwoFactorRegisterParams extends AbstractPasswordRegisterParams { + + protected TwoFactorRegisterParams(Player player) { + super(player); + } + + /** + * Creates a parameters object. + * + * @param player the player to register + * @return params object with the given player + */ + public static TwoFactorRegisterParams of(Player player) { + return new TwoFactorRegisterParams(player); + } +} diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java index 764e5676..eabd902e 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -3,19 +3,18 @@ package fr.xephi.authme.process.unregister; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AuthGroupHandler; import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.process.AsynchronousProcess; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.task.LimboPlayerTaskManager; -import fr.xephi.authme.service.BukkitService; -import fr.xephi.authme.service.TeleportationService; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; @@ -43,10 +42,7 @@ public class AsynchronousUnregister implements AsynchronousProcess { private BukkitService bukkitService; @Inject - private LimboCache limboCache; - - @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; @Inject private TeleportationService teleportationService; @@ -111,12 +107,10 @@ public class AsynchronousUnregister implements AsynchronousProcess { teleportationService.teleportOnJoin(player); player.saveData(); - limboCache.deletePlayerData(player); - limboCache.addPlayerData(player); - - limboPlayerTaskManager.registerTimeoutTask(player); - limboPlayerTaskManager.registerMessageTask(name, false); - applyBlindEffect(player); + bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> { + limboService.createLimboPlayer(player, false); + applyBlindEffect(player); + }); } authGroupHandler.setGroup(player, AuthGroupType.UNREGISTERED); service.send(player, MessageKey.UNREGISTERED_SUCCESS); @@ -124,13 +118,8 @@ public class AsynchronousUnregister implements AsynchronousProcess { private void applyBlindEffect(final Player player) { if (service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { - final int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; - bukkitService.runTask(new Runnable() { - @Override - public void run() { - player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeout, 2)); - } - }); + int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; + player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeout, 2)); } } } diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index 732582e0..f12da678 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -7,36 +7,36 @@ import fr.xephi.authme.security.crypts.EncryptionMethod; */ public enum HashAlgorithm { - BCRYPT(fr.xephi.authme.security.crypts.BCRYPT.class), - BCRYPT2Y(fr.xephi.authme.security.crypts.BCRYPT2Y.class), - CRAZYCRYPT1(fr.xephi.authme.security.crypts.CRAZYCRYPT1.class), - DOUBLEMD5(fr.xephi.authme.security.crypts.DOUBLEMD5.class), - IPB3(fr.xephi.authme.security.crypts.IPB3.class), - IPB4(fr.xephi.authme.security.crypts.IPB4.class), - JOOMLA(fr.xephi.authme.security.crypts.JOOMLA.class), - MD5(fr.xephi.authme.security.crypts.MD5.class), - MD5VB(fr.xephi.authme.security.crypts.MD5VB.class), - MYBB(fr.xephi.authme.security.crypts.MYBB.class), + BCRYPT(fr.xephi.authme.security.crypts.BCrypt.class), + BCRYPT2Y(fr.xephi.authme.security.crypts.BCrypt2y.class), + CRAZYCRYPT1(fr.xephi.authme.security.crypts.CrazyCrypt1.class), + DOUBLEMD5(fr.xephi.authme.security.crypts.DoubleMd5.class), + IPB3(fr.xephi.authme.security.crypts.Ipb3.class), + IPB4(fr.xephi.authme.security.crypts.Ipb4.class), + JOOMLA(fr.xephi.authme.security.crypts.Joomla.class), + MD5(fr.xephi.authme.security.crypts.Md5.class), + MD5VB(fr.xephi.authme.security.crypts.Md5vB.class), + MYBB(fr.xephi.authme.security.crypts.MyBB.class), PBKDF2(fr.xephi.authme.security.crypts.Pbkdf2.class), PBKDF2DJANGO(fr.xephi.authme.security.crypts.Pbkdf2Django.class), - PHPBB(fr.xephi.authme.security.crypts.PHPBB.class), - PHPFUSION(fr.xephi.authme.security.crypts.PHPFUSION.class), + PHPBB(fr.xephi.authme.security.crypts.PhpBB.class), + PHPFUSION(fr.xephi.authme.security.crypts.PhpFusion.class), @Deprecated - PLAINTEXT(fr.xephi.authme.security.crypts.PLAINTEXT.class), - ROYALAUTH(fr.xephi.authme.security.crypts.ROYALAUTH.class), - SALTED2MD5(fr.xephi.authme.security.crypts.SALTED2MD5.class), - SALTEDSHA512(fr.xephi.authme.security.crypts.SALTEDSHA512.class), - SHA1(fr.xephi.authme.security.crypts.SHA1.class), - SHA256(fr.xephi.authme.security.crypts.SHA256.class), - SHA512(fr.xephi.authme.security.crypts.SHA512.class), - SMF(fr.xephi.authme.security.crypts.SMF.class), + PLAINTEXT(fr.xephi.authme.security.crypts.PlainText.class), + ROYALAUTH(fr.xephi.authme.security.crypts.RoyalAuth.class), + SALTED2MD5(fr.xephi.authme.security.crypts.Salted2Md5.class), + SALTEDSHA512(fr.xephi.authme.security.crypts.SaltedSha512.class), + SHA1(fr.xephi.authme.security.crypts.Sha1.class), + SHA256(fr.xephi.authme.security.crypts.Sha256.class), + SHA512(fr.xephi.authme.security.crypts.Sha512.class), + SMF(fr.xephi.authme.security.crypts.Smf.class), TWO_FACTOR(fr.xephi.authme.security.crypts.TwoFactor.class), - WBB3(fr.xephi.authme.security.crypts.WBB3.class), - WBB4(fr.xephi.authme.security.crypts.WBB4.class), - WHIRLPOOL(fr.xephi.authme.security.crypts.WHIRLPOOL.class), - WORDPRESS(fr.xephi.authme.security.crypts.WORDPRESS.class), - XAUTH(fr.xephi.authme.security.crypts.XAUTH.class), - XFBCRYPT(fr.xephi.authme.security.crypts.XFBCRYPT.class), + WBB3(fr.xephi.authme.security.crypts.Wbb3.class), + WBB4(fr.xephi.authme.security.crypts.Wbb4.class), + WHIRLPOOL(fr.xephi.authme.security.crypts.Whirlpool.class), + WORDPRESS(fr.xephi.authme.security.crypts.Wordpress.class), + XAUTH(fr.xephi.authme.security.crypts.XAuth.class), + XFBCRYPT(fr.xephi.authme.security.crypts.XfBCrypt.class), CUSTOM(null); private final Class clazz; diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java similarity index 95% rename from src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java rename to src/main/java/fr/xephi/authme/security/crypts/BCrypt.java index aae6b910..02e12d45 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java @@ -14,12 +14,12 @@ 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 { +public class BCrypt implements EncryptionMethod { private final int bCryptLog2Rounds; @Inject - public BCRYPT(Settings settings) { + public BCrypt(Settings settings) { bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java similarity index 95% rename from src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java rename to src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java index 49bd45f8..cf4807ab 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java @@ -4,7 +4,7 @@ import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; @Recommendation(Usage.RECOMMENDED) -public class BCRYPT2Y extends HexSaltedMethod { +public class BCrypt2y extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java b/src/main/java/fr/xephi/authme/security/crypts/CrazyCrypt1.java similarity index 95% rename from src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java rename to src/main/java/fr/xephi/authme/security/crypts/CrazyCrypt1.java index 59584231..6130c6a1 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CrazyCrypt1.java @@ -6,7 +6,7 @@ import fr.xephi.authme.security.MessageDigestAlgorithm; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; -public class CRAZYCRYPT1 extends UsernameSaltMethod { +public class CrazyCrypt1 extends UsernameSaltMethod { private static final char[] CRYPTCHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; diff --git a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java b/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java similarity index 81% rename from src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java rename to src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java index b7823a2b..c28e0440 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import static fr.xephi.authme.security.HashUtils.md5; -public class DOUBLEMD5 extends UnsaltedMethod { +public class DoubleMd5 extends UnsaltedMethod { @Override public String computeHash(String password) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/Ipb3.java similarity index 93% rename from src/main/java/fr/xephi/authme/security/crypts/IPB3.java rename to src/main/java/fr/xephi/authme/security/crypts/Ipb3.java index a4e62461..15dcd189 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Ipb3.java @@ -10,7 +10,7 @@ import static fr.xephi.authme.security.HashUtils.md5; @Recommendation(Usage.ACCEPTABLE) @HasSalt(value = SaltType.TEXT, length = 5) -public class IPB3 extends SeparateSaltMethod { +public class Ipb3 extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB4.java b/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java similarity index 91% rename from src/main/java/fr/xephi/authme/security/crypts/IPB4.java rename to src/main/java/fr/xephi/authme/security/crypts/Ipb4.java index 40ea7516..76289795 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/IPB4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java @@ -11,15 +11,15 @@ import fr.xephi.authme.util.StringUtils; /** - * Implementation for IPB4 (Invision Power Board 4). + * 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" + * 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). */ @Recommendation(Usage.DOES_NOT_WORK) @HasSalt(value = SaltType.TEXT, length = 22) -public class IPB4 implements EncryptionMethod { +public class Ipb4 implements EncryptionMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/Joomla.java similarity index 94% rename from src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java rename to src/main/java/fr/xephi/authme/security/crypts/Joomla.java index ca72674b..462f5cb2 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Joomla.java @@ -5,7 +5,7 @@ import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; @Recommendation(Usage.ACCEPTABLE) -public class JOOMLA extends HexSaltedMethod { +public class Joomla extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5.java b/src/main/java/fr/xephi/authme/security/crypts/Md5.java similarity index 82% rename from src/main/java/fr/xephi/authme/security/crypts/MD5.java rename to src/main/java/fr/xephi/authme/security/crypts/Md5.java index 50bb7d97..c2a2ba04 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Md5.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -public class MD5 extends UnsaltedMethod { +public class Md5 extends UnsaltedMethod { @Override public String computeHash(String password) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/Md5vB.java similarity index 93% rename from src/main/java/fr/xephi/authme/security/crypts/MD5VB.java rename to src/main/java/fr/xephi/authme/security/crypts/Md5vB.java index f9c21ae7..c244ec49 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Md5vB.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import static fr.xephi.authme.security.HashUtils.md5; -public class MD5VB extends HexSaltedMethod { +public class Md5vB extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MyBB.java similarity index 93% rename from src/main/java/fr/xephi/authme/security/crypts/MYBB.java rename to src/main/java/fr/xephi/authme/security/crypts/MyBB.java index b25f4769..3f0a477a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MyBB.java @@ -10,7 +10,7 @@ import static fr.xephi.authme.security.HashUtils.md5; @Recommendation(Usage.ACCEPTABLE) @HasSalt(value = SaltType.TEXT, length = 8) -public class MYBB extends SeparateSaltMethod { +public class MyBB extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java similarity index 99% rename from src/main/java/fr/xephi/authme/security/crypts/PHPBB.java rename to src/main/java/fr/xephi/authme/security/crypts/PhpBB.java index 074143fd..e5f7e54c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java @@ -9,7 +9,7 @@ import java.security.MessageDigest; /** * @author stefano */ -public class PHPBB extends HexSaltedMethod { +public class PhpBB extends HexSaltedMethod { private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PhpFusion.java similarity index 96% rename from src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java rename to src/main/java/fr/xephi/authme/security/crypts/PhpFusion.java index 905798ec..5a49ed4c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PhpFusion.java @@ -14,7 +14,7 @@ import java.security.NoSuchAlgorithmException; @Recommendation(Usage.DO_NOT_USE) @AsciiRestricted -public class PHPFUSION extends SeparateSaltMethod { +public class PhpFusion extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java b/src/main/java/fr/xephi/authme/security/crypts/PlainText.java similarity index 85% rename from src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java rename to src/main/java/fr/xephi/authme/security/crypts/PlainText.java index a294ca91..add333d6 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PlainText.java @@ -6,7 +6,7 @@ package fr.xephi.authme.security.crypts; * @deprecated Using this is no longer supported. AuthMe will migrate to SHA256 on startup. */ @Deprecated -public class PLAINTEXT extends UnsaltedMethod { +public class PlainText extends UnsaltedMethod { @Override public String computeHash(String password) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/RoyalAuth.java similarity index 88% rename from src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java rename to src/main/java/fr/xephi/authme/security/crypts/RoyalAuth.java index db089e36..989ef838 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/RoyalAuth.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -public class ROYALAUTH extends UnsaltedMethod { +public class RoyalAuth extends UnsaltedMethod { @Override public String computeHash(String password) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/Salted2Md5.java similarity index 91% rename from src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java rename to src/main/java/fr/xephi/authme/security/crypts/Salted2Md5.java index b2f22ab9..6d708b3c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Salted2Md5.java @@ -14,12 +14,12 @@ import static fr.xephi.authme.security.HashUtils.md5; @Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8) @HasSalt(value = SaltType.TEXT) // length defined by the doubleMd5SaltLength setting -public class SALTED2MD5 extends SeparateSaltMethod { +public class Salted2Md5 extends SeparateSaltMethod { private final int saltLength; @Inject - public SALTED2MD5(Settings settings) { + public Salted2Md5(Settings settings) { saltLength = settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SaltedSha512.java similarity index 90% rename from src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java rename to src/main/java/fr/xephi/authme/security/crypts/SaltedSha512.java index f0f29343..b5660d65 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SaltedSha512.java @@ -6,7 +6,7 @@ import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; @Recommendation(Usage.RECOMMENDED) -public class SALTEDSHA512 extends SeparateSaltMethod { +public class SaltedSha512 extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java index 7d4b3d95..d0dacda4 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java @@ -8,15 +8,15 @@ public abstract class SeparateSaltMethod implements EncryptionMethod { @Override public abstract String computeHash(String password, String salt, String name); - @Override - public abstract String generateSalt(); - @Override public HashedPassword computeHash(String password, String name) { String salt = generateSalt(); return new HashedPassword(computeHash(password, salt, name), salt); } + @Override + public abstract String generateSalt(); + @Override public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { return hashedPassword.getHash().equals(computeHash(password, hashedPassword.getSalt(), null)); diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java b/src/main/java/fr/xephi/authme/security/crypts/Sha1.java similarity index 82% rename from src/main/java/fr/xephi/authme/security/crypts/SHA1.java rename to src/main/java/fr/xephi/authme/security/crypts/Sha1.java index 080910ec..e3d078e7 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Sha1.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -public class SHA1 extends UnsaltedMethod { +public class Sha1 extends UnsaltedMethod { @Override public String computeHash(String password) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/Sha256.java similarity index 94% rename from src/main/java/fr/xephi/authme/security/crypts/SHA256.java rename to src/main/java/fr/xephi/authme/security/crypts/Sha256.java index ee55d451..1b77a2e4 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Sha256.java @@ -6,7 +6,7 @@ import fr.xephi.authme.security.crypts.description.Usage; import static fr.xephi.authme.security.HashUtils.sha256; @Recommendation(Usage.RECOMMENDED) -public class SHA256 extends HexSaltedMethod { +public class Sha256 extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java b/src/main/java/fr/xephi/authme/security/crypts/Sha512.java similarity index 81% rename from src/main/java/fr/xephi/authme/security/crypts/SHA512.java rename to src/main/java/fr/xephi/authme/security/crypts/Sha512.java index 81f1e026..12e51a31 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Sha512.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -public class SHA512 extends UnsaltedMethod { +public class Sha512 extends UnsaltedMethod { @Override public String computeHash(String password) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/Smf.java similarity index 85% rename from src/main/java/fr/xephi/authme/security/crypts/SMF.java rename to src/main/java/fr/xephi/authme/security/crypts/Smf.java index 175efc3f..46114d2e 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Smf.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -public class SMF extends UsernameSaltMethod { +public class Smf extends UsernameSaltMethod { @Override public HashedPassword computeHash(String password, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java index 698979d8..23101e22 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java @@ -17,13 +17,13 @@ public abstract class UsernameSaltMethod implements EncryptionMethod { public abstract HashedPassword computeHash(String password, String name); @Override - public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { - return hashedPassword.getHash().equals(computeHash(password, name).getHash()); + public String computeHash(String password, String salt, String name) { + return computeHash(password, name).getHash(); } @Override - public String computeHash(String password, String salt, String name) { - return computeHash(password, name).getHash(); + public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { + return hashedPassword.getHash().equals(computeHash(password, name).getHash()); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java b/src/main/java/fr/xephi/authme/security/crypts/Wbb3.java similarity index 94% rename from src/main/java/fr/xephi/authme/security/crypts/WBB3.java rename to src/main/java/fr/xephi/authme/security/crypts/Wbb3.java index 546c2dc8..0a042b48 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Wbb3.java @@ -10,7 +10,7 @@ import static fr.xephi.authme.security.HashUtils.sha1; @Recommendation(Usage.ACCEPTABLE) @HasSalt(value = SaltType.TEXT, length = 40) -public class WBB3 extends SeparateSaltMethod { +public class Wbb3 extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java similarity index 96% rename from src/main/java/fr/xephi/authme/security/crypts/WBB4.java rename to src/main/java/fr/xephi/authme/security/crypts/Wbb4.java index a8150337..d1d4953d 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java @@ -6,7 +6,7 @@ import fr.xephi.authme.security.crypts.description.Usage; import static fr.xephi.authme.security.crypts.BCryptService.hashpw; @Recommendation(Usage.RECOMMENDED) -public class WBB4 extends HexSaltedMethod { +public class Wbb4 extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java similarity index 99% rename from src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java rename to src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java index 72d8e8fb..84efae8e 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java @@ -61,7 +61,7 @@ package fr.xephi.authme.security.crypts; import java.util.Arrays; -public class WHIRLPOOL extends UnsaltedMethod { +public class Whirlpool extends UnsaltedMethod { /** * The message digest size (in bits) @@ -158,7 +158,7 @@ public class WHIRLPOOL extends UnsaltedMethod { protected final long[] block = new long[8]; protected final long[] state = new long[8]; - public WHIRLPOOL() { + public Whirlpool() { } protected static String display(byte[] array) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/Wordpress.java similarity index 98% rename from src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java rename to src/main/java/fr/xephi/authme/security/crypts/Wordpress.java index f331d1fc..768b92c5 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Wordpress.java @@ -16,7 +16,7 @@ import java.util.Arrays; @HasSalt(value = SaltType.TEXT, length = 9) // Note ljacqu 20151228: Wordpress is actually a salted algorithm but salt generation is handled internally // and isn't exposed to the outside, so we treat it as an unsalted implementation -public class WORDPRESS extends UnsaltedMethod { +public class Wordpress extends UnsaltedMethod { private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private final SecureRandom randomGen = new SecureRandom(); diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAuth.java similarity index 87% rename from src/main/java/fr/xephi/authme/security/crypts/XAUTH.java rename to src/main/java/fr/xephi/authme/security/crypts/XAuth.java index f2ebf197..9f921b6a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XAuth.java @@ -4,15 +4,15 @@ import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; @Recommendation(Usage.RECOMMENDED) -public class XAUTH extends HexSaltedMethod { +public class XAuth extends HexSaltedMethod { private static String getWhirlpool(String message) { - WHIRLPOOL w = new WHIRLPOOL(); - byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES]; + Whirlpool w = new Whirlpool(); + byte[] digest = new byte[Whirlpool.DIGESTBYTES]; w.NESSIEinit(); w.NESSIEadd(message); w.NESSIEfinalize(digest); - return WHIRLPOOL.display(digest); + return Whirlpool.display(digest); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java similarity index 97% rename from src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java rename to src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java index a20ee65a..bf554529 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java @@ -7,7 +7,7 @@ import fr.xephi.authme.util.StringUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class XFBCRYPT implements EncryptionMethod { +public class XfBCrypt implements EncryptionMethod { public static final String SCHEME_CLASS = "XenForo_Authentication_Core12"; private static final Pattern HASH_PATTERN = Pattern.compile("\"hash\";s.*\"(.*)?\""); diff --git a/src/main/java/fr/xephi/authme/service/BukkitService.java b/src/main/java/fr/xephi/authme/service/BukkitService.java index 14cf0dcb..e945cebd 100644 --- a/src/main/java/fr/xephi/authme/service/BukkitService.java +++ b/src/main/java/fr/xephi/authme/service/BukkitService.java @@ -13,7 +13,6 @@ import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.Event; -import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitTask; @@ -172,7 +171,7 @@ public class BukkitService implements SettingsDependent { * @return a BukkitTask that contains the id number * @throws IllegalArgumentException if plugin is null * @throws IllegalStateException if this was already scheduled - * @see BukkitScheduler#runTaskTimer(Plugin, Runnable, long, long) + * @see BukkitScheduler#runTaskTimer(org.bukkit.plugin.Plugin, Runnable, long, long) */ public BukkitTask runTaskTimer(BukkitRunnable task, long delay, long period) { return task.runTaskTimer(authMe, delay, period); diff --git a/src/main/java/fr/xephi/authme/service/CommonService.java b/src/main/java/fr/xephi/authme/service/CommonService.java index cea51ea0..d2381a45 100644 --- a/src/main/java/fr/xephi/authme/service/CommonService.java +++ b/src/main/java/fr/xephi/authme/service/CommonService.java @@ -65,16 +65,6 @@ public class CommonService { messages.send(sender, key, replacements); } - /** - * Retrieves a message. - * - * @param key the key of the message - * @return the message, split by line - */ - public String[] retrieveMessage(MessageKey key) { - return messages.retrieve(key); - } - /** * Retrieves a message in one piece. * @@ -102,6 +92,7 @@ public class CommonService { * @param player the player to process * @param group the group to add the player to */ + // TODO ljacqu 20170304: Move this out of CommonService public void setGroup(Player player, AuthGroupType group) { authGroupHandler.setGroup(player, group); } diff --git a/src/main/java/fr/xephi/authme/service/MigrationService.java b/src/main/java/fr/xephi/authme/service/MigrationService.java index 043de898..338ef51a 100644 --- a/src/main/java/fr/xephi/authme/service/MigrationService.java +++ b/src/main/java/fr/xephi/authme/service/MigrationService.java @@ -5,7 +5,7 @@ import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.security.crypts.SHA256; +import fr.xephi.authme.security.crypts.Sha256; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; @@ -20,14 +20,14 @@ public final class MigrationService { } /** - * Hash all passwords to SHA256 and updated the setting if the password hash is set to the deprecated PLAINTEXT. + * Hash all passwords to Sha256 and updated the setting if the password hash is set to the deprecated PLAINTEXT. * * @param settings The settings instance * @param dataSource The data source - * @param authmeSha256 Instance to the AuthMe SHA256 encryption method implementation + * @param authmeSha256 Instance to the AuthMe Sha256 encryption method implementation */ public static void changePlainTextToSha256(Settings settings, DataSource dataSource, - SHA256 authmeSha256) { + Sha256 authmeSha256) { if (HashAlgorithm.PLAINTEXT == settings.getProperty(SecuritySettings.PASSWORD_HASH)) { ConsoleLogger.warning("Your HashAlgorithm has been detected as plaintext and is now deprecated;" + " it will be changed and hashed now to the AuthMe default hashing method"); diff --git a/src/main/java/fr/xephi/authme/service/TeleportationService.java b/src/main/java/fr/xephi/authme/service/TeleportationService.java index 9be5012f..65fd8404 100644 --- a/src/main/java/fr/xephi/authme/service/TeleportationService.java +++ b/src/main/java/fr/xephi/authme/service/TeleportationService.java @@ -99,19 +99,19 @@ public class TeleportationService implements Reloadable { * * @param player the player * @param auth corresponding PlayerAuth object - * @param limbo corresponding PlayerData object + * @param limbo corresponding LimboPlayer object */ public void teleportOnLogin(final Player player, PlayerAuth auth, LimboPlayer limbo) { if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) { return; } - // #856: If PlayerData comes from a persisted file, the Location might be null + // #856: If LimboPlayer comes from a persisted file, the Location might be null String worldName = (limbo != null && limbo.getLocation() != null) ? limbo.getLocation().getWorld().getName() : null; - // The world in PlayerData is from where the player comes, before any teleportation by AuthMe + // The world in LimboPlayer is from where the player comes, before any teleportation by AuthMe if (mustForceSpawnAfterLogin(worldName)) { teleportToSpawn(player, true); } else if (settings.getProperty(TELEPORT_UNAUTHED_TO_SPAWN)) { @@ -148,7 +148,7 @@ public class TeleportationService implements Reloadable { /** * Emits the teleportation event and performs teleportation according to it (potentially modified - * by external listeners). Note that not teleportation is performed if the event's location is empty. + * by external listeners). Note that no teleportation is performed if the event's location is empty. * * @param player the player to teleport * @param event the event to emit and according to which to teleport diff --git a/src/main/java/fr/xephi/authme/service/ValidationService.java b/src/main/java/fr/xephi/authme/service/ValidationService.java index 7c7abf78..9511c6e3 100644 --- a/src/main/java/fr/xephi/authme/service/ValidationService.java +++ b/src/main/java/fr/xephi/authme/service/ValidationService.java @@ -1,6 +1,9 @@ package fr.xephi.authme.service; import ch.jalu.configme.properties.Property; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.message.MessageKey; @@ -11,8 +14,10 @@ import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.ProtectionSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.Utils; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import javax.annotation.PostConstruct; import javax.inject.Inject; @@ -22,6 +27,8 @@ import java.util.List; import java.util.Set; import java.util.regex.Pattern; +import static fr.xephi.authme.util.StringUtils.isInsideString; + /** * Validation service. */ @@ -38,6 +45,7 @@ public class ValidationService implements Reloadable { private Pattern passwordRegex; private Set unrestrictedNames; + private Multimap restrictedNames; ValidationService() { } @@ -48,6 +56,9 @@ public class ValidationService implements Reloadable { 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(); } /** @@ -115,9 +126,10 @@ public class ValidationService implements Reloadable { } String countryCode = geoIpService.getCountryCode(hostAddress); - return validateWhitelistAndBlacklist(countryCode, - ProtectionSettings.COUNTRIES_WHITELIST, - ProtectionSettings.COUNTRIES_BLACKLIST); + boolean isCountryAllowed = validateWhitelistAndBlacklist(countryCode, + ProtectionSettings.COUNTRIES_WHITELIST, ProtectionSettings.COUNTRIES_BLACKLIST); + ConsoleLogger.debug("Country code `{0}` for `{1}` is allowed: {2}", countryCode, hostAddress, isCountryAllowed); + return isCountryAllowed; } /** @@ -130,6 +142,24 @@ public class ValidationService implements Reloadable { return unrestrictedNames.contains(name.toLowerCase()); } + /** + * Checks that the player meets any name restriction if present (IP/domain-based). + * + * @param player the player to check + * @return true if the player may join, false if the player does not satisfy the name restrictions + */ + public boolean fulfillsNameRestrictions(Player player) { + Collection restrictions = restrictedNames.get(player.getName().toLowerCase()); + if (Utils.isCollectionEmpty(restrictions)) { + return true; + } + + String ip = PlayerUtils.getPlayerIp(player); + String domain = player.getAddress().getHostName(); + return restrictions.stream() + .anyMatch(restriction -> ip.equals(restriction) || domain.equalsIgnoreCase(restriction)); + } + /** * Verifies whether the given value is allowed according to the given whitelist and blacklist settings. * Whitelist has precedence over blacklist: if a whitelist is set, the value is rejected if not present @@ -159,6 +189,26 @@ public class ValidationService implements Reloadable { return false; } + /** + * Loads the configured name restrictions into a Multimap by player name (all-lowercase). + * + * @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) { + Multimap restrictions = HashMultimap.create(); + for (String restriction : configuredRestrictions) { + if (isInsideString(';', restriction)) { + String[] data = restriction.split(";"); + restrictions.put(data[0].toLowerCase(), data[1]); + } else { + ConsoleLogger.warning("Restricted user rule must have a ';' separating name from restriction," + + " but found: '" + restriction + "'"); + } + } + return restrictions; + } + public static final class ValidationResult { private final MessageKey messageKey; private final String[] args; @@ -194,6 +244,7 @@ public class ValidationService implements Reloadable { public MessageKey getMessageKey() { return messageKey; } + public String[] getArgs() { return args; } 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 d87ebd5e..f996640c 100644 --- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java @@ -74,7 +74,7 @@ public class CommandManager implements Reloadable { private void executeCommands(Player player, List commands) { for (Command command : commands) { - final String execution = command.getCommand().replace("%p", player.getName()); + final String execution = command.getCommand(); if (Executor.CONSOLE.equals(command.getExecutor())) { bukkitService.dispatchConsoleCommand(execution); } else { 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 8cfb29dc..2fbe7ea2 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java +++ b/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java @@ -20,9 +20,9 @@ public final class AuthMeSettingsRetriever { */ public static ConfigurationData buildConfigurationData() { return ConfigurationDataBuilder.collectData( - DatabaseSettings.class, ConverterSettings.class, PluginSettings.class, - RestrictionSettings.class, EmailSettings.class, HooksSettings.class, - ProtectionSettings.class, PurgeSettings.class, SecuritySettings.class, - RegistrationSettings.class, BackupSettings.class); + DatabaseSettings.class, PluginSettings.class, RestrictionSettings.class, + EmailSettings.class, HooksSettings.class, ProtectionSettings.class, + PurgeSettings.class, SecuritySettings.class, RegistrationSettings.class, + LimboSettings.class, BackupSettings.class, ConverterSettings.class); } } diff --git a/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java b/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java index 162bf8aa..57bb5941 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java @@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class BackupSettings implements SettingsHolder { +public final class BackupSettings implements SettingsHolder { @Comment("Enable or disable automatic backup") public static final Property ENABLED = 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 ae289e54..d2b34c9a 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java @@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class ConverterSettings implements SettingsHolder { +public final class ConverterSettings implements SettingsHolder { @Comment("Rakamak file name") public static final Property RAKAMAK_FILE_NAME = 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 fde994af..1e4697bf 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java @@ -7,10 +7,10 @@ import fr.xephi.authme.datasource.DataSourceType; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class DatabaseSettings implements SettingsHolder { +public final class DatabaseSettings implements SettingsHolder { @Comment({"What type of database do you want to use?", - "Valid values: sqlite, mysql"}) + "Valid values: SQLITE, MYSQL"}) public static final Property BACKEND = newProperty(DataSourceType.class, "DataSource.backend", DataSourceType.SQLITE); diff --git a/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java b/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java index 3a9ede5d..f7522b94 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java @@ -9,7 +9,7 @@ import java.util.List; import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class EmailSettings implements SettingsHolder { +public final class EmailSettings implements SettingsHolder { @Comment("Email SMTP server host") public static final Property SMTP_HOST = diff --git a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java index f512d67b..e752057c 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java @@ -9,7 +9,7 @@ import java.util.List; import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class HooksSettings implements SettingsHolder { +public final class HooksSettings implements SettingsHolder { @Comment("Do we need to hook with multiverse for spawn checking?") public static final Property MULTIVERSE = @@ -54,6 +54,18 @@ public class HooksSettings implements SettingsHolder { public static final Property PHPBB_ACTIVATED_GROUP_ID = newProperty("ExternalBoardOptions.phpbbActivatedGroupId", 2); + @Comment("IP Board table prefix defined during the IP Board installation process") + public static final Property IPB_TABLE_PREFIX = + newProperty("ExternalBoardOptions.IPBTablePrefix", "ipb_"); + + @Comment("IP Board default group ID; 3 is the default registered group defined by IP Board") + public static final Property IPB_ACTIVATED_GROUP_ID = + newProperty("ExternalBoardOptions.IPBActivatedGroupId", 3); + + @Comment("XenForo default group ID; 2 is the default registered group defined by Xenforo") + public static final Property XF_ACTIVATED_GROUP_ID = + newProperty("ExternalBoardOptions.XFActivatedGroupId", 2); + @Comment("Wordpress prefix defined during WordPress installation") public static final Property WORDPRESS_TABLE_PREFIX = newProperty("ExternalBoardOptions.wordpressTablePrefix", "wp_"); diff --git a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java new file mode 100644 index 00000000..a48db6b3 --- /dev/null +++ b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java @@ -0,0 +1,86 @@ +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.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.SegmentConfiguration; + +import java.util.Map; + +import static ch.jalu.configme.properties.PropertyInitializer.newProperty; + +/** + * Settings for the LimboPlayer feature. + */ +public final class LimboSettings implements SettingsHolder { + + @Comment({ + "Besides storing the data in memory, you can define if/how the data should be persisted", + "on disk. This is useful in case of a server crash, so next time the server starts we can", + "properly restore things like OP status, ability to fly, and walk/fly speed.", + "DISABLED: no disk storage, INDIVIDUAL_FILES: each player data in its own file,", + "SINGLE_FILE: all data in one single file (only if you have a small server!)", + "SEGMENT_FILES: distributes players into different buckets based on their UUID. See below." + }) + public static final Property LIMBO_PERSISTENCE_TYPE = + newProperty(LimboPersistenceType.class, "limbo.persistence.type", LimboPersistenceType.INDIVIDUAL_FILES); + + @Comment({ + "This setting only affects SEGMENT_FILES persistence. The segment file", + "persistence attempts to reduce the number of files by distributing players into various", + "buckets based on their UUID. This setting defines into how many files the players should", + "be distributed. Possible values: ONE, FOUR, EIGHT, SIXTEEN, THIRTY_TWO, SIXTY_FOUR,", + "ONE_TWENTY for 128, TWO_FIFTY for 256.", + "For example, if you expect 100 non-logged in players, setting to SIXTEEN will average", + "6.25 players per file (100 / 16). If you set to ONE, like persistence SINGLE_FILE, only", + "one file will be used. Contrary to SINGLE_FILE, it won't keep the entries in cache, which", + "may deliver different results in terms of performance.", + "Note: if you change this setting all data will be migrated. If you have a lot of data,", + "change this setting only on server restart, not with /authme reload." + }) + public static final Property SEGMENT_DISTRIBUTION = + newProperty(SegmentConfiguration.class, "limbo.persistence.segmentDistribution", SegmentConfiguration.SIXTEEN); + + @Comment({ + "Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE.", + "RESTORE sets back the old property from the player." + }) + public static final Property RESTORE_ALLOW_FLIGHT = + newProperty(AllowFlightRestoreType.class, "limbo.restoreAllowFlight", AllowFlightRestoreType.RESTORE); + + @Comment({ + "Restore fly speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO.", + "RESTORE: restore the speed the player had;", + "DEFAULT: always set to default speed;", + "MAX_RESTORE: take the maximum of the player's current speed and the previous one", + "RESTORE_NO_ZERO: Like 'restore' but sets speed to default if the player's speed was 0" + }) + public static final Property RESTORE_FLY_SPEED = + newProperty(WalkFlySpeedRestoreType.class, "limbo.restoreFlySpeed", WalkFlySpeedRestoreType.MAX_RESTORE); + + @Comment({ + "Restore walk speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO.", + "See above for a description of the values." + }) + public static final Property RESTORE_WALK_SPEED = + newProperty(WalkFlySpeedRestoreType.class, "limbo.restoreWalkSpeed", WalkFlySpeedRestoreType.MAX_RESTORE); + + private LimboSettings() { + } + + @SectionComments + public static Map createSectionComments() { + 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.", + "Once the user is logged in, we add back the properties we previously saved.", + "In this section, you may define how these properties should be handled." + }; + return ImmutableMap.of("limbo", limboExplanation); + } +} diff --git a/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java b/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java index 5f45ca5d..d99ffa0b 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java @@ -7,7 +7,7 @@ import fr.xephi.authme.output.LogLevel; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class PluginSettings implements SettingsHolder { +public final class PluginSettings implements SettingsHolder { @Comment({ "Do you want to enable the session feature?", 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 3a19a70e..2bae7179 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java @@ -10,7 +10,7 @@ import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class ProtectionSettings implements SettingsHolder { +public final class ProtectionSettings implements SettingsHolder { @Comment("Enable some servers protection (country based login, antibot)") public static final Property ENABLE_PROTECTION = diff --git a/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java b/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java index 0cfa029a..2c62454c 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java @@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class PurgeSettings implements SettingsHolder { +public final class PurgeSettings implements SettingsHolder { @Comment("If enabled, AuthMe automatically purges old, unused accounts") public static final Property USE_AUTO_PURGE = diff --git a/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java index 38615b78..7d87e77b 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java @@ -8,7 +8,7 @@ import fr.xephi.authme.process.register.RegistrationType; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class RegistrationSettings implements SettingsHolder { +public final class RegistrationSettings implements SettingsHolder { @Comment("Enable registration on the server?") public static final Property IS_ENABLED = @@ -42,7 +42,8 @@ public class RegistrationSettings implements SettingsHolder { "EMAIL_MANDATORY = for password register: 2nd argument MUST be an email address" }) public static final Property REGISTER_SECOND_ARGUMENT = - newProperty(RegisterSecondaryArgument.class, "settings.registration.secondArg", RegisterSecondaryArgument.CONFIRMATION); + newProperty(RegisterSecondaryArgument.class, "settings.registration.secondArg", + RegisterSecondaryArgument.CONFIRMATION); @Comment({ "Do we force kick a player after a successful registration?", 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 d0677579..40de8ca6 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java @@ -10,7 +10,7 @@ import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class RestrictionSettings implements SettingsHolder { +public final class RestrictionSettings implements SettingsHolder { @Comment({ "Can not authenticated players chat?", @@ -81,7 +81,7 @@ public class RestrictionSettings implements SettingsHolder { "Example:", " AllowedRestrictedUser:", " - playername;127.0.0.1"}) - public static final Property> ALLOWED_RESTRICTED_USERS = + public static final Property> RESTRICTED_USERS = newLowercaseListProperty("settings.restrictions.AllowedRestrictedUser"); @Comment("Ban unknown IPs trying to log in with a restricted username?") 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 d16a1c56..33482dbb 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java @@ -12,7 +12,7 @@ import java.util.Set; import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class SecuritySettings implements SettingsHolder { +public final class SecuritySettings implements SettingsHolder { @Comment({"Stop the server if we can't contact the sql database", "Take care with this, if you set this to false,", @@ -86,7 +86,8 @@ public class SecuritySettings implements SettingsHolder { "- 'password'", "- 'help'"}) public static final Property> UNSAFE_PASSWORDS = - newLowercaseListProperty("settings.security.unsafePasswords", "123456", "password", "qwerty", "12345", "54321", "123456789", "help"); + newLowercaseListProperty("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") public static final Property TEMPBAN_ON_MAX_LOGINS = diff --git a/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java b/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java deleted file mode 100644 index 23456567..00000000 --- a/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java +++ /dev/null @@ -1,123 +0,0 @@ -package fr.xephi.authme.task; - -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; -import fr.xephi.authme.data.limbo.LimboPlayer; -import fr.xephi.authme.message.MessageKey; -import fr.xephi.authme.message.Messages; -import fr.xephi.authme.service.BukkitService; -import fr.xephi.authme.settings.Settings; -import fr.xephi.authme.settings.properties.RegistrationSettings; -import fr.xephi.authme.settings.properties.RestrictionSettings; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; - -import javax.inject.Inject; - -import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND; - -/** - * Registers tasks associated with a PlayerData. - */ -public class LimboPlayerTaskManager { - - @Inject - private Messages messages; - - @Inject - private Settings settings; - - @Inject - private BukkitService bukkitService; - - @Inject - private LimboCache limboCache; - - @Inject - private PlayerCache playerCache; - - LimboPlayerTaskManager() { - } - - - /** - * Registers a {@link MessageTask} for the given player name. - * - * @param name the name of the player to schedule a repeating message task for - * @param isRegistered whether the name is registered or not - * (false shows "please register", true shows "please log in") - */ - public void registerMessageTask(String name, boolean isRegistered) { - final int interval = settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL); - final MessageKey key = getMessageKey(isRegistered); - if (interval > 0) { - final LimboPlayer limboPlayer = limboCache.getPlayerData(name); - if (limboPlayer == null) { - ConsoleLogger.info("PlayerData for '" + name + "' is not available"); - } else { - cancelTask(limboPlayer.getMessageTask()); - MessageTask messageTask = new MessageTask(name, messages.retrieve(key), bukkitService, playerCache); - bukkitService.runTaskTimer(messageTask, 2 * TICKS_PER_SECOND, interval * TICKS_PER_SECOND); - limboPlayer.setMessageTask(messageTask); - } - } - } - - /** - * Registers a {@link TimeoutTask} for the given player according to the configuration. - * - * @param player the player to register a timeout task for - */ - public void registerTimeoutTask(Player player) { - final int timeout = settings.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; - if (timeout > 0) { - final LimboPlayer limboPlayer = limboCache.getPlayerData(player.getName()); - if (limboPlayer == null) { - ConsoleLogger.info("PlayerData for '" + player.getName() + "' is not available"); - } else { - cancelTask(limboPlayer.getTimeoutTask()); - String message = messages.retrieveSingle(MessageKey.LOGIN_TIMEOUT_ERROR); - BukkitTask task = bukkitService.runTaskLater(new TimeoutTask(player, message, playerCache), timeout); - limboPlayer.setTimeoutTask(task); - } - } - } - - /** - * Returns the appropriate message key according to the registration status and settings. - * - * @param isRegistered whether or not the username is registered - * @return the message key to display to the user - */ - private MessageKey getMessageKey(boolean isRegistered) { - if (isRegistered) { - return MessageKey.LOGIN_MESSAGE; - } else { - return MessageKey.REGISTER_MESSAGE; - } - } - - /** - * Null-safe method to cancel a potentially existing task. - * - * @param task the task to cancel (or null) - */ - private static void cancelTask(BukkitTask task) { - if (task != null) { - task.cancel(); - } - } - - /** - * Null-safe method to cancel a potentially existing task. - * - * @param task the task to cancel (or null) - */ - private static void cancelTask(BukkitRunnable task) { - if (task != null) { - task.cancel(); - } - } -} diff --git a/src/main/java/fr/xephi/authme/task/MessageTask.java b/src/main/java/fr/xephi/authme/task/MessageTask.java index 74ef45f1..cf4366d9 100644 --- a/src/main/java/fr/xephi/authme/task/MessageTask.java +++ b/src/main/java/fr/xephi/authme/task/MessageTask.java @@ -1,7 +1,5 @@ package fr.xephi.authme.task; -import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.service.BukkitService; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; @@ -10,20 +8,16 @@ import org.bukkit.scheduler.BukkitRunnable; */ public class MessageTask extends BukkitRunnable { - private final String name; + private final Player player; private final String[] message; - private final BukkitService bukkitService; - private final PlayerCache playerCache; private boolean isMuted; /* * Constructor. */ - public MessageTask(String name, String[] lines, BukkitService bukkitService, PlayerCache playerCache) { - this.name = name; + public MessageTask(Player player, String[] lines) { + this.player = player; this.message = lines; - this.bukkitService = bukkitService; - this.playerCache = playerCache; isMuted = false; } @@ -33,21 +27,8 @@ public class MessageTask extends BukkitRunnable { @Override public void run() { - if (playerCache.isAuthenticated(name)) { - cancel(); - } - - if (isMuted) { - return; - } - - for (Player player : bukkitService.getOnlinePlayers()) { - if (player.getName().equalsIgnoreCase(name)) { - for (String ms : message) { - player.sendMessage(ms); - } - break; - } + if (!isMuted) { + player.sendMessage(message); } } } diff --git a/src/main/java/fr/xephi/authme/task/purge/PurgeService.java b/src/main/java/fr/xephi/authme/task/purge/PurgeService.java index fed07768..fe6d2fbf 100644 --- a/src/main/java/fr/xephi/authme/task/purge/PurgeService.java +++ b/src/main/java/fr/xephi/authme/task/purge/PurgeService.java @@ -9,14 +9,13 @@ import fr.xephi.authme.settings.properties.PurgeSettings; import fr.xephi.authme.util.Utils; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; import javax.inject.Inject; import java.util.Calendar; import java.util.Collection; import java.util.Set; -// TODO: move into services. -sgdc3 +import static fr.xephi.authme.util.Utils.logAndSendMessage; /** * Initiates purge tasks. @@ -119,12 +118,4 @@ public class PurgeService { void executePurge(Collection players, Collection names) { purgeExecutor.executePurge(players, names); } - - private static void logAndSendMessage(CommandSender sender, String message) { - ConsoleLogger.info(message); - // Make sure sender is not console user, which will see the message from ConsoleLogger already - if (sender != null && !(sender instanceof ConsoleCommandSender)) { - sender.sendMessage(message); - } - } } diff --git a/src/main/java/fr/xephi/authme/util/RandomStringUtils.java b/src/main/java/fr/xephi/authme/util/RandomStringUtils.java index db166a74..2dce3c64 100644 --- a/src/main/java/fr/xephi/authme/util/RandomStringUtils.java +++ b/src/main/java/fr/xephi/authme/util/RandomStringUtils.java @@ -24,7 +24,7 @@ public final class RandomStringUtils { * @return The random string */ public static String generate(int length) { - return generate(length, LOWER_ALPHANUMERIC_INDEX); + return generateString(length, LOWER_ALPHANUMERIC_INDEX); } /** @@ -35,7 +35,7 @@ public final class RandomStringUtils { * @return The random hexadecimal string */ public static String generateHex(int length) { - return generate(length, HEX_MAX_INDEX); + return generateString(length, HEX_MAX_INDEX); } /** @@ -46,10 +46,10 @@ public final class RandomStringUtils { * @return The random string */ public static String generateLowerUpper(int length) { - return generate(length, CHARS.length); + return generateString(length, CHARS.length); } - private static String generate(int length, int maxIndex) { + private static String generateString(int length, int maxIndex) { if (length < 0) { throw new IllegalArgumentException("Length must be positive but was " + length); } diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java index 5e0696af..1f200c0f 100644 --- a/src/main/java/fr/xephi/authme/util/StringUtils.java +++ b/src/main/java/fr/xephi/authme/util/StringUtils.java @@ -76,4 +76,20 @@ public final class StringUtils { public static String formatException(Throwable th) { return "[" + th.getClass().getSimpleName() + "]: " + th.getMessage(); } + + /** + * Check that the given needle is in the middle of the haystack, i.e. that the haystack + * contains the needle and that it is not at the very start or end. + * + * @param needle the needle to search for + * @param haystack the haystack to search in + * + * @return true if the needle is in the middle of the word, false otherwise + */ + // Note ljacqu 20170314: `needle` is restricted to char type intentionally because something like + // isInsideString("11", "2211") would unexpectedly return true... + public static boolean isInsideString(char needle, String haystack) { + int index = haystack.indexOf(needle); + return index > 0 && index < haystack.length() - 1; + } } diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index b7628147..4d6983df 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -1,6 +1,8 @@ package fr.xephi.authme.util; import fr.xephi.authme.ConsoleLogger; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; import java.util.Collection; import java.util.regex.Pattern; @@ -51,6 +53,22 @@ public final class Utils { } } + /** + * Sends a message to the given sender (null safe), and logs the message to the console. + * This method is aware that the command sender might be the console sender and avoids + * displaying the message twice in this case. + * + * @param sender the sender to inform + * @param message the message to log and send + */ + public static void logAndSendMessage(CommandSender sender, String message) { + ConsoleLogger.info(message); + // Make sure sender is not console user, which will see the message from ConsoleLogger already + if (sender != null && !(sender instanceof ConsoleCommandSender)) { + sender.sendMessage(message); + } + } + /** * Null-safe way to check whether a collection is empty or not. * diff --git a/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java b/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java index 660132fb..a9d19319 100644 --- a/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java +++ b/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java @@ -11,7 +11,7 @@ import java.util.stream.Collectors; * * @param the argument type */ -public class TagReplacer { +public final class TagReplacer { private final List> tags; private final Collection messages; diff --git a/src/main/resources/messages/help_pt.yml b/src/main/resources/messages/help_pt.yml new file mode 100644 index 00000000..2b2ead6c --- /dev/null +++ b/src/main/resources/messages/help_pt.yml @@ -0,0 +1,45 @@ +# Translation config for the AuthMe help, e.g. when /authme help or /authme help register is called + +# ------------------------------------------------------- +# List of texts used in the help section +common: + header: '==========[ AJUDA DO AuthMeReloaded ]==========' + optional: 'Opcional' + hasPermission: 'Tu tens permissão' + noPermission: 'Não tens permissão' + default: 'Padrão' + result: 'Resultado' + defaultPermissions: + notAllowed: 'Sem permissão' + opOnly: 'Só OP' + allowed: 'Toda gente é permitida' + +# ------------------------------------------------------- +# 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: 'Comando' + description: 'Breve descrição' + detailedDescription: 'Descrição detalhada' + arguments: 'Argumentos' + permissions: 'Permissões' + alternatives: 'Alternativas' + children: 'Comandos' + +# ------------------------------------------------------- +# You can translate the data for all commands using the below pattern. +# For example to translate /authme reload, create a section "authme.reload", or "login" for /login +# If the command has arguments, you can use arg1 as below to translate the first argument, and so forth +# 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: 'Registar um jogador' + detailedDescription: 'Registar um jogador com uma senha especifica.' + arg1: + label: 'jogador' + description: 'Nome de jogador' + arg2: + label: 'senha' + description: 'Senha' diff --git a/src/main/resources/messages/messages_bg.yml b/src/main/resources/messages/messages_bg.yml index fdaf32fa..2e004b62 100644 --- a/src/main/resources/messages/messages_bg.yml +++ b/src/main/resources/messages/messages_bg.yml @@ -1,108 +1,109 @@ # Registration -reg_msg: '&cМоля регистрирай се с "/register <парола> <парола>"' +reg_msg: '&3Моля регистрирайте се с: /register парола парола' usage_reg: '&cКоманда: /register парола парола' -reg_only: '&fСамо за регистрирани! Моля посети http://example.com за регистрация' -# TODO kicked_admin_registered: 'An admin just registered you; please log in again' -registered: '&cУспешно премахната регистрация!' +reg_only: '&4Само регистрирани потребители могат да влизат в сървъра! Моля посетете http://example.com, за да се регистрирате!' +kicked_admin_registered: 'Ти беше регистриран от администратора, моля влезте отново' +registered: '&2Успешна регистрация!' reg_disabled: '&cРегистрациите са изключени!' -user_regged: '&cПотребителското име е заето!' +user_regged: '&cПотребителското име е заетo!' # Password errors on registration -password_error: '&fПаролата не съвпада' -# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' -# TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' -# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' -pass_len: '&cВашета парола не е достатъчно дълга или къса.' +password_error: '&cПаролите не съвпадат, провете ги отново!' +password_error_nick: '&cНе можеш да използваш потребителското си име за парола, моля изберете друга парола.' +password_error_unsafe: '&cИзбраната парола не е безопасна, моля изберете друга парола.' +password_error_chars: '&4Паролата съдържа непозволени символи. Позволени символи: REG_EX' +pass_len: '&cПаролата е твърде къса или прекалено дълга! Моля опитайте с друга парола.' # Login usage_log: '&cКоманда: /login парола' wrong_pwd: '&cГрешна парола!' -login: '&cВход успешен!' -login_msg: '&cМоля влез с "/login парола"' -timeout: '&fВремето изтече, опитай отново!' +login: '&2Успешен вход!' +login_msg: '&cМоля влезте с: /login парола' +timeout: '&4Времето за вход изтече, беше кикнат от сървъра. Моля опитайте отново!' # Errors -unknown_user: '&cПотребителя не е регистриран' -# TODO denied_command: '&cIn order to use this command you must be authenticated!' -# TODO denied_chat: '&cIn order to chat you must be authenticated!' +unknown_user: '&cПотребителското име не е регистрирано!' +denied_command: '&cЗа да използваш тази команда трябва да си си влезнал в акаунта!' +denied_chat: '&cЗа да пишеш в чата трябва даи сиси влезнал в акаунта!' not_logged_in: '&cНе си влязъл!' -# TODO tempban_max_logins: '&cYou have been temporarily banned for failing to log in too many times.' -# TODO: Missing tags %reg_count, %max_acc, %reg_names -max_reg: '&fТи достигна максималния брой регистрации!' -no_perm: '&cНямаш Достъп!' -error: '&fПолучи се грешка; Моля свържете се с админ' -unsafe_spawn: '&fТвоята локация когато излезе не беше безопасна, телепортиран си на Spawn!' -kick_forvip: '&cVIP влезе докато сървъра е пълен, ти беше изгонен!' +tempban_max_logins: '&cТи беше баннат временно, понеже си сгрешил паролата прекалено много пъти.' +max_reg: '&cТи си достигнал максималният брой регистрации (%reg_count/%max_acc %reg_names)!' +no_perm: '&4Нямаш нужните права за това действие!' +error: '&4Получи се неочаквана грешка, моля свържете се с администратора!' +unsafe_spawn: '&cКогато излезе твоето местоположение не беше безопастно, телепортиран си на Spawn.' +kick_forvip: '&3VIP потребител влезе докато сървъра беше пълен, ти беше изгонен!' # AntiBot -# TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' -antibot_auto_enabled: '[AuthMe] AntiBotMod автоматично включен, открита е потенциална атака!' -antibot_auto_disabled: '[AuthMe] AntiBotMod автоматично изключване след %m Минути.' +kick_antibot: 'Защитата от ботове е включена! Трябва да изчакаш няколко минути преди да влезеш в сървъра.' +antibot_auto_enabled: '&4Защитата за ботове е включена заради потенциална атака!' +antibot_auto_disabled: '&2Защитата за ботове ще се изключи след %m минута/и!' # Other messages +unregistered: '&cРегистрацията е премахната успешно!' +accounts_owned_self: 'Претежаваш %count акаунт/а:' +accounts_owned_other: 'Потребителят %name има %count акаунт/а:' +two_factor_create: '&2Кода е %code. Можеш да го провериш оттука: %url' +recovery_code_sent: 'Възстановяващият код беше изпратен на твоят email адрес.' +recovery_code_incorrect: 'Възстановяващият код е неправилен! Използвайте: /email recovery имейл, за да генерирате нов' +vb_nonActiv: '&cТвоят акаунт все още не е актириван, моля провете своят email адрес!' unregistered: '&cУспешно от-регистриран!' -# TODO accounts_owned_self: 'You own %count accounts:' -# TODO accounts_owned_other: 'The player %name has %count accounts:' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fТвоята регистрация не е активирана, моля провери своя Имейл!' usage_unreg: '&cКоманда: /unregister парола' -pwd_changed: '&cПаролата е променена!' -logged_in: '&cВече сте влязъл!' -logout: '&cУспешен изход от регистрацията!' -reload: '&fКонфигурацията презаредена!' -usage_changepassword: '&fКоманда: /changepassword СтараПарола НоваПарола' +pwd_changed: '&2Паротала е променена успешно!' +logged_in: '&cВече си вписан!' +logout: '&2Излязохте успешно!' +reload: '&2Конфигурацията и база данните бяха презаредени правилно!' +usage_changepassword: '&cКоманда: /changepassword Стара-Парола Нова-Парола' # Session messages -# TODO invalid_session: '&cYour IP has been changed and your session data has expired!' -valid_session: '&aСесията продължена!' +invalid_session: '&cТвоят IP се е променил и сесията беше прекратена.' +valid_session: '&2Сесията е продължена.' # Error messages when joining -name_len: '&cТвоя никнейм е твърде малък или голям' -regex: '&cТвоя никнейм съдържа забранени знацхи. Позволените са: REG_EX' -country_banned: 'Твоята държава е забранена в този сървър!' -# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' -kick_fullserver: '&cСървъра е пълен, Съжеляваме!' -same_nick: '&fПотребител с този никнейм е в игра' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO same_ip_online: 'A player with the same IP is already in game!' +name_len: '&4Потребителското име е прекалено късо или дълга. Моля опитайте с друго потребителско име!' +regex: '&4Потребителското име съдържа забранени знаци. Позволени знаци: REG_EX' +country_banned: '&4Твоята държава е забранена в този сървър!' +not_owner_error: 'Ти не си собственика на този акаунт. Моля избери друго потребителско име!' +kick_fullserver: '&4Сървъра е пълен, моля опитайте отново!' +same_nick: '&4Вече има потребител, който играете в сървъра със същото потребителско име!' +invalid_name_case: 'Трябва да влезеш с %valid, а не с %invalid.' +same_ip_online: 'Вече има потребител със същото IP в сървъра!' # Email -usage_email_add: '&fКоманда: /email add ' -usage_email_change: '&fКоманда: /email change <СтарИмейл> <НовИмейл> ' -usage_email_recovery: '&fКоманда: /email recovery <имейл>' -new_email_invalid: '[AuthMe] Новия имейл е грешен!' -old_email_invalid: '[AuthMe] Стария имейл е грешен!' -email_invalid: '[AuthMe] Грешен имейл' -email_added: '[AuthMe] Имейла добавен !' -email_confirm: '[AuthMe] Потвърди своя имейл !' -email_changed: '[AuthMe] Имейла е сменен !' -email_send: '[AuthMe] Изпраен е имейл !' -# TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO email_show: '&2Your current email address is: &f%email' -# TODO incomplete_email_settings: 'Error: not all required settings are set for sending emails. Please contact an admin.' -# TODO email_already_used: '&4The email address is already being used' -# TODO email_send_failure: 'The email could not be sent. Please contact an administrator.' -# TODO show_no_email: '&2You currently don''t have email address associated with this account.' -add_email: '&cМоля добави своя имейл с : /email add имейл имейл' -recovery_email: '&cЗабравихте своята парола? Моля използвай /email recovery <имейл>' +usage_email_add: '&cКоманда: /email add имейл имейл' +usage_email_change: '&cКоманда: /email change Стар-Имейл Нов-Имейл' +usage_email_recovery: '&cКоманда: /email recovery имейл' +new_email_invalid: '&cНовият имейл е грешен, опитайте отново!' +old_email_invalid: '&cСтарият имейл е грешен, опитайте отново!' +email_invalid: '&cИмейла е невалиден, опитайте с друг!' +email_added: '&2Имейл адреса е добавен!' +email_confirm: '&cМоля потвърди своя имейл адрес!' +email_changed: '&2Имейл адреса е сменен!' +email_send: '&2Възстановяващият имейл е изпратен успешно. Моля провете пощата си!' +email_exists: '&cВъзстановяващият имейл е бил изпратен. Може да го откажеш или изпратиш отново с тази команда:' +email_show: '&2Твоят имейл адрес е: &f%email' +incomplete_email_settings: 'Грешка: Не всички настройки са написани за изпращане на имейл адрес. Моля свържете се с администратора!' +email_already_used: '&4Имейл адреса вече се използва, опитайте с друг.' +email_send_failure: 'Съобщението не беше изпратено. Моля свържете се с администратора.' +show_no_email: '&2Няма добавен имейл адрес към акаунта.' +add_email: '&3Моля добавете имейл адрес към своят акаунт: /email add имейл имейл' +recovery_email: '&3Забравена парола? Използвайте: /email recovery имейл' # TODO change_password_expired: 'You cannot change your password using this command anymore.' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +email_cooldown_error: '&cВече е бил изпратен имейл адрес. Трябва а изчакаш %time преди да пратиш нов.' # Captcha -usage_captcha: '&cYou need to type a captcha, please type: /captcha ' -wrong_captcha: '&cГрешен код, използвай : /captcha THE_CAPTCHA' -valid_captcha: '&cТвоя код е валиден!' +usage_captcha: '&3Моля въведе цифрите/буквите от капчата: /captcha ' +wrong_captcha: '&cКода е грешен, използвайте: "/captcha THE_CAPTCHA" в чата!' +valid_captcha: '&2Кода е валиден!' # Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +second: 'секунда' +seconds: 'секунди' +minute: 'минута' +minutes: 'минути' +hour: 'час' +hours: 'часа' +day: 'ден' +days: 'дена' diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml index 96c0f6a1..808b0cce 100644 --- a/src/main/resources/messages/messages_es.yml +++ b/src/main/resources/messages/messages_es.yml @@ -93,7 +93,7 @@ show_no_email: '&2No tienes ningun E-Mail asociado en esta cuenta.' add_email: '&cPor favor agrega tu e-mail con: /email add tuEmail confirmarEmail' recovery_email: '&c¿Olvidaste tu contraseña? Por favor usa /email recovery ' # TODO change_password_expired: 'You cannot change your password using this command anymore.' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +email_cooldown_error: '&cEl correo ha sido enviado recientemente. Debes esperar %time antes de volver a enviar uno nuevo.' # Captcha usage_captcha: '&cUso: /captcha ' @@ -101,11 +101,11 @@ wrong_captcha: '&cCaptcha incorrecto, por favor usa: /captcha THE_CAPTCHA' valid_captcha: '&c¡ Captcha ingresado correctamente !' # Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +second: 'segundo' +seconds: 'segundos' +minute: 'minuto' +minutes: 'minutos' +hour: 'hora' +hours: 'horas' +day: 'día' +days: 'días' diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml index e79904d6..8b080da5 100644 --- a/src/main/resources/messages/messages_pt.yml +++ b/src/main/resources/messages/messages_pt.yml @@ -1,24 +1,24 @@ # Registration -reg_msg: '&cPor favor registe-se com "/register password confirmePassword"' -usage_reg: '&cUse: /register seu@email.com seu@email.com' -reg_only: '&fApenas jogadores registados! Visite http://example.com para se registar' -# TODO kicked_admin_registered: 'An admin just registered you; please log in again' +reg_msg: '&cPor favor registe-se com "/register "' +usage_reg: '&cUse: /register ' +reg_only: '&fApenas jogadores registados podem entrar no servidor! Visite http://example.com para se registar' +kicked_admin_registered: 'Um administrador registou-te, por favor entre novamente' registered: '&cRegistado com sucesso!' -reg_disabled: '&cRegito de novos utilizadores desactivado' +reg_disabled: '&cRegisto de novos utilizadores desactivado' user_regged: '&cUtilizador já registado' # Password errors on registration password_error: '&fAs passwords não coincidem' password_error_nick: '&cNão pode o usar seu nome como senha, por favor, escolha outra ...' password_error_unsafe: '&cA senha escolhida não é segura, por favor, escolha outra ...' -password_error_chars: '&4Sua senha contém caracteres ilegais. caracteres permitidos: REG_EX' -pass_len: '&fPassword demasiado curta' +password_error_chars: '&4Sua senha contém caracteres ilegais. Caracteres permitidos: REG_EX' +pass_len: '&fPassword demasiado curta ou longa! Por favor escolhe outra outra!' # Login -usage_log: '&cUse: /login password' +usage_log: '&cUse: /login ' wrong_pwd: '&cPassword errada!' login: '&bAutenticado com sucesso!' -login_msg: '&cIdentifique-se com "/login password"' +login_msg: '&cIdentifique-se com "/login "' timeout: '&fExcedeu o tempo para autenticação' # Errors @@ -26,11 +26,11 @@ unknown_user: '&cUsername não registado' denied_command: '&cPara utilizar este comando é necessário estar logado!' denied_chat: '&cPara usar o chat deve estar logado!' not_logged_in: '&cNão autenticado!' -# TODO tempban_max_logins: '&cYou have been temporarily banned for failing to log in too many times.' +tempban_max_logins: '&cVocê foi temporariamente banido por falhar muitas vezes o login.' # TODO: Missing tags %reg_names max_reg: '&cAtingiu o numero máximo de %reg_count contas registas, maximo de contas %max_acc' no_perm: '&cSem Permissões' -error: '&fOcorreu um erro; Por favor contacte um admin' +error: '&fOcorreu um erro; Por favor contacte um administrador' unsafe_spawn: '&fA sua localização na saída não é segura, será tele-portado para a Spawn' kick_forvip: '&cUm jogador VIP entrou no servidor cheio!' @@ -41,20 +41,20 @@ antibot_auto_disabled: '[AuthMe] AntiBotMod desactivado automaticamente após %m # Other messages unregistered: '&cRegisto eliminado com sucesso!' -# TODO accounts_owned_self: 'You own %count accounts:' -# TODO accounts_owned_other: 'The player %name has %count accounts:' +accounts_owned_self: 'Você possui %count contas:' +accounts_owned_other: 'O jogador %name possui %count contas:' two_factor_create: '&2O seu código secreto é o %code. Você pode verificá-lo a partir daqui %url' -# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! You have %count tries remaining.' +recovery_code_sent: 'O codigo para redefinir a senha foi enviado para o seu e-mail.' +recovery_code_incorrect: 'O codigo de recuperação está incorreto! Use "/email recovery [email]" para gerar um novo' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fA sua conta não foi ainda activada, verifique o seu email onde irá receber indicações para activação de conta. ' -usage_unreg: '&cUse: /unregister password' +usage_unreg: '&cUse: /unregister ' pwd_changed: '&cPassword alterada!' logged_in: '&cJá se encontra autenticado!' logout: '&cSaida com sucesso' reload: '&fConfiguração e base de dados foram recarregadas' -usage_changepassword: '&fUse: /changepassword passwordAntiga passwordNova' +usage_changepassword: '&fUse: /changepassword ' # Session messages invalid_session: '&fDados de sessão não correspondem. Por favor aguarde o fim da sessão' @@ -82,15 +82,15 @@ email_confirm: 'Confirme o seu email!' email_changed: 'Email alterado com sucesso!' email_send: 'Nova palavra-passe enviada para o seu email!' email_exists: '&cUm e-mail de recuperação já foi enviado! Pode descartá-lo e enviar um novo usando o comando abaixo:' -# TODO email_show: '&2Your current email address is: &f%email' -# TODO incomplete_email_settings: 'Error: not all required settings are set for sending emails. Please contact an admin.' +email_show: '&2O seu endereço de email atual é &f%email' +incomplete_email_settings: 'Erro: nem todas as definições necessarias para enviar email foram preenchidas. Por favor contate um administrador.' email_already_used: '&4O endereço de e-mail já está sendo usado' -# TODO email_send_failure: 'The email could not be sent. Please contact an administrator.' -# TODO show_no_email: '&2You currently don''t have email address associated with this account.' -add_email: '&cPor favor adicione o seu email com : /email add seuEmail confirmarSeuEmail' +email_send_failure: 'Não foi possivel enviar o email. Por favor contate um administrador.' +show_no_email: '&2Você atualmente não tem um endereço de email associado a essa conta.' +add_email: '&cPor favor adicione o seu email com : /email add ' recovery_email: '&cPerdeu a sua password? Para a recuperar escreva /email recovery ' # TODO change_password_expired: 'You cannot change your password using this command anymore.' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +email_cooldown_error: '&cUm email já foi enviado recentemente.Por favor, espere %time antes de enviar novamente' # Captcha usage_captcha: '&cPrecisa digitar um captcha, escreva: /captcha ' @@ -98,11 +98,11 @@ wrong_captcha: '&cCaptcha errado, por favor escreva: /captcha THE_CAPTCHA' valid_captcha: '&cO seu captcha é válido!' # Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +second: 'segundo' +seconds: 'segundos' +minute: 'minuto' +minutes: 'minutos' +hour: 'hora' +hours: 'horas' +day: 'dia' +days: 'dias' diff --git a/src/main/resources/messages/messages_zhcn.yml b/src/main/resources/messages/messages_zhcn.yml index 6fa481bb..c894d39b 100644 --- a/src/main/resources/messages/messages_zhcn.yml +++ b/src/main/resources/messages/messages_zhcn.yml @@ -91,7 +91,7 @@ show_no_email: '&2您当前并没有任何邮箱与该账号绑定' add_email: '&8[&6玩家系统&8] &c请输入“/email add <你的邮箱> <再输入一次以确认>”以把你的邮箱添加到此帐号' recovery_email: '&8[&6玩家系统&8] &c忘了你的密码?请输入:“/email recovery <你的邮箱>”' # TODO change_password_expired: 'You cannot change your password using this command anymore.' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +email_cooldown_error: '&c邮件已在几分钟前发送,您需要等待 %time 后才能再次请求发送' # Captcha usage_captcha: '&8[&6玩家系统&8] &c正确用法:/captcha ' @@ -99,11 +99,11 @@ wrong_captcha: '&8[&6玩家系统&8] &c错误的验证码,请输入:“/capt valid_captcha: '&8[&6玩家系统&8] &c你的验证码是有效的!' # Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +second: '秒' +seconds: '秒' +minute: '分钟' +minutes: '分钟' +hour: '小时' +hours: '小时' +day: '天' +days: '天' diff --git a/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java b/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java index 89132fcf..f11d7311 100644 --- a/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java +++ b/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java @@ -9,6 +9,7 @@ import fr.xephi.authme.command.CommandHandler; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.initialization.factory.FactoryDependencyHandler; +import fr.xephi.authme.initialization.factory.SingletonStoreDependencyHandler; import fr.xephi.authme.listener.BlockListener; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.Management; @@ -93,7 +94,7 @@ public class AuthMeInitializationTest { new Settings(dataFolder, mock(PropertyResource.class), null, buildConfigurationData()); Injector injector = new InjectorBuilder() - .addHandlers(new FactoryDependencyHandler()) + .addHandlers(new FactoryDependencyHandler(), new SingletonStoreDependencyHandler()) .addDefaultHandlers("fr.xephi.authme") .create(); injector.provide(DataFolder.class, dataFolder); diff --git a/src/test/java/fr/xephi/authme/ReflectionTestUtils.java b/src/test/java/fr/xephi/authme/ReflectionTestUtils.java index fdd716bd..bdae313a 100644 --- a/src/test/java/fr/xephi/authme/ReflectionTestUtils.java +++ b/src/test/java/fr/xephi/authme/ReflectionTestUtils.java @@ -25,7 +25,7 @@ public final class ReflectionTestUtils { */ public static void setField(Class clazz, T instance, String fieldName, Object value) { try { - Field field = getField(clazz, instance, fieldName); + Field field = getField(clazz, fieldName); field.set(instance, value); } catch (IllegalAccessException e) { throw new UnsupportedOperationException( @@ -34,24 +34,30 @@ public final class ReflectionTestUtils { } } - private static Field getField(Class clazz, T instance, String fieldName) { + private static Field getField(Class clazz, String fieldName) { try { Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); return field; } catch (NoSuchFieldException e) { - throw new UnsupportedOperationException(format("Could not get field '%s' for instance '%s' of class '%s'", - fieldName, instance, clazz.getName()), e); + throw new UnsupportedOperationException(format("Could not get field '%s' from class '%s'", + fieldName, clazz.getName()), e); } } @SuppressWarnings("unchecked") public static V getFieldValue(Class clazz, T instance, String fieldName) { - Field field = getField(clazz, instance, fieldName); + Field field = getField(clazz, fieldName); + return getFieldValue(field, instance); + } + + @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 '" + fieldName + "'", e); + throw new UnsupportedOperationException("Could not get value of field '" + field.getName() + "'", e); } } @@ -75,10 +81,11 @@ public final class ReflectionTestUtils { } } - public static Object invokeMethod(Method method, Object instance, Object... parameters) { + @SuppressWarnings("unchecked") + public static V invokeMethod(Method method, Object instance, Object... parameters) { method.setAccessible(true); try { - return method.invoke(instance, parameters); + return (V) method.invoke(instance, parameters); } catch (InvocationTargetException | IllegalAccessException e) { throw new UnsupportedOperationException("Could not invoke method '" + method + "'", e); } diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java index 91419912..5ee2161e 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java @@ -2,7 +2,6 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.security.PasswordSecurity; @@ -56,9 +55,6 @@ public class RegisterAdminCommandTest { @Mock private ValidationService validationService; - @Mock - private LimboCache limboCache; - @BeforeClass public static void setUpLogger() { TestHelper.setupLogger(); diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionConsistencyTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionConsistencyTest.java new file mode 100644 index 00000000..18ab9fd2 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionConsistencyTest.java @@ -0,0 +1,51 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.ClassCollector; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * Consistency tests for {@link DebugSection} implementors. + */ +public class DebugSectionConsistencyTest { + + private static List> debugClasses; + + @BeforeClass + public static void collectClasses() { + debugClasses = new ClassCollector("src/main/java", "fr/xephi/authme/command/executable/authme/debug") + .collectClasses(); + } + + @Test + public void shouldAllBePackagePrivate() { + for (Class clazz : debugClasses) { + if (clazz != DebugCommand.class) { + assertThat(clazz + " should be package-private", + Modifier.isPublic(clazz.getModifiers()), equalTo(false)); + } + } + } + + @Test + public void shouldHaveDifferentSubcommandName() throws IllegalAccessException, InstantiationException { + Set names = new HashSet<>(); + for (Class clazz : debugClasses) { + if (DebugSection.class.isAssignableFrom(clazz) && !clazz.isInterface()) { + DebugSection debugSection = (DebugSection) clazz.newInstance(); + if (!names.add(debugSection.getName())) { + fail("Encountered name '" + debugSection.getName() + "' a second time in " + clazz); + } + } + } + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtilsTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtilsTest.java new file mode 100644 index 00000000..e8443260 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtilsTest.java @@ -0,0 +1,69 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.ReflectionTestUtils; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.data.limbo.LimboService; +import org.bukkit.Location; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Test for {@link DebugSectionUtils}. + */ +public class DebugSectionUtilsTest { + + @Test + public void shouldFormatLocation() { + // given / when + String result = DebugSectionUtils.formatLocation(0.0, 10.248592, -18934.2349023, "Main"); + + // then + assertThat(result, equalTo("(0, 10.25, -18934.23) in 'Main'")); + } + + @Test + public void shouldHandleNullWorld() { + // given + Location location = new Location(null, 3.7777, 2.14156, 1); + + // when + String result = DebugSectionUtils.formatLocation(location); + + // then + assertThat(result, equalTo("(3.78, 2.14, 1) in 'null'")); + } + + @Test + public void shouldHandleNullLocation() { + // given / when / then + assertThat(DebugSectionUtils.formatLocation(null), equalTo("null")); + } + + @Test + public void shouldHaveHiddenConstructor() { + TestHelper.validateHasOnlyPrivateEmptyConstructor(DebugSectionUtils.class); + } + + @Test + public void shouldFetchMapInLimboService() { + // given + LimboService limboService = mock(LimboService.class); + Map limboMap = new HashMap<>(); + ReflectionTestUtils.setField(LimboService.class, limboService, "entries", limboMap); + + // when + Map map = DebugSectionUtils.applyToLimboPlayersMap(limboService, Function.identity()); + + // then + assertThat(map, sameInstance(limboMap)); + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionCheckerTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionCheckerTest.java new file mode 100644 index 00000000..9f7c6a92 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionCheckerTest.java @@ -0,0 +1,97 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.ClassCollector; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.permission.AdminPermission; +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.service.BukkitService; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.hamcrest.MockitoHamcrest.argThat; + +/** + * Test for {@link HasPermissionChecker}. + */ +@RunWith(MockitoJUnitRunner.class) +public class HasPermissionCheckerTest { + + @InjectMocks + private HasPermissionChecker hasPermissionChecker; + + @Mock + private PermissionsManager permissionsManager; + + @Mock + private BukkitService bukkitService; + + @Test + public void shouldListAllPermissionNodeClasses() { + // given + List> permissionClasses = + new ClassCollector(TestHelper.SOURCES_FOLDER, TestHelper.PROJECT_ROOT) + .collectClasses(PermissionNode.class).stream() + .filter(clz -> !clz.isInterface()) + .collect(Collectors.toList()); + + // when / then + assertThat(HasPermissionChecker.PERMISSION_NODE_CLASSES.containsAll(permissionClasses), equalTo(true)); + assertThat(HasPermissionChecker.PERMISSION_NODE_CLASSES, hasSize(permissionClasses.size())); + } + + @Test + public void shouldShowUsageInfo() { + // given + CommandSender sender = mock(CommandSender.class); + + // when + hasPermissionChecker.execute(sender, emptyList()); + + // then + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + verify(sender, atLeast(2)).sendMessage(msgCaptor.capture()); + assertThat( + msgCaptor.getAllValues().stream().anyMatch(msg -> msg.contains("/authme debug perm bobby my.perm.node")), + equalTo(true)); + } + + @Test + public void shouldShowSuccessfulTestWithRegularPlayer() { + // given + String name = "Chuck"; + Player player = mock(Player.class); + given(bukkitService.getPlayerExact(name)).willReturn(player); + PermissionNode permission = AdminPermission.CHANGE_EMAIL; + given(permissionsManager.hasPermission(player, permission)).willReturn(true); + CommandSender sender = mock(CommandSender.class); + + // when + hasPermissionChecker.execute(sender, asList(name, permission.getNode())); + + // then + verify(bukkitService).getPlayerExact(name); + verify(permissionsManager).hasPermission(player, permission); + verify(sender).sendMessage(argThat(containsString("Success: player '" + player.getName() + + "' has permission '" + permission.getNode() + "'"))); + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java index dd924005..bb331b28 100644 --- a/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java @@ -2,11 +2,9 @@ package fr.xephi.authme.command.executable.captcha; import fr.xephi.authme.data.CaptchaManager; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.service.CommonService; -import fr.xephi.authme.data.limbo.LimboCache; -import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.message.MessageKey; -import fr.xephi.authme.task.MessageTask; +import fr.xephi.authme.service.CommonService; import org.bukkit.entity.Player; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,7 +38,7 @@ public class CaptchaCommandTest { private CommonService commandService; @Mock - private LimboCache limboCache; + private LimboService limboService; @Test public void shouldDetectIfPlayerIsLoggedIn() { @@ -82,10 +80,6 @@ public class CaptchaCommandTest { given(captchaManager.isCaptchaRequired(name)).willReturn(true); String captchaCode = "3991"; given(captchaManager.checkCode(name, captchaCode)).willReturn(true); - MessageTask messageTask = mock(MessageTask.class); - LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboPlayer.getMessageTask()).willReturn(messageTask); - given(limboCache.getPlayerData(name)).willReturn(limboPlayer); // when command.executeCommand(player, Collections.singletonList(captchaCode)); @@ -96,7 +90,7 @@ public class CaptchaCommandTest { verifyNoMoreInteractions(captchaManager); verify(commandService).send(player, MessageKey.CAPTCHA_SUCCESS); verify(commandService).send(player, MessageKey.LOGIN_MESSAGE); - verify(messageTask).setMuted(false); + verify(limboService).unmuteMessageTask(player); verifyNoMoreInteractions(commandService); } diff --git a/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java index c87252e1..6224a606 100644 --- a/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java @@ -1,13 +1,17 @@ package fr.xephi.authme.command.executable.register; +import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; import fr.xephi.authme.process.register.RegisterSecondaryArgument; import fr.xephi.authme.process.register.RegistrationType; -import fr.xephi.authme.process.register.executors.RegistrationExecutor; -import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider; +import fr.xephi.authme.process.register.executors.EmailRegisterParams; +import fr.xephi.authme.process.register.executors.PasswordRegisterParams; +import fr.xephi.authme.process.register.executors.RegistrationMethod; +import fr.xephi.authme.process.register.executors.RegistrationParameters; +import fr.xephi.authme.process.register.executors.TwoFactorRegisterParams; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.ValidationService; @@ -16,6 +20,9 @@ import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -24,10 +31,16 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.Objects; import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -57,9 +70,6 @@ public class RegisterCommandTest { @Mock private ValidationService validationService; - @Mock - private RegistrationExecutorProvider registrationExecutorProvider; - @BeforeClass public static void setup() { TestHelper.setupLogger(); @@ -90,14 +100,13 @@ public class RegisterCommandTest { // given given(commonService.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.TWO_FACTOR); Player player = mock(Player.class); - RegistrationExecutor executor = mock(RegistrationExecutor.class); - given(registrationExecutorProvider.getTwoFactorRegisterExecutor(player)).willReturn(executor); // when command.executeCommand(player, Collections.emptyList()); // then - verify(management).performRegister(player, executor); + verify(management).performRegister(eq(RegistrationMethod.TWO_FACTOR_REGISTRATION), + argThat(isEqualTo(TwoFactorRegisterParams.of(player)))); verifyZeroInteractions(emailService); } @@ -208,8 +217,6 @@ public class RegisterCommandTest { given(commonService.getProperty(RegistrationSettings.REGISTER_SECOND_ARGUMENT)).willReturn(RegisterSecondaryArgument.CONFIRMATION); given(emailService.hasAllInformation()).willReturn(true); Player player = mock(Player.class); - RegistrationExecutor executor = mock(RegistrationExecutor.class); - given(registrationExecutorProvider.getEmailRegisterExecutor(player, playerMail)).willReturn(executor); // when command.executeCommand(player, Arrays.asList(playerMail, playerMail)); @@ -217,7 +224,8 @@ public class RegisterCommandTest { // then verify(validationService).validateEmail(playerMail); verify(emailService).hasAllInformation(); - verify(management).performRegister(player, executor); + verify(management).performRegister(eq(RegistrationMethod.EMAIL_REGISTRATION), + argThat(isEqualTo(EmailRegisterParams.of(player, playerMail)))); } @Test @@ -239,14 +247,13 @@ public class RegisterCommandTest { public void shouldPerformPasswordRegistration() { // given Player player = mock(Player.class); - RegistrationExecutor executor = mock(RegistrationExecutor.class); - given(registrationExecutorProvider.getPasswordRegisterExecutor(player, "myPass", null)).willReturn(executor); // when command.executeCommand(player, Collections.singletonList("myPass")); // then - verify(management).performRegister(player, executor); + verify(management).performRegister(eq(RegistrationMethod.PASSWORD_REGISTRATION), + argThat(isEqualTo(PasswordRegisterParams.of(player, "myPass", null)))); } @Test @@ -257,15 +264,14 @@ public class RegisterCommandTest { String email = "email@example.org"; given(validationService.validateEmail(email)).willReturn(true); Player player = mock(Player.class); - RegistrationExecutor executor = mock(RegistrationExecutor.class); - given(registrationExecutorProvider.getPasswordRegisterExecutor(player, "myPass", email)).willReturn(executor); // when command.executeCommand(player, Arrays.asList("myPass", email)); // then verify(validationService).validateEmail(email); - verify(management).performRegister(player, executor); + verify(management).performRegister(eq(RegistrationMethod.PASSWORD_REGISTRATION), + argThat(isEqualTo(PasswordRegisterParams.of(player, "myPass", email)))); } @Test @@ -292,14 +298,64 @@ public class RegisterCommandTest { given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationType.PASSWORD); given(commonService.getProperty(RegistrationSettings.REGISTER_SECOND_ARGUMENT)).willReturn(RegisterSecondaryArgument.EMAIL_OPTIONAL); Player player = mock(Player.class); - RegistrationExecutor executor = mock(RegistrationExecutor.class); - given(registrationExecutorProvider.getPasswordRegisterExecutor(eq(player), anyString(), eq(null))).willReturn(executor); // when command.executeCommand(player, Collections.singletonList("myPass")); // then - verify(registrationExecutorProvider).getPasswordRegisterExecutor(player, "myPass", null); - verify(management).performRegister(player, executor); + verify(management).performRegister(eq(RegistrationMethod.PASSWORD_REGISTRATION), + argThat(isEqualTo(PasswordRegisterParams.of(player, "myPass", null)))); + } + + + // TODO ljacqu 20170317: Document and extract as util + + private static

    Matcher

    isEqualTo(P expected) { + return new TypeSafeMatcher

    () { + @Override + protected boolean matchesSafely(RegistrationParameters item) { + assertAreParamsEqual(expected, item); + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("parameters " + expected); + } + }; + } + + private static void assertAreParamsEqual(RegistrationParameters lhs, RegistrationParameters rhs) { + if (lhs.getClass() != rhs.getClass()) { + fail("Params classes don't match, got " + lhs.getClass().getSimpleName() + + " and " + rhs.getClass().getSimpleName()); + } + + List fieldsToCheck = getFields(lhs); + for (Field field : fieldsToCheck) { + Object lhsValue = ReflectionTestUtils.getFieldValue(field, lhs); + Object rhsValue = ReflectionTestUtils.getFieldValue(field, rhs); + if (!Objects.equals(lhsValue, rhsValue)) { + fail("Field '" + field.getName() + "' does not have same value: '" + + lhsValue + "' vs. '" + rhsValue + "'"); + } + } + } + + private static List getFields(RegistrationParameters params) { + List fields = new ArrayList<>(); + Class currentClass = params.getClass(); + while (currentClass != null) { + for (Field f : currentClass.getDeclaredFields()) { + if (!Modifier.isStatic(f.getModifiers())) { + fields.add(f); + } + } + if (currentClass == RegistrationParameters.class) { + break; + } + currentClass = currentClass.getSuperclass(); + } + return fields; } } diff --git a/src/test/java/fr/xephi/authme/data/limbo/AllowFlightRestoreTypeTest.java b/src/test/java/fr/xephi/authme/data/limbo/AllowFlightRestoreTypeTest.java new file mode 100644 index 00000000..8c22cc75 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/AllowFlightRestoreTypeTest.java @@ -0,0 +1,72 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.entity.Player; +import org.junit.Test; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link AllowFlightRestoreType}. + */ +public class AllowFlightRestoreTypeTest { + + @Test + public void shouldRestoreValue() { + // given + LimboPlayer limboWithFly = newLimboWithAllowFlight(true); + LimboPlayer limboWithoutFly = newLimboWithAllowFlight(false); + Player player1 = mock(Player.class); + Player player2 = mock(Player.class); + + // when + AllowFlightRestoreType.RESTORE.restoreAllowFlight(player1, limboWithFly); + AllowFlightRestoreType.RESTORE.restoreAllowFlight(player2, limboWithoutFly); + + // then + verify(player1).setAllowFlight(true); + verify(player2).setAllowFlight(false); + } + + @Test + public void shouldEnableFlight() { + // given + LimboPlayer limboWithFly = newLimboWithAllowFlight(true); + LimboPlayer limboWithoutFly = newLimboWithAllowFlight(false); + Player player1 = mock(Player.class); + Player player2 = mock(Player.class); + + // when + AllowFlightRestoreType.ENABLE.restoreAllowFlight(player1, limboWithFly); + AllowFlightRestoreType.ENABLE.restoreAllowFlight(player2, limboWithoutFly); + + // then + verify(player1).setAllowFlight(true); + verify(player2).setAllowFlight(true); + } + + + @Test + public void shouldDisableFlight() { + // given + LimboPlayer limboWithFly = newLimboWithAllowFlight(true); + LimboPlayer limboWithoutFly = newLimboWithAllowFlight(false); + Player player1 = mock(Player.class); + Player player2 = mock(Player.class); + + // when + AllowFlightRestoreType.DISABLE.restoreAllowFlight(player1, limboWithFly); + AllowFlightRestoreType.DISABLE.restoreAllowFlight(player2, limboWithoutFly); + + // then + verify(player1).setAllowFlight(false); + verify(player2).setAllowFlight(false); + } + + private static LimboPlayer newLimboWithAllowFlight(boolean allowFlight) { + LimboPlayer limbo = mock(LimboPlayer.class); + given(limbo.isCanFly()).willReturn(allowFlight); + return limbo; + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java deleted file mode 100644 index 8d307c25..00000000 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java +++ /dev/null @@ -1,215 +0,0 @@ -package fr.xephi.authme.data.limbo; - -import fr.xephi.authme.ReflectionTestUtils; -import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.settings.SpawnLoader; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -import java.util.Map; - -import static org.hamcrest.Matchers.aMapWithSize; -import static org.hamcrest.Matchers.anEmptyMap; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * Test for {@link LimboCache}. - */ -@RunWith(MockitoJUnitRunner.class) -public class LimboCacheTest { - - @InjectMocks - private LimboCache limboCache; - - @Mock - private PermissionsManager permissionsManager; - - @Mock - private SpawnLoader spawnLoader; - - @Mock - private LimboPlayerStorage limboPlayerStorage; - - @Test - public void shouldAddPlayerData() { - // given - Player player = mock(Player.class); - String name = "Bobby"; - given(player.getName()).willReturn(name); - Location location = mock(Location.class); - given(spawnLoader.getPlayerLocationOrSpawn(player)).willReturn(location); - given(player.isOp()).willReturn(true); - float walkSpeed = 2.1f; - given(player.getWalkSpeed()).willReturn(walkSpeed); - given(player.getAllowFlight()).willReturn(true); - float flySpeed = 3.0f; - given(player.getFlySpeed()).willReturn(flySpeed); - given(permissionsManager.hasGroupSupport()).willReturn(true); - String group = "test-group"; - given(permissionsManager.getPrimaryGroup(player)).willReturn(group); - given(limboPlayerStorage.hasData(player)).willReturn(false); - - // when - limboCache.addPlayerData(player); - - // then - LimboPlayer limboPlayer = limboCache.getPlayerData(name); - assertThat(limboPlayer.getLocation(), equalTo(location)); - assertThat(limboPlayer.isOperator(), equalTo(true)); - assertThat(limboPlayer.getWalkSpeed(), equalTo(walkSpeed)); - assertThat(limboPlayer.isCanFly(), equalTo(true)); - assertThat(limboPlayer.getFlySpeed(), equalTo(flySpeed)); - assertThat(limboPlayer.getGroup(), equalTo(group)); - } - - @Test - public void shouldGetPlayerDataFromDisk() { - // given - String name = "player01"; - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - given(limboPlayerStorage.hasData(player)).willReturn(true); - LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboPlayerStorage.readData(player)).willReturn(limboPlayer); - float walkSpeed = 2.4f; - given(limboPlayer.getWalkSpeed()).willReturn(walkSpeed); - given(limboPlayer.isCanFly()).willReturn(true); - float flySpeed = 1.0f; - given(limboPlayer.getFlySpeed()).willReturn(flySpeed); - String group = "primary-group"; - given(limboPlayer.getGroup()).willReturn(group); - - // when - limboCache.addPlayerData(player); - - // then - LimboPlayer result = limboCache.getPlayerData(name); - assertThat(result.getWalkSpeed(), equalTo(walkSpeed)); - assertThat(result.isCanFly(), equalTo(true)); - assertThat(result.getFlySpeed(), equalTo(flySpeed)); - assertThat(result.getGroup(), equalTo(group)); - } - - @Test - public void shouldRestorePlayerInfo() { - // given - String name = "Champ"; - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboPlayer.isOperator()).willReturn(true); - float walkSpeed = 2.4f; - given(limboPlayer.getWalkSpeed()).willReturn(walkSpeed); - given(limboPlayer.isCanFly()).willReturn(true); - float flySpeed = 1.0f; - given(limboPlayer.getFlySpeed()).willReturn(flySpeed); - getCache().put(name.toLowerCase(), limboPlayer); - - // when - limboCache.restoreData(player); - - // then - verify(player).setOp(true); - verify(player).setWalkSpeed(walkSpeed); - verify(player).setAllowFlight(true); - verify(player).setFlySpeed(flySpeed); - verify(limboPlayer).clearTasks(); - } - - @Test - public void shouldResetPlayerSpeed() { - // given - String name = "Champ"; - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboPlayer.isOperator()).willReturn(true); - given(limboPlayer.getWalkSpeed()).willReturn(0f); - given(limboPlayer.isCanFly()).willReturn(true); - given(limboPlayer.getFlySpeed()).willReturn(0f); - getCache().put(name.toLowerCase(), limboPlayer); - - // when - limboCache.restoreData(player); - - // then - verify(player).setWalkSpeed(LimboPlayer.DEFAULT_WALK_SPEED); - verify(player).setFlySpeed(LimboPlayer.DEFAULT_FLY_SPEED); - } - - @Test - public void shouldNotInteractWithPlayerIfNoDataAvailable() { - // given - String name = "player"; - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - - // when - limboCache.restoreData(player); - - // then - verify(player).getName(); - verifyNoMoreInteractions(player); - } - - @Test - public void shouldRemoveAndClearTasks() { - // given - LimboPlayer limboPlayer = mock(LimboPlayer.class); - String name = "abcdef"; - getCache().put(name, limboPlayer); - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - - // when - limboCache.removeFromCache(player); - - // then - assertThat(getCache(), anEmptyMap()); - verify(limboPlayer).clearTasks(); - } - - @Test - public void shouldDeleteFromCacheAndStorage() { - // given - LimboPlayer limboPlayer = mock(LimboPlayer.class); - String name = "SomeName"; - getCache().put(name.toLowerCase(), limboPlayer); - getCache().put("othername", mock(LimboPlayer.class)); - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - - // when - limboCache.deletePlayerData(player); - - // then - assertThat(getCache(), aMapWithSize(1)); - verify(limboPlayer).clearTasks(); - verify(limboPlayerStorage).removeData(player); - } - - @Test - public void shouldReturnIfHasData() { - // given - String name = "tester"; - getCache().put(name, mock(LimboPlayer.class)); - - // when / then - assertThat(limboCache.hasPlayerData(name), equalTo(true)); - assertThat(limboCache.hasPlayerData("someone_else"), equalTo(false)); - } - - private Map getCache() { - return ReflectionTestUtils.getFieldValue(LimboCache.class, limboCache, "cache"); - } -} diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerMatchers.java b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerMatchers.java new file mode 100644 index 00000000..4c4793a3 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerMatchers.java @@ -0,0 +1,112 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.Location; +import org.bukkit.World; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import static java.lang.String.format; + +/** + * Contains matchers for LimboPlayer. + */ +public final class LimboPlayerMatchers { + + private LimboPlayerMatchers() { + } + + public static Matcher isLimbo(LimboPlayer limbo) { + return isLimbo(limbo.isOperator(), limbo.getGroup(), limbo.isCanFly(), + limbo.getWalkSpeed(), limbo.getFlySpeed()); + } + + public static Matcher isLimbo(boolean isOp, String group, boolean canFly, + float walkSpeed, float flySpeed) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(LimboPlayer item) { + return item.isOperator() == isOp && item.getGroup().equals(group) && item.isCanFly() == canFly + && walkSpeed == item.getWalkSpeed() && flySpeed == item.getFlySpeed(); + } + + @Override + public void describeTo(Description description) { + description.appendText(format("Limbo with isOp=%s, group=%s, canFly=%s, walkSpeed=%f, flySpeed=%f", + isOp, group, canFly, walkSpeed, flySpeed)); + } + + @Override + public void describeMismatchSafely(LimboPlayer item, Description description) { + description.appendText(format("Limbo with isOp=%s, group=%s, canFly=%s, walkSpeed=%f, flySpeed=%f", + item.isOperator(), item.getGroup(), item.isCanFly(), item.getWalkSpeed(), item.getFlySpeed())); + } + }; + } + + public static Matcher hasLocation(String world, double x, double y, double z) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(LimboPlayer item) { + Location location = item.getLocation(); + return location.getWorld().getName().equals(world) + && location.getX() == x && location.getY() == y && location.getZ() == z; + } + + @Override + public void describeTo(Description description) { + description.appendText(format("Limbo with location: world=%s, x=%f, y=%f, z=%f", + world, x, y, z)); + } + + @Override + public void describeMismatchSafely(LimboPlayer item, Description description) { + Location location = item.getLocation(); + if (location == null) { + description.appendText("Limbo with location = null"); + } else { + description.appendText(format("Limbo with location: world=%s, x=%f, y=%f, z=%f", + location.getWorld().getName(), location.getX(), location.getY(), location.getZ())); + } + } + }; + } + + public static Matcher hasLocation(World world, double x, double y, double z) { + return hasLocation(world.getName(), x, y, z); + } + + public static Matcher hasLocation(String world, double x, double y, double z, float yaw, float pitch) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(LimboPlayer item) { + Location location = item.getLocation(); + return hasLocation(location.getWorld(), location.getX(), location.getY(), location.getZ()).matches(item) + && location.getYaw() == yaw && location.getPitch() == pitch; + } + + @Override + public void describeTo(Description description) { + description.appendText(format("Limbo with location: world=%s, x=%f, y=%f, z=%f, yaw=%f, pitch=%f", + world, x, y, z, yaw, pitch)); + } + + @Override + public void describeMismatchSafely(LimboPlayer item, Description description) { + Location location = item.getLocation(); + if (location == null) { + description.appendText("Limbo with location = null"); + } else { + description.appendText(format("Limbo with location: world=%s, x=%f, y=%f, z=%f, yaw=%f, pitch=%f", + location.getWorld().getName(), location.getX(), location.getY(), location.getZ(), + location.getYaw(), location.getPitch())); + } + } + }; + } + + public static Matcher hasLocation(Location location) { + return hasLocation(location.getWorld().getName(), location.getX(), location.getY(), location.getZ(), + location.getYaw(), location.getPitch()); + } +} diff --git a/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManagerTest.java similarity index 65% rename from src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java rename to src/test/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManagerTest.java index 51110d91..8d3b6e85 100644 --- a/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManagerTest.java @@ -1,15 +1,15 @@ -package fr.xephi.authme.task; +package fr.xephi.authme.data.limbo; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; -import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.task.MessageTask; +import fr.xephi.authme.task.TimeoutTask; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; import org.junit.BeforeClass; @@ -20,6 +20,11 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -46,9 +51,6 @@ public class LimboPlayerTaskManagerTest { @Mock private BukkitService bukkitService; - @Mock - private LimboCache limboCache; - @Mock private PlayerCache playerCache; @@ -60,16 +62,15 @@ public class LimboPlayerTaskManagerTest { @Test public void shouldRegisterMessageTask() { // given - String name = "bobby"; + Player player = mock(Player.class); LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboCache.getPlayerData(name)).willReturn(limboPlayer); MessageKey key = MessageKey.REGISTER_MESSAGE; given(messages.retrieve(key)).willReturn(new String[]{"Please register!"}); int interval = 12; given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(interval); // when - limboPlayerTaskManager.registerMessageTask(name, false); + limboPlayerTaskManager.registerMessageTask(player, limboPlayer, false); // then verify(limboPlayer).setMessageTask(any(MessageTask.class)); @@ -78,32 +79,16 @@ public class LimboPlayerTaskManagerTest { any(MessageTask.class), eq(2L * TICKS_PER_SECOND), eq((long) interval * TICKS_PER_SECOND)); } - @Test - public void shouldNotScheduleTaskForMissingLimboPlayer() { - // given - String name = "ghost"; - given(limboCache.getPlayerData(name)).willReturn(null); - given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(5); - - // when - limboPlayerTaskManager.registerMessageTask(name, true); - - // then - verify(limboCache).getPlayerData(name); - verifyZeroInteractions(bukkitService); - verifyZeroInteractions(messages); - } - @Test public void shouldNotScheduleTaskForZeroAsInterval() { // given - String name = "Tester1"; + Player player = mock(Player.class); LimboPlayer limboPlayer = mock(LimboPlayer.class); given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(0); // when - limboPlayerTaskManager.registerMessageTask(name, true); + limboPlayerTaskManager.registerMessageTask(player, limboPlayer, true); // then verifyZeroInteractions(limboPlayer, bukkitService); @@ -112,19 +97,18 @@ public class LimboPlayerTaskManagerTest { @Test public void shouldCancelExistingMessageTask() { // given - LimboPlayer limboPlayer = mock(LimboPlayer.class); + Player player = mock(Player.class); + LimboPlayer limboPlayer = new LimboPlayer(null, true, "grp", false, 0.1f, 0.0f); MessageTask existingMessageTask = mock(MessageTask.class); - given(limboPlayer.getMessageTask()).willReturn(existingMessageTask); - - String name = "bobby"; - given(limboCache.getPlayerData(name)).willReturn(limboPlayer); + limboPlayer.setMessageTask(existingMessageTask); given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(8); // when - limboPlayerTaskManager.registerMessageTask(name, false); + limboPlayerTaskManager.registerMessageTask(player, limboPlayer, false); // then - verify(limboPlayer).setMessageTask(any(MessageTask.class)); + assertThat(limboPlayer.getMessageTask(), not(nullValue())); + assertThat(limboPlayer.getMessageTask(), not(sameInstance(existingMessageTask))); verify(messages).retrieve(MessageKey.REGISTER_MESSAGE); verify(existingMessageTask).cancel(); } @@ -132,17 +116,14 @@ public class LimboPlayerTaskManagerTest { @Test public void shouldRegisterTimeoutTask() { // given - String name = "l33tPlayer"; Player player = mock(Player.class); - given(player.getName()).willReturn(name); LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboCache.getPlayerData(name)).willReturn(limboPlayer); given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(30); BukkitTask bukkitTask = mock(BukkitTask.class); given(bukkitService.runTaskLater(any(TimeoutTask.class), anyLong())).willReturn(bukkitTask); // when - limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerTimeoutTask(player, limboPlayer); // then verify(limboPlayer).setTimeoutTask(bukkitTask); @@ -150,22 +131,6 @@ public class LimboPlayerTaskManagerTest { verify(messages).retrieveSingle(MessageKey.LOGIN_TIMEOUT_ERROR); } - @Test - public void shouldNotRegisterTimeoutTaskForMissingLimboPlayer() { - // given - String name = "Phantom_"; - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - given(limboCache.getPlayerData(name)).willReturn(null); - given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(27); - - // when - limboPlayerTaskManager.registerTimeoutTask(player); - - // then - verifyZeroInteractions(bukkitService, messages); - } - @Test public void shouldNotRegisterTimeoutTaskForZeroTimeout() { // given @@ -174,7 +139,7 @@ public class LimboPlayerTaskManagerTest { given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(0); // when - limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerTimeoutTask(player, limboPlayer); // then verifyZeroInteractions(limboPlayer, bukkitService); @@ -183,23 +148,20 @@ public class LimboPlayerTaskManagerTest { @Test public void shouldCancelExistingTimeoutTask() { // given - String name = "l33tPlayer"; Player player = mock(Player.class); - given(player.getName()).willReturn(name); - LimboPlayer limboPlayer = mock(LimboPlayer.class); + LimboPlayer limboPlayer = new LimboPlayer(null, false, "", true, 0.3f, 0.1f); BukkitTask existingTask = mock(BukkitTask.class); - given(limboPlayer.getTimeoutTask()).willReturn(existingTask); - given(limboCache.getPlayerData(name)).willReturn(limboPlayer); + limboPlayer.setTimeoutTask(existingTask); given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(18); BukkitTask bukkitTask = mock(BukkitTask.class); given(bukkitService.runTaskLater(any(TimeoutTask.class), anyLong())).willReturn(bukkitTask); // when - limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerTimeoutTask(player, limboPlayer); // then verify(existingTask).cancel(); - verify(limboPlayer).setTimeoutTask(bukkitTask); + assertThat(limboPlayer.getTimeoutTask(), equalTo(bukkitTask)); verify(bukkitService).runTaskLater(any(TimeoutTask.class), eq(360L)); // 18 * TICKS_PER_SECOND verify(messages).retrieveSingle(MessageKey.LOGIN_TIMEOUT_ERROR); } diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java new file mode 100644 index 00000000..43f11033 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java @@ -0,0 +1,81 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.Location; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link LimboServiceHelper}. + *

    + * Note: some methods are tested directly where they are used via {@link LimboServiceTest}. + */ +@RunWith(MockitoJUnitRunner.class) +public class LimboServiceHelperTest { + + @InjectMocks + private LimboServiceHelper limboServiceHelper; + + @Test + public void shouldMergeLimboPlayers() { + // given + Location newLocation = mock(Location.class); + LimboPlayer newLimbo = new LimboPlayer(newLocation, false, "grp-new", false, 0.0f, 0.0f); + Location oldLocation = mock(Location.class); + LimboPlayer oldLimbo = new LimboPlayer(oldLocation, true, "grp-old", true, 0.1f, 0.8f); + + // when + LimboPlayer result = limboServiceHelper.merge(newLimbo, oldLimbo); + + // then + assertThat(result.getLocation(), equalTo(oldLocation)); + assertThat(result.isOperator(), equalTo(true)); + assertThat(result.getGroup(), equalTo("grp-old")); + assertThat(result.isCanFly(), equalTo(true)); + assertThat(result.getWalkSpeed(), equalTo(0.1f)); + assertThat(result.getFlySpeed(), equalTo(0.8f)); + } + + @Test + public void shouldFallBackToNewLimboForMissingData() { + // given + Location newLocation = mock(Location.class); + LimboPlayer newLimbo = new LimboPlayer(newLocation, false, "grp-new", true, 0.3f, 0.0f); + LimboPlayer oldLimbo = new LimboPlayer(null, false, "", false, 0.1f, 0.1f); + + // when + LimboPlayer result = limboServiceHelper.merge(newLimbo, oldLimbo); + + // then + assertThat(result.getLocation(), equalTo(newLocation)); + assertThat(result.isOperator(), equalTo(false)); + assertThat(result.getGroup(), equalTo("grp-new")); + assertThat(result.isCanFly(), equalTo(true)); + assertThat(result.getWalkSpeed(), equalTo(0.3f)); + assertThat(result.getFlySpeed(), equalTo(0.1f)); + } + + @Test + public void shouldHandleNullInputs() { + // given + LimboPlayer limbo = mock(LimboPlayer.class); + + // when + LimboPlayer result1 = limboServiceHelper.merge(limbo, null); + LimboPlayer result2 = limboServiceHelper.merge(null, limbo); + LimboPlayer result3 = limboServiceHelper.merge(null, null); + + // then + verifyZeroInteractions(limbo); + assertThat(result1, equalTo(limbo)); + assertThat(result2, equalTo(limbo)); + assertThat(result3, nullValue()); + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java new file mode 100644 index 00000000..807024cd --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java @@ -0,0 +1,238 @@ +package fr.xephi.authme.data.limbo; + +import ch.jalu.injector.testing.DelayedInjectionRunner; +import ch.jalu.injector.testing.InjectDelayed; +import fr.xephi.authme.ReflectionTestUtils; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.limbo.persistence.LimboPersistence; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.SpawnLoader; +import fr.xephi.authme.settings.properties.LimboSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; + +import java.util.Map; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link LimboService}, and {@link LimboServiceHelper}. + */ +@RunWith(DelayedInjectionRunner.class) +public class LimboServiceTest { + + @InjectDelayed + private LimboService limboService; + + @InjectDelayed + private LimboServiceHelper limboServiceHelper; + + @Mock + private SpawnLoader spawnLoader; + + @Mock + private PermissionsManager permissionsManager; + + @Mock + private Settings settings; + + @Mock + private LimboPlayerTaskManager taskManager; + + @Mock + private LimboPersistence limboPersistence; + + @BeforeClass + public static void initLogger() { + TestHelper.setupLogger(); + } + + @Before + public void mockSettings() { + given(settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)).willReturn(false); + given(settings.getProperty(RestrictionSettings.REMOVE_SPEED)).willReturn(true); + } + + @Test + public void shouldCreateLimboPlayer() { + // given + Player player = newPlayer("Bobby", true, 0.3f, false, 0.2f); + Location playerLoc = mock(Location.class); + given(spawnLoader.getPlayerLocationOrSpawn(player)).willReturn(playerLoc); + given(permissionsManager.hasGroupSupport()).willReturn(true); + given(permissionsManager.getPrimaryGroup(player)).willReturn("permgrwp"); + + // when + limboService.createLimboPlayer(player, true); + + // then + verify(taskManager).registerMessageTask(eq(player), any(LimboPlayer.class), eq(true)); + verify(taskManager).registerTimeoutTask(eq(player), any(LimboPlayer.class)); + verify(player).setAllowFlight(false); + verify(player).setFlySpeed(0.0f); + verify(player).setWalkSpeed(0.0f); + + assertThat(limboService.hasLimboPlayer("Bobby"), equalTo(true)); + LimboPlayer limbo = limboService.getLimboPlayer("Bobby"); + assertThat(limbo, not(nullValue())); + assertThat(limbo.isOperator(), equalTo(true)); + assertThat(limbo.getWalkSpeed(), equalTo(0.3f)); + assertThat(limbo.isCanFly(), equalTo(false)); + assertThat(limbo.getFlySpeed(), equalTo(0.2f)); + assertThat(limbo.getLocation(), equalTo(playerLoc)); + assertThat(limbo.getGroup(), equalTo("permgrwp")); + } + + @Test + public void shouldNotKeepOpStatusForUnregisteredPlayer() { + // given + Player player = newPlayer("CharleS", true, 0.1f, true, 0.4f); + Location playerLoc = mock(Location.class); + given(spawnLoader.getPlayerLocationOrSpawn(player)).willReturn(playerLoc); + given(permissionsManager.hasGroupSupport()).willReturn(false); + + // when + limboService.createLimboPlayer(player, false); + + // then + verify(taskManager).registerMessageTask(eq(player), any(LimboPlayer.class), eq(false)); + verify(taskManager).registerTimeoutTask(eq(player), any(LimboPlayer.class)); + verify(permissionsManager, only()).hasGroupSupport(); + verify(player).setAllowFlight(false); + verify(player).setFlySpeed(0.0f); + verify(player).setWalkSpeed(0.0f); + + LimboPlayer limbo = limboService.getLimboPlayer("charles"); + assertThat(limbo, not(nullValue())); + assertThat(limbo.isOperator(), equalTo(false)); + assertThat(limbo.getWalkSpeed(), equalTo(0.1f)); + assertThat(limbo.isCanFly(), equalTo(true)); + assertThat(limbo.getFlySpeed(), equalTo(0.4f)); + assertThat(limbo.getLocation(), equalTo(playerLoc)); + assertThat(limbo.getGroup(), equalTo("")); + } + + @Test + public void shouldClearTasksOnAlreadyExistingLimbo() { + // given + LimboPlayer existingLimbo = mock(LimboPlayer.class); + getLimboMap().put("carlos", existingLimbo); + Player player = newPlayer("Carlos"); + + // when + limboService.createLimboPlayer(player, false); + + // then + verify(existingLimbo).clearTasks(); + LimboPlayer newLimbo = limboService.getLimboPlayer("Carlos"); + assertThat(newLimbo, not(nullValue())); + assertThat(newLimbo, not(sameInstance(existingLimbo))); + } + + @Test + public void shouldRestoreData() { + // given + LimboPlayer limbo = Mockito.spy(convertToLimboPlayer( + newPlayer("John", true, 0.4f, false, 0.0f), null, "")); + getLimboMap().put("john", limbo); + Player player = newPlayer("John", false, 0.2f, false, 0.7f); + + given(settings.getProperty(LimboSettings.RESTORE_ALLOW_FLIGHT)).willReturn(AllowFlightRestoreType.ENABLE); + given(settings.getProperty(LimboSettings.RESTORE_WALK_SPEED)).willReturn(WalkFlySpeedRestoreType.RESTORE); + given(settings.getProperty(LimboSettings.RESTORE_FLY_SPEED)).willReturn(WalkFlySpeedRestoreType.RESTORE_NO_ZERO); + + // when + limboService.restoreData(player); + + // then + verify(player).setOp(true); + verify(player).setWalkSpeed(0.4f); + verify(player).setAllowFlight(true); + verify(player).setFlySpeed(LimboPlayer.DEFAULT_FLY_SPEED); + verify(limbo).clearTasks(); + assertThat(limboService.hasLimboPlayer("John"), equalTo(false)); + } + + @Test + public void shouldHandleMissingLimboPlayerWhileRestoring() { + // given + Player player = newPlayer("Test"); + + // when + limboService.restoreData(player); + + // then + verify(player, only()).getName(); + } + + @Test + public void shouldReplaceTasks() { + // given + LimboPlayer limbo = mock(LimboPlayer.class); + getLimboMap().put("jeff", limbo); + Player player = newPlayer("JEFF"); + + + // when + limboService.replaceTasksAfterRegistration(player); + + // then + verify(taskManager).registerTimeoutTask(player, limbo); + verify(taskManager).registerMessageTask(player, limbo, true); + } + + @Test + public void shouldHandleMissingLimboForReplaceTasks() { + // given + Player player = newPlayer("ghost"); + + // when + limboService.replaceTasksAfterRegistration(player); + + // then + verifyZeroInteractions(taskManager); + } + + private static Player newPlayer(String name) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + return player; + } + + private static Player newPlayer(String name, boolean isOp, float walkSpeed, boolean canFly, float flySpeed) { + Player player = newPlayer(name); + given(player.isOp()).willReturn(isOp); + given(player.getWalkSpeed()).willReturn(walkSpeed); + given(player.getAllowFlight()).willReturn(canFly); + given(player.getFlySpeed()).willReturn(flySpeed); + return player; + } + + private static LimboPlayer convertToLimboPlayer(Player player, Location location, String group) { + return new LimboPlayer(location, player.isOp(), group, player.getAllowFlight(), + player.getWalkSpeed(), player.getFlySpeed()); + } + + private Map getLimboMap() { + return ReflectionTestUtils.getFieldValue(LimboService.class, limboService, "entries"); + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreTypeTest.java b/src/test/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreTypeTest.java new file mode 100644 index 00000000..f8aa2719 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreTypeTest.java @@ -0,0 +1,108 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.entity.Player; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static fr.xephi.authme.data.limbo.LimboPlayer.DEFAULT_FLY_SPEED; +import static fr.xephi.authme.data.limbo.LimboPlayer.DEFAULT_WALK_SPEED; +import static fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType.DEFAULT; +import static fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType.MAX_RESTORE; +import static fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType.RESTORE; +import static fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType.RESTORE_NO_ZERO; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link WalkFlySpeedRestoreType}. + */ +@RunWith(Parameterized.class) +public class WalkFlySpeedRestoreTypeTest { + + private final TestParameters parameters; + + public WalkFlySpeedRestoreTypeTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void shouldRestoreToExpectedValue() { + // given + LimboPlayer limbo = mock(LimboPlayer.class); + given(limbo.getWalkSpeed()).willReturn(parameters.givenLimboWalkSpeed); + given(limbo.getFlySpeed()).willReturn(parameters.givenLimboFlySpeed); + + Player player = mock(Player.class); + given(player.getWalkSpeed()).willReturn(parameters.givenPlayerWalkSpeed); + given(player.getFlySpeed()).willReturn(parameters.givenPlayerFlySpeed); + + // when + parameters.testedType.restoreWalkSpeed(player, limbo); + parameters.testedType.restoreFlySpeed(player, limbo); + + // then + verify(player).setWalkSpeed(parameters.expectedWalkSpeed); + verify(player).setFlySpeed(parameters.expectedFlySpeed); + } + + @Parameterized.Parameters(name = "{0}") + public static List buildParams() { + List parameters = Arrays.asList( + create(RESTORE).withLimbo(0.1f, 0.4f).withPlayer(0.3f, 0.9f).expect(0.1f, 0.4f), + create(RESTORE).withLimbo(0.9f, 0.2f).withPlayer(0.3f, 0.0f).expect(0.9f, 0.2f), + create(MAX_RESTORE).withLimbo(0.3f, 0.8f).withPlayer(0.5f, 0.2f).expect(0.5f, 0.8f), + create(MAX_RESTORE).withLimbo(0.4f, 0.2f).withPlayer(0.1f, 0.4f).expect(0.4f, 0.4f), + create(RESTORE_NO_ZERO).withLimbo(0.1f, 0.2f).withPlayer(0.5f, 0.1f).expect(0.1f, 0.2f), + create(RESTORE_NO_ZERO).withLimbo(0.0f, 0.005f).withPlayer(0.4f, 0.8f).expect(DEFAULT_WALK_SPEED, DEFAULT_FLY_SPEED), + create(DEFAULT).withLimbo(0.1f, 0.7f).withPlayer(0.4f, 0.0f).expect(DEFAULT_WALK_SPEED, DEFAULT_FLY_SPEED) + ); + + // Convert List to List + return parameters.stream().map(p -> new Object[]{p}).collect(Collectors.toList()); + } + + private static TestParameters create(WalkFlySpeedRestoreType testedType) { + TestParameters params = new TestParameters(); + params.testedType = testedType; + return params; + } + + private static final class TestParameters { + private WalkFlySpeedRestoreType testedType; + private float givenLimboWalkSpeed; + private float givenLimboFlySpeed; + private float givenPlayerWalkSpeed; + private float givenPlayerFlySpeed; + private float expectedWalkSpeed; + private float expectedFlySpeed; + + TestParameters withLimbo(float walkSpeed, float flySpeed) { + this.givenLimboWalkSpeed = walkSpeed; + this.givenLimboFlySpeed = flySpeed; + return this; + } + + TestParameters withPlayer(float walkSpeed, float flySpeed) { + this.givenPlayerWalkSpeed = walkSpeed; + this.givenPlayerFlySpeed = flySpeed; + return this; + } + + TestParameters expect(float walkSpeed, float flySpeed) { + this.expectedWalkSpeed = walkSpeed; + this.expectedFlySpeed = flySpeed; + return this; + } + + @Override + public String toString() { + return testedType + " {" + expectedWalkSpeed + ", " + expectedFlySpeed + "}"; + } + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java new file mode 100644 index 00000000..d253869f --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java @@ -0,0 +1,158 @@ +package fr.xephi.authme.data.limbo.persistence; + +import ch.jalu.injector.testing.BeforeInjecting; +import ch.jalu.injector.testing.DelayedInjectionRunner; +import ch.jalu.injector.testing.InjectDelayed; +import fr.xephi.authme.ReflectionTestUtils; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.initialization.factory.Factory; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.LimboSettings; +import org.bukkit.entity.Player; +import org.hamcrest.Matcher; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.logging.Logger; + +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.hamcrest.MockitoHamcrest.argThat; + +/** + * Test for {@link LimboPersistence}. + */ +@RunWith(DelayedInjectionRunner.class) +public class LimboPersistenceTest { + + @InjectDelayed + private LimboPersistence limboPersistence; + + @Mock + private Factory handlerFactory; + + @Mock + private Settings settings; + + @BeforeClass + public static void setUpLogger() { + TestHelper.setupLogger(); + } + + @BeforeInjecting + @SuppressWarnings("unchecked") + public void setUpMocks() { + given(settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE)).willReturn(LimboPersistenceType.DISABLED); + given(handlerFactory.newInstance(any(Class.class))) + .willAnswer(invocation -> mock(invocation.getArgument(0))); + } + + @Test + public void shouldInitializeProperly() { + // given / when / then + assertThat(getHandler(), instanceOf(NoOpPersistenceHandler.class)); + } + + @Test + public void shouldDelegateToHandler() { + // given + Player player = mock(Player.class); + LimboPersistenceHandler handler = getHandler(); + LimboPlayer limbo = mock(LimboPlayer.class); + given(handler.getLimboPlayer(player)).willReturn(limbo); + + // when + LimboPlayer result = limboPersistence.getLimboPlayer(player); + limboPersistence.saveLimboPlayer(player, mock(LimboPlayer.class)); + limboPersistence.removeLimboPlayer(mock(Player.class)); + + // then + assertThat(result, equalTo(limbo)); + verify(handler).getLimboPlayer(player); + verify(handler).saveLimboPlayer(eq(player), argThat(notNullAndDifferentFrom(limbo))); + verify(handler).removeLimboPlayer(argThat(notNullAndDifferentFrom(player))); + } + + @Test + public void shouldReloadProperly() { + // given + given(settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE)) + .willReturn(LimboPersistenceType.INDIVIDUAL_FILES); + + // when + limboPersistence.reload(settings); + + // then + assertThat(getHandler(), instanceOf(LimboPersistenceType.INDIVIDUAL_FILES.getImplementationClass())); + } + + @Test + public void shouldHandleExceptionWhenGettingLimbo() { + // given + Player player = mock(Player.class); + Logger logger = TestHelper.setupLogger(); + LimboPersistenceHandler handler = getHandler(); + doThrow(IllegalAccessException.class).when(handler).getLimboPlayer(player); + + // when + LimboPlayer result = limboPersistence.getLimboPlayer(player); + + // then + assertThat(result, nullValue()); + verify(logger).warning(argThat(containsString("[IllegalAccessException]"))); + } + + @Test + public void shouldHandleExceptionWhenSavingLimbo() { + // given + Player player = mock(Player.class); + LimboPlayer limbo = mock(LimboPlayer.class); + Logger logger = TestHelper.setupLogger(); + LimboPersistenceHandler handler = getHandler(); + doThrow(IllegalStateException.class).when(handler).saveLimboPlayer(player, limbo); + + // when + limboPersistence.saveLimboPlayer(player, limbo); + + // then + verify(logger).warning(argThat(containsString("[IllegalStateException]"))); + } + + @Test + public void shouldHandleExceptionWhenRemovingLimbo() { + // given + Player player = mock(Player.class); + Logger logger = TestHelper.setupLogger(); + LimboPersistenceHandler handler = getHandler(); + doThrow(UnsupportedOperationException.class).when(handler).removeLimboPlayer(player); + + // when + limboPersistence.removeLimboPlayer(player); + + // then + verify(logger).warning(argThat(containsString("[UnsupportedOperationException]"))); + } + + private LimboPersistenceHandler getHandler() { + return ReflectionTestUtils.getFieldValue(LimboPersistence.class, limboPersistence, "handler"); + } + + private static Matcher notNullAndDifferentFrom(T o) { + return both(not(sameInstance(o))).and(not(nullValue())); + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTypeTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTypeTest.java new file mode 100644 index 00000000..248ab9c1 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTypeTest.java @@ -0,0 +1,48 @@ +package fr.xephi.authme.data.limbo.persistence; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Test for {@link LimboPersistenceType}. + */ +public class LimboPersistenceTypeTest { + + @Test + public void shouldHaveUniqueImplementationClasses() { + // given + Set> classes = new HashSet<>(); + + // when / then + for (LimboPersistenceType persistenceType : LimboPersistenceType.values()) { + if (!classes.add(persistenceType.getImplementationClass())) { + fail("Implementation class '" + persistenceType.getImplementationClass() + "' from '" + + persistenceType + "' already encountered previously"); + } + } + } + + @Test + public void shouldHaveTypeReturnedFromImplementationClass() { + for (LimboPersistenceType persistenceType : LimboPersistenceType.values()) { + // given + LimboPersistenceHandler implementationMock = mock(persistenceType.getImplementationClass()); + given(implementationMock.getType()).willCallRealMethod(); + + // when + LimboPersistenceType returnedType = implementationMock.getType(); + + // then + assertThat(returnedType, equalTo(persistenceType)); + } + } + +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentConfigurationTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentConfigurationTest.java new file mode 100644 index 00000000..34d23723 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentConfigurationTest.java @@ -0,0 +1,47 @@ +package fr.xephi.authme.data.limbo.persistence; + +import com.google.common.collect.ImmutableSet; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.fail; + +/** + * Test for {@link SegmentConfiguration}. + */ +public class SegmentConfigurationTest { + + @Test + public void shouldHaveDistributionThatIsPowerOf2() { + // given + Set allowedDistributions = ImmutableSet.of(1, 2, 4, 8, 16); + + // when / then + for (SegmentConfiguration entry : SegmentConfiguration.values()) { + if (!allowedDistributions.contains(entry.getDistribution())) { + fail("Distribution must be a power of 2 and within [1, 16]. Offending item: " + entry); + } + } + } + + @Test + public void shouldHaveDifferentSegmentSizes() { + // given + Set segmentTotals = new HashSet<>(); + + // when / then + for (SegmentConfiguration entry : SegmentConfiguration.values()) { + int totalSegments = entry.getTotalSegments(); + assertThat(entry + " must have a positive segment size", + totalSegments, greaterThan(0)); + + assertThat(entry + " has a segment total that was already encountered (" + totalSegments + ")", + segmentTotals.add(totalSegments), equalTo(true)); + } + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolderTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolderTest.java new file mode 100644 index 00000000..064fc9c3 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolderTest.java @@ -0,0 +1,205 @@ +package fr.xephi.authme.data.limbo.persistence; + +import ch.jalu.injector.testing.BeforeInjecting; +import ch.jalu.injector.testing.DelayedInjectionRunner; +import ch.jalu.injector.testing.InjectDelayed; +import com.google.common.io.Files; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.LimboSettings; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.hamcrest.Matcher; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +import static fr.xephi.authme.data.limbo.LimboPlayerMatchers.hasLocation; +import static fr.xephi.authme.data.limbo.LimboPlayerMatchers.isLimbo; +import static java.util.UUID.fromString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayContainingInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Test for {@link SegmentFilesPersistenceHolder}. + */ +@RunWith(DelayedInjectionRunner.class) +public class SegmentFilesPersistenceHolderTest { + + /** Player is in seg32-10110 and should be migrated into seg16-f. */ + private static final UUID MIGRATED_UUID = fromString("f6a97c88-7c8f-c12e-4931-6206d4ca067d"); + private static final Matcher MIGRATED_LIMBO_MATCHER = + isLimbo(false, "noob", true, 0.2f, 0.1f); + + /** Existing player in seg16-f. */ + private static final UUID UUID_FAB69 = fromString("fab69c88-2cd0-1fed-f00d-dead14ca067d"); + private static final Matcher FAB69_MATCHER = + isLimbo(false, "", false, 0.2f, 0.1f); + + /** Player in seg16-8. */ + private static final UUID UUID_STAFF = fromString("88897c88-7c8f-c12e-4931-6206d4ca067d"); + private static final Matcher STAFF_MATCHER = + isLimbo(true, "staff", false, 0.3f, 0.1f); + + /** Player in seg16-8. */ + private static final UUID UUID_8C679 = fromString("8c679491-1234-abcd-9102-1fa6e0cc3f81"); + private static final Matcher SC679_MATCHER = + isLimbo(false, "primary", true, 0.1f, 0.0f); + + /** UUID for which no data is stored (belongs to a segment file that does not exist, seg16-4). */ + private static final UUID UNKNOWN_UUID = fromString("42d1cc0b-8f12-d04a-e7ba-a067d05cdc39"); + + /** UUID for which no data is stored (belongs to an existing segment file: seg16-8). */ + private static final UUID UNKNOWN_UUID2 = fromString("84d1cc0b-8f12-d04a-e7ba-a067d05cdc39"); + + + @InjectDelayed + private SegmentFilesPersistenceHolder persistenceHandler; + + @Mock + private Settings settings; + @Mock + private BukkitService bukkitService; + @DataFolder + private File dataFolder; + private File playerDataFolder; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @BeforeClass + public static void initLogger() { + TestHelper.setupLogger(); + } + + @BeforeInjecting + public void setUpClasses() throws IOException { + given(settings.getProperty(LimboSettings.SEGMENT_DISTRIBUTION)).willReturn(SegmentConfiguration.SIXTEEN); + dataFolder = temporaryFolder.newFolder(); + playerDataFolder = new File(dataFolder, "playerdata"); + playerDataFolder.mkdir(); + + File limboFilesFolder = new File("src/test/resources/fr/xephi/authme/data/limbo"); + for (File file : limboFilesFolder.listFiles()) { + File from = new File(playerDataFolder, file.getName()); + Files.copy(file, from); + } + + given(bukkitService.getWorld(anyString())) + .willAnswer(invocation -> { + World world = mock(World.class); + given(world.getName()).willReturn(invocation.getArgument(0)); + return world; + }); + } + + // Note ljacqu 20170314: These tests are a little slow to set up; therefore we sometimes + // test things in one test that would traditionally belong into two separate tests + + @Test + public void shouldMigrateOldSegmentFilesOnStartup() { + // Ensure that only the files of the current segmenting scheme remain + assertThat(playerDataFolder.list(), arrayContainingInAnyOrder("seg16-8-limbo.json", "seg16-f-limbo.json")); + + // Check that the expected limbo players can be read + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(MIGRATED_UUID)), MIGRATED_LIMBO_MATCHER); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_FAB69)), FAB69_MATCHER); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_STAFF)), STAFF_MATCHER); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_8C679)), SC679_MATCHER); + + // Check that unknown players are null (whose segment file exists and does not exist) + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UNKNOWN_UUID)), nullValue()); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UNKNOWN_UUID2)), nullValue()); + } + + @Test + public void shouldRemovePlayer() { + // given + Player playerToRemove = mockPlayerWithUuid(UUID_STAFF); + Player unknownPlayerToRemove = mockPlayerWithUuid(UNKNOWN_UUID); + + // when + persistenceHandler.removeLimboPlayer(playerToRemove); + persistenceHandler.removeLimboPlayer(unknownPlayerToRemove); + + // then + assertThat(persistenceHandler.getLimboPlayer(playerToRemove), nullValue()); + assertThat(persistenceHandler.getLimboPlayer(unknownPlayerToRemove), nullValue()); + // Player in same segment should still exist... + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_8C679)), SC679_MATCHER); + + // Check that we didn't create seg16-4 by deleting UNKNOWN_UUID. + assertThat(playerDataFolder.list(), arrayContainingInAnyOrder("seg16-8-limbo.json", "seg16-f-limbo.json")); + } + + @Test + public void shouldAddPlayer() { + // given + Player uuidToAdd1 = mockPlayerWithUuid(UNKNOWN_UUID); + Location location1 = new Location(mockWorldWithName("1world"), 120, 60, -80, 0.42345f, 120.32f); + LimboPlayer limbo1 = new LimboPlayer(location1, false, "group-1", true, 0.1f, 0.2f); + Player uuidToAdd2 = mockPlayerWithUuid(UNKNOWN_UUID2); + Location location2 = new Location(mockWorldWithName("2world"), -40, 20, 33, 4.235f, 8.32299f); + LimboPlayer limbo2 = new LimboPlayer(location2, true, "", false, 0.0f, 0.25f); + + // when + persistenceHandler.saveLimboPlayer(uuidToAdd1, limbo1); + persistenceHandler.saveLimboPlayer(uuidToAdd2, limbo2); + + // then + LimboPlayer addedPlayer1 = persistenceHandler.getLimboPlayer(uuidToAdd1); + assertThat(addedPlayer1, isLimbo(limbo1)); + assertThat(addedPlayer1, hasLocation(location1)); + LimboPlayer addedPlayer2 = persistenceHandler.getLimboPlayer(uuidToAdd2); + assertThat(addedPlayer2, isLimbo(limbo2)); + assertThat(addedPlayer2, hasLocation(location2)); + + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(MIGRATED_UUID)), MIGRATED_LIMBO_MATCHER); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_FAB69)), FAB69_MATCHER); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_STAFF)), STAFF_MATCHER); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_8C679)), SC679_MATCHER); + } + + @Test + public void shouldHandleReadErrorGracefully() throws IOException { + // given + // assumption + File invalidFile = new File(playerDataFolder, "seg16-4-limbo.json"); + assertThat(invalidFile.exists(), equalTo(false)); + Files.write("not valid json".getBytes(), invalidFile); + + // when + LimboPlayer result = persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UNKNOWN_UUID)); + + // then + assertThat(result, nullValue()); + } + + private static Player mockPlayerWithUuid(UUID uuid) { + Player player = mock(Player.class); + given(player.getUniqueId()).willReturn(uuid); + return player; + } + + private static World mockWorldWithName(String name) { + World world = mock(World.class); + given(world.getName()).willReturn(name); + return world; + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilderTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilderTest.java new file mode 100644 index 00000000..64843db8 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilderTest.java @@ -0,0 +1,141 @@ +package fr.xephi.authme.data.limbo.persistence; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.EIGHT; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.FOUR; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.ONE; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.SIXTEEN; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.SIXTY_FOUR; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.THIRTY_TWO; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.TWO_FIFTY; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link SegmentNameBuilder}. + */ +public class SegmentNameBuilderTest { + + /** + * Checks that using a given segment size really produces as many segments as defined. + * E.g. if we partition with {@link SegmentConfiguration#EIGHT} we expect eight different buckets. + */ + @Test + public void shouldCreatePromisedSizeOfSegments() { + for (SegmentConfiguration part : SegmentConfiguration.values()) { + // Perform this check only for `length` <= 5 because the test creates all hex numbers with `length` digits. + if (part.getLength() <= 5) { + checkTotalSegmentsProduced(part); + } + } + } + + private void checkTotalSegmentsProduced(SegmentConfiguration part) { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(part); + Set encounteredSegments = new HashSet<>(); + int shift = part.getLength() * 4; + // e.g. (1 << 16) - 1 = 0xFFFF. (Number of digits = shift/4, since 16 = 2^4) + int max = (1 << shift) - 1; + + // when + for (int i = 0; i <= max; ++i) { + String uuid = toPaddedHex(i, part.getLength()); + encounteredSegments.add(nameBuilder.createSegmentName(uuid)); + } + + // then + assertThat(encounteredSegments, hasSize(part.getTotalSegments())); + } + + private static String toPaddedHex(int dec, int padLength) { + String hexResult = Integer.toString(dec, 16); + while (hexResult.length() < padLength) { + hexResult = "0" + hexResult; + } + return hexResult; + } + + @Test + public void shouldCreateOneSegment() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(ONE); + + // when / then + assertThat(nameBuilder.createSegmentName("abc"), equalTo("seg1-0")); + assertThat(nameBuilder.createSegmentName("f0e"), equalTo("seg1-0")); + assertThat(nameBuilder.createSegmentName("329"), equalTo("seg1-0")); + } + + @Test + public void shouldCreateFourSegments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(FOUR); + + // when / then + assertThat(nameBuilder.createSegmentName("f9cc"), equalTo("seg4-3")); + assertThat(nameBuilder.createSegmentName("84c9"), equalTo("seg4-2")); + assertThat(nameBuilder.createSegmentName("3799"), equalTo("seg4-0")); + } + + @Test + public void shouldCreateEightSegments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(EIGHT); + + // when / then + assertThat(nameBuilder.createSegmentName("fc9c"), equalTo("seg8-7")); + assertThat(nameBuilder.createSegmentName("90ad"), equalTo("seg8-4")); + assertThat(nameBuilder.createSegmentName("35e4"), equalTo("seg8-1")); + assertThat(nameBuilder.createSegmentName("a39f"), equalTo("seg8-5")); + } + + @Test + public void shouldCreateSixteenSegments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(SIXTEEN); + + // when / then + assertThat(nameBuilder.createSegmentName("fc9a054"), equalTo("seg16-f")); + assertThat(nameBuilder.createSegmentName("b0a945e"), equalTo("seg16-b")); + assertThat(nameBuilder.createSegmentName("7afebab"), equalTo("seg16-7")); + } + + @Test + public void shouldCreateThirtyTwoSegments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(THIRTY_TWO); + + // when / then + assertThat(nameBuilder.createSegmentName("f890c9"), equalTo("seg32-11101")); + assertThat(nameBuilder.createSegmentName("49c39a"), equalTo("seg32-01101")); + assertThat(nameBuilder.createSegmentName("b75d09"), equalTo("seg32-10010")); + } + + @Test + public void shouldCreateSixtyFourSegments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(SIXTY_FOUR); + + // when / then + assertThat(nameBuilder.createSegmentName("82f"), equalTo("seg64-203")); + assertThat(nameBuilder.createSegmentName("9b4"), equalTo("seg64-221")); + assertThat(nameBuilder.createSegmentName("068"), equalTo("seg64-012")); + } + + @Test + public void shouldCreate256Segments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(TWO_FIFTY); + + // when / then + assertThat(nameBuilder.createSegmentName("a813c"), equalTo("seg256-a8")); + assertThat(nameBuilder.createSegmentName("b4d01"), equalTo("seg256-b4")); + assertThat(nameBuilder.createSegmentName("7122f"), equalTo("seg256-71")); + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandlerTest.java similarity index 72% rename from src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java rename to src/test/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandlerTest.java index 6d346854..a4e7dc93 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandlerTest.java @@ -1,13 +1,12 @@ -package fr.xephi.authme.data.limbo; +package fr.xephi.authme.data.limbo.persistence; import ch.jalu.injector.testing.BeforeInjecting; import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.initialization.DataFolder; -import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.service.BukkitService; -import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.util.FileUtils; import org.bukkit.Location; import org.bukkit.World; @@ -31,26 +30,20 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** - * Test for {@link LimboPlayerStorage}. + * Test for {@link SeparateFilePersistenceHandler}. */ @RunWith(DelayedInjectionRunner.class) -public class LimboPlayerStorageTest { +public class SeparateFilePersistenceHandlerTest { - private static final UUID SAMPLE_UUID = UUID.nameUUIDFromBytes("PlayerDataStorageTest".getBytes()); + private static final UUID SAMPLE_UUID = UUID.nameUUIDFromBytes("PersistenceTest".getBytes()); private static final String SOURCE_FOLDER = TestHelper.PROJECT_ROOT + "data/backup/"; @InjectDelayed - private LimboPlayerStorage limboPlayerStorage; - - @Mock - private SpawnLoader spawnLoader; + private SeparateFilePersistenceHandler handler; @Mock private BukkitService bukkitService; - @Mock - private PermissionsManager permissionsManager; - @DataFolder private File dataFolder; @@ -77,7 +70,7 @@ public class LimboPlayerStorageTest { given(bukkitService.getWorld("nether")).willReturn(world); // when - LimboPlayer data = limboPlayerStorage.readData(player); + LimboPlayer data = handler.getLimboPlayer(player); // then assertThat(data, not(nullValue())); @@ -102,44 +95,28 @@ public class LimboPlayerStorageTest { given(player.getUniqueId()).willReturn(UUID.nameUUIDFromBytes("other-player".getBytes())); // when - LimboPlayer data = limboPlayerStorage.readData(player); + LimboPlayer data = handler.getLimboPlayer(player); // then assertThat(data, nullValue()); } - @Test - public void shouldReturnIfHasData() { - // given - Player player1 = mock(Player.class); - given(player1.getUniqueId()).willReturn(SAMPLE_UUID); - Player player2 = mock(Player.class); - given(player2.getUniqueId()).willReturn(UUID.nameUUIDFromBytes("not-stored".getBytes())); - - // when / then - assertThat(limboPlayerStorage.hasData(player1), equalTo(true)); - assertThat(limboPlayerStorage.hasData(player2), equalTo(false)); - } - @Test public void shouldSavePlayerData() { // given Player player = mock(Player.class); UUID uuid = UUID.nameUUIDFromBytes("New player".getBytes()); given(player.getUniqueId()).willReturn(uuid); - given(permissionsManager.getPrimaryGroup(player)).willReturn("primary-grp"); - given(player.isOp()).willReturn(true); - given(player.getWalkSpeed()).willReturn(1.2f); - given(player.getFlySpeed()).willReturn(0.8f); - given(player.getAllowFlight()).willReturn(true); + World world = mock(World.class); given(world.getName()).willReturn("player-world"); Location location = new Location(world, 0.2, 102.25, -89.28, 3.02f, 90.13f); - given(spawnLoader.getPlayerLocationOrSpawn(player)).willReturn(location); + String group = "primary-grp"; + LimboPlayer limbo = new LimboPlayer(location, true, group, true, 1.2f, 0.8f); // when - limboPlayerStorage.saveData(player); + handler.saveLimboPlayer(player, limbo); // then File playerFile = new File(dataFolder, FileUtils.makePath("playerdata", uuid.toString(), "data.json")); diff --git a/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java b/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java index 835d4b49..74318498 100644 --- a/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java +++ b/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java @@ -4,8 +4,10 @@ import ch.jalu.injector.testing.BeforeInjecting; import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import fr.xephi.authme.TestHelper; +import fr.xephi.authme.output.LogLevel; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.settings.properties.PluginSettings; import org.apache.commons.mail.EmailException; import org.apache.commons.mail.HtmlEmail; import org.junit.BeforeClass; @@ -49,6 +51,7 @@ public class SendMailSSLTest { public void initFields() throws IOException { given(settings.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn("mail@example.org"); given(settings.getProperty(EmailSettings.MAIL_PASSWORD)).willReturn("pass1234"); + given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(LogLevel.INFO); } @Test @@ -67,6 +70,7 @@ public class SendMailSSLTest { given(settings.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn(senderAccount); String senderName = "Server administration"; given(settings.getProperty(EmailSettings.MAIL_SENDER_NAME)).willReturn(senderName); + given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(LogLevel.DEBUG); // when HtmlEmail email = sendMailSSL.initializeMail("recipient@example.com"); diff --git a/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java b/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java index 12d4fee7..9f21b7ba 100644 --- a/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java +++ b/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java @@ -3,18 +3,17 @@ package fr.xephi.authme.process.login; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; import fr.xephi.authme.message.MessageKey; -import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.properties.DatabaseSettings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.task.LimboPlayerTaskManager; import org.bukkit.entity.Player; import org.junit.BeforeClass; import org.junit.Test; @@ -58,11 +57,9 @@ public class AsynchronousLoginTest { @Mock private CommonService commonService; @Mock - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; @Mock private BukkitService bukkitService; - @Mock - private PermissionsManager permissionsManager; @BeforeClass public static void initLogger() { @@ -182,7 +179,7 @@ public class AsynchronousLoginTest { // given Player player = mockPlayer("Carl"); given(commonService.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP)).willReturn(2); - given(permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(false); + given(commonService.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(false); mockOnlinePlayersInBukkitService(); // when @@ -190,7 +187,7 @@ public class AsynchronousLoginTest { // then assertThat(result, equalTo(false)); - verify(permissionsManager).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); + verify(commonService).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); verify(bukkitService).getOnlinePlayers(); } @@ -213,14 +210,14 @@ public class AsynchronousLoginTest { // given Player player = mockPlayer("Frank"); given(commonService.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP)).willReturn(1); - given(permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(true); + given(commonService.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(true); // when boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "127.0.0.4"); // then assertThat(result, equalTo(false)); - verify(permissionsManager).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); + verify(commonService).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); verifyZeroInteractions(bukkitService); } @@ -229,7 +226,7 @@ public class AsynchronousLoginTest { // given Player player = mockPlayer("Ian"); given(commonService.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP)).willReturn(2); - given(permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(false); + given(commonService.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(false); mockOnlinePlayersInBukkitService(); // when @@ -237,7 +234,7 @@ public class AsynchronousLoginTest { // then assertThat(result, equalTo(true)); - verify(permissionsManager).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); + verify(commonService).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); verify(bukkitService).getOnlinePlayers(); } diff --git a/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java b/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java index b845b8d5..dee3dc64 100644 --- a/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java +++ b/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java @@ -3,9 +3,13 @@ package fr.xephi.authme.process.register; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.initialization.factory.SingletonStore; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.process.register.executors.PasswordRegisterParams; import fr.xephi.authme.process.register.executors.RegistrationExecutor; +import fr.xephi.authme.process.register.executors.RegistrationMethod; +import fr.xephi.authme.process.register.executors.TwoFactorRegisterParams; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; @@ -16,6 +20,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.only; @@ -39,6 +44,8 @@ public class AsyncRegisterTest { private CommonService commonService; @Mock private DataSource dataSource; + @Mock + private SingletonStore registrationExecutorStore; @Test public void shouldDetectAlreadyLoggedInPlayer() { @@ -47,9 +54,10 @@ public class AsyncRegisterTest { Player player = mockPlayerWithName(name); given(playerCache.isAuthenticated(name)).willReturn(true); RegistrationExecutor executor = mock(RegistrationExecutor.class); + singletonStoreWillReturn(registrationExecutorStore, executor); // when - asyncRegister.register(player, executor); + asyncRegister.register(RegistrationMethod.PASSWORD_REGISTRATION, PasswordRegisterParams.of(player, "abc", null)); // then verify(commonService).send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); @@ -64,9 +72,10 @@ public class AsyncRegisterTest { given(playerCache.isAuthenticated(name)).willReturn(false); given(commonService.getProperty(RegistrationSettings.IS_ENABLED)).willReturn(false); RegistrationExecutor executor = mock(RegistrationExecutor.class); + singletonStoreWillReturn(registrationExecutorStore, executor); // when - asyncRegister.register(player, executor); + asyncRegister.register(RegistrationMethod.TWO_FACTOR_REGISTRATION, TwoFactorRegisterParams.of(player)); // then verify(commonService).send(player, MessageKey.REGISTRATION_DISABLED); @@ -82,9 +91,10 @@ public class AsyncRegisterTest { given(commonService.getProperty(RegistrationSettings.IS_ENABLED)).willReturn(true); given(dataSource.isAuthAvailable(name)).willReturn(true); RegistrationExecutor executor = mock(RegistrationExecutor.class); + singletonStoreWillReturn(registrationExecutorStore, executor); // when - asyncRegister.register(player, executor); + asyncRegister.register(RegistrationMethod.TWO_FACTOR_REGISTRATION, TwoFactorRegisterParams.of(player)); // then verify(commonService).send(player, MessageKey.NAME_ALREADY_REGISTERED); @@ -93,6 +103,7 @@ public class AsyncRegisterTest { } @Test + @SuppressWarnings("unchecked") public void shouldStopForFailedExecutorCheck() { // given String name = "edbert"; @@ -103,14 +114,16 @@ public class AsyncRegisterTest { given(commonService.getProperty(RestrictionSettings.MAX_REGISTRATION_PER_IP)).willReturn(0); given(dataSource.isAuthAvailable(name)).willReturn(false); RegistrationExecutor executor = mock(RegistrationExecutor.class); - given(executor.isRegistrationAdmitted()).willReturn(false); + TwoFactorRegisterParams params = TwoFactorRegisterParams.of(player); + given(executor.isRegistrationAdmitted(params)).willReturn(false); + singletonStoreWillReturn(registrationExecutorStore, executor); // when - asyncRegister.register(player, executor); + asyncRegister.register(RegistrationMethod.TWO_FACTOR_REGISTRATION, params); // then verify(dataSource, only()).isAuthAvailable(name); - verify(executor, only()).isRegistrationAdmitted(); + verify(executor, only()).isRegistrationAdmitted(params); } private static Player mockPlayerWithName(String name) { @@ -118,4 +131,10 @@ public class AsyncRegisterTest { given(player.getName()).willReturn(name); return player; } + + @SuppressWarnings("unchecked") + private static void singletonStoreWillReturn(SingletonStore store, + RegistrationExecutor mock) { + given(store.getSingleton(any(Class.class))).willReturn(mock); + } } diff --git a/src/test/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProviderTest.java b/src/test/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProviderTest.java index 9ca9c7aa..6aa22161 100644 --- a/src/test/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProviderTest.java +++ b/src/test/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProviderTest.java @@ -1,6 +1,5 @@ package fr.xephi.authme.process.register.executors; -import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; @@ -34,13 +33,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; /** - * Test for {@link EmailRegisterExecutorProvider}. + * Test for {@link EmailRegisterExecutor}. */ @RunWith(MockitoJUnitRunner.class) public class EmailRegisterExecutorProviderTest { @InjectMocks - private EmailRegisterExecutorProvider emailRegisterExecutorProvider; + private EmailRegisterExecutor executor; @Mock private PermissionsManager permissionsManager; @@ -62,10 +61,10 @@ public class EmailRegisterExecutorProviderTest { String email = "test@example.com"; given(dataSource.countAuthsByEmail(email)).willReturn(4); Player player = mock(Player.class); - RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, email); + EmailRegisterParams params = EmailRegisterParams.of(player, email); // when - boolean result = executor.isRegistrationAdmitted(); + boolean result = executor.isRegistrationAdmitted(params); // then assertThat(result, equalTo(false)); @@ -80,10 +79,10 @@ public class EmailRegisterExecutorProviderTest { given(commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL)).willReturn(3); Player player = mock(Player.class); given(permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(true); - RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com"); // when - boolean result = executor.isRegistrationAdmitted(); + boolean result = executor.isRegistrationAdmitted(params); // then assertThat(result, equalTo(true)); @@ -97,10 +96,10 @@ public class EmailRegisterExecutorProviderTest { String email = "test@example.com"; given(dataSource.countAuthsByEmail(email)).willReturn(0); Player player = mock(Player.class); - RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com"); // when - boolean result = executor.isRegistrationAdmitted(); + boolean result = executor.isRegistrationAdmitted(params); // then assertThat(result, equalTo(true)); @@ -120,10 +119,10 @@ public class EmailRegisterExecutorProviderTest { World world = mock(World.class); given(world.getName()).willReturn("someWorld"); given(player.getLocation()).willReturn(new Location(world, 48, 96, 144)); - RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com"); // when - PlayerAuth auth = executor.buildPlayerAuth(); + PlayerAuth auth = executor.buildPlayerAuth(params); // then assertThat(auth, hasAuthBasicData("veronica", "Veronica", "test@example.com", "123.45.67.89")); @@ -132,18 +131,17 @@ public class EmailRegisterExecutorProviderTest { } @Test - @SuppressWarnings("unchecked") public void shouldPerformActionAfterDataSourceSave() { // given given(emailService.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(true); Player player = mock(Player.class); given(player.getName()).willReturn("Laleh"); - RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com"); String password = "A892C#@"; - ReflectionTestUtils.setField((Class) executor.getClass(), executor, "password", password); + params.setPassword(password); // when - executor.executePostPersistAction(); + executor.executePostPersistAction(params); // then verify(emailService).sendPasswordMail("Laleh", "test@example.com", password); @@ -151,18 +149,17 @@ public class EmailRegisterExecutorProviderTest { } @Test - @SuppressWarnings("unchecked") public void shouldHandleEmailSendingFailure() { // given given(emailService.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(false); Player player = mock(Player.class); given(player.getName()).willReturn("Laleh"); - RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com"); String password = "A892C#@"; - ReflectionTestUtils.setField((Class) executor.getClass(), executor, "password", password); + params.setPassword(password); // when - executor.executePostPersistAction(); + executor.executePostPersistAction(params); // then verify(emailService).sendPasswordMail("Laleh", "test@example.com", password); diff --git a/src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProviderTest.java b/src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorTest.java similarity index 81% rename from src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProviderTest.java rename to src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorTest.java index a033975f..6c103944 100644 --- a/src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProviderTest.java +++ b/src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorTest.java @@ -34,13 +34,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; /** - * Test for {@link PasswordRegisterExecutorProvider}. + * Test for {@link PasswordRegisterExecutor}. */ @RunWith(MockitoJUnitRunner.class) -public class PasswordRegisterExecutorProviderTest { +public class PasswordRegisterExecutorTest { @InjectMocks - private PasswordRegisterExecutorProvider passwordRegisterExecutorProvider; + private PasswordRegisterExecutor executor; @Mock private ValidationService validationService; @@ -62,10 +62,10 @@ public class PasswordRegisterExecutorProviderTest { String name = "player040"; given(validationService.validatePassword(password, name)).willReturn(new ValidationResult()); Player player = mockPlayerWithName(name); - RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, password, null); + PasswordRegisterParams params = PasswordRegisterParams.of(player, password, null); // when - boolean result = executor.isRegistrationAdmitted(); + boolean result = executor.isRegistrationAdmitted(params); // then assertThat(result, equalTo(true)); @@ -80,10 +80,10 @@ public class PasswordRegisterExecutorProviderTest { given(validationService.validatePassword(password, name)).willReturn( new ValidationResult(MessageKey.PASSWORD_CHARACTERS_ERROR, "[a-z]")); Player player = mockPlayerWithName(name); - RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, password, null); + PasswordRegisterParams params = PasswordRegisterParams.of(player, password, null); // when - boolean result = executor.isRegistrationAdmitted(); + boolean result = executor.isRegistrationAdmitted(params); // then assertThat(result, equalTo(false)); @@ -101,10 +101,10 @@ public class PasswordRegisterExecutorProviderTest { World world = mock(World.class); given(world.getName()).willReturn("someWorld"); given(player.getLocation()).willReturn(new Location(world, 48, 96, 144)); - RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, "pass", "mail@example.org"); + PasswordRegisterParams params = PasswordRegisterParams.of(player, "pass", "mail@example.org"); // when - PlayerAuth auth = executor.buildPlayerAuth(); + PlayerAuth auth = executor.buildPlayerAuth(params); // then assertThat(auth, hasAuthBasicData("s1m0n", "S1m0N", "mail@example.org", "123.45.67.89")); @@ -118,10 +118,10 @@ public class PasswordRegisterExecutorProviderTest { given(commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER)).willReturn(false); given(commonService.getProperty(PluginSettings.USE_ASYNC_TASKS)).willReturn(false); Player player = mock(Player.class); - RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, "pass", "mail@example.org"); + PasswordRegisterParams params = PasswordRegisterParams.of(player, "pass", "mail@example.org"); // when - executor.executePostPersistAction(); + executor.executePostPersistAction(params); // then TestHelper.runSyncDelayedTaskWithDelay(bukkitService); @@ -134,10 +134,10 @@ public class PasswordRegisterExecutorProviderTest { // given given(commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER)).willReturn(true); Player player = mock(Player.class); - RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, "pass", "mail@example.org"); + PasswordRegisterParams params = PasswordRegisterParams.of(player, "pass", "mail@example.org"); // when - executor.executePostPersistAction(); + executor.executePostPersistAction(params); // then verifyZeroInteractions(bukkitService, asynchronousLogin); diff --git a/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java b/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java index d586b425..26433a45 100644 --- a/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java +++ b/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java @@ -3,19 +3,17 @@ package fr.xephi.authme.process.unregister; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AuthGroupHandler; import fr.xephi.authme.permission.AuthGroupType; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.settings.properties.RegistrationSettings; -import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.task.LimboPlayerTaskManager; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.BeforeClass; @@ -53,9 +51,7 @@ public class AsynchronousUnregisterTest { @Mock private BukkitService bukkitService; @Mock - private LimboCache limboCache; - @Mock - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; @Mock private TeleportationService teleportationService; @Mock @@ -85,7 +81,7 @@ public class AsynchronousUnregisterTest { // then verify(service).send(player, MessageKey.WRONG_PASSWORD); verify(passwordSecurity).comparePassword(userPassword, password, name); - verifyZeroInteractions(dataSource, limboPlayerTaskManager, limboCache, authGroupHandler, teleportationService); + verifyZeroInteractions(dataSource, limboService, authGroupHandler, teleportationService); verify(player, only()).getName(); } @@ -104,8 +100,6 @@ public class AsynchronousUnregisterTest { given(passwordSecurity.comparePassword(userPassword, password, name)).willReturn(true); given(dataSource.removeAuth(name)).willReturn(true); given(service.getProperty(RegistrationSettings.FORCE)).willReturn(true); - given(service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)).willReturn(true); - given(service.getProperty(RestrictionSettings.TIMEOUT)).willReturn(12); // when asynchronousUnregister.unregister(player, userPassword); @@ -117,7 +111,7 @@ public class AsynchronousUnregisterTest { verify(playerCache).removePlayer(name); verify(teleportationService).teleportOnJoin(player); verify(authGroupHandler).setGroup(player, AuthGroupType.UNREGISTERED); - verify(bukkitService).runTask(any(Runnable.class)); + verify(bukkitService).scheduleSyncTaskFromOptionallyAsyncTask(any(Runnable.class)); } @Test @@ -135,8 +129,6 @@ public class AsynchronousUnregisterTest { given(passwordSecurity.comparePassword(userPassword, password, name)).willReturn(true); given(dataSource.removeAuth(name)).willReturn(true); given(service.getProperty(RegistrationSettings.FORCE)).willReturn(true); - given(service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)).willReturn(true); - given(service.getProperty(RestrictionSettings.TIMEOUT)).willReturn(0); // when asynchronousUnregister.unregister(player, userPassword); @@ -148,7 +140,7 @@ public class AsynchronousUnregisterTest { verify(playerCache).removePlayer(name); verify(teleportationService).teleportOnJoin(player); verify(authGroupHandler).setGroup(player, AuthGroupType.UNREGISTERED); - verify(bukkitService).runTask(any(Runnable.class)); + verify(bukkitService).scheduleSyncTaskFromOptionallyAsyncTask(any(Runnable.class)); } @Test @@ -175,7 +167,7 @@ public class AsynchronousUnregisterTest { verify(dataSource).removeAuth(name); verify(playerCache).removePlayer(name); verify(authGroupHandler).setGroup(player, AuthGroupType.UNREGISTERED); - verifyZeroInteractions(teleportationService, limboPlayerTaskManager); + verifyZeroInteractions(teleportationService, limboService); verify(bukkitService, never()).runTask(any(Runnable.class)); } @@ -237,8 +229,6 @@ public class AsynchronousUnregisterTest { given(player.isOnline()).willReturn(true); given(dataSource.removeAuth(name)).willReturn(true); given(service.getProperty(RegistrationSettings.FORCE)).willReturn(true); - given(service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)).willReturn(true); - given(service.getProperty(RestrictionSettings.TIMEOUT)).willReturn(12); CommandSender initiator = mock(CommandSender.class); // when @@ -251,7 +241,7 @@ public class AsynchronousUnregisterTest { verify(playerCache).removePlayer(name); verify(teleportationService).teleportOnJoin(player); verify(authGroupHandler).setGroup(player, AuthGroupType.UNREGISTERED); - verify(bukkitService).runTask(any(Runnable.class)); + verify(bukkitService).scheduleSyncTaskFromOptionallyAsyncTask(any(Runnable.class)); } @Test diff --git a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java index 04d2bc0b..cebde5da 100644 --- a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java +++ b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java @@ -9,7 +9,7 @@ import fr.xephi.authme.events.PasswordEncryptionEvent; import fr.xephi.authme.initialization.factory.FactoryDependencyHandler; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.security.crypts.JOOMLA; +import fr.xephi.authme.security.crypts.Joomla; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.SecuritySettings; @@ -231,7 +231,7 @@ public class PasswordSecurityTest { ArgumentCaptor captor = ArgumentCaptor.forClass(PasswordEncryptionEvent.class); verify(pluginManager).callEvent(captor.capture()); PasswordEncryptionEvent event = captor.getValue(); - assertThat(JOOMLA.class.equals(caughtClassInEvent), equalTo(true)); + assertThat(Joomla.class.equals(caughtClassInEvent), equalTo(true)); assertThat(event.getPlayerName(), equalTo(usernameLowerCase)); } diff --git a/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java b/src/test/java/fr/xephi/authme/security/crypts/BCrypt2yTest.java similarity index 73% rename from src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java rename to src/test/java/fr/xephi/authme/security/crypts/BCrypt2yTest.java index ac34dea0..83308c9f 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/BCrypt2yTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link BCRYPT2Y}. + * Test for {@link BCrypt2y}. */ -public class BCRYPT2YTest extends AbstractEncryptionMethodTest { +public class BCrypt2yTest extends AbstractEncryptionMethodTest { - public BCRYPT2YTest() { - super(new BCRYPT2Y(), + public BCrypt2yTest() { + super(new BCrypt2y(), "$2y$10$da641e404b982edf1c7c0uTU9BcKzfA2vWKV05q6r.dCvm/93wqVK", // password "$2y$10$e52c48a76f5b86f5da899uiK/HYocyPsfQXESNbP278rIz08LKEP2", // PassWord1 "$2y$10$be6f11548dc5fb4088410ONdC0dXnJ04y1RHcJh5fVF3XK5d.qgqK", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java b/src/test/java/fr/xephi/authme/security/crypts/BCryptTest.java similarity index 83% rename from src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java rename to src/test/java/fr/xephi/authme/security/crypts/BCryptTest.java index 1fe9ed08..c2d9d6f5 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/BCryptTest.java @@ -7,12 +7,12 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** - * Test for {@link BCRYPT}. + * Test for {@link BCrypt}. */ -public class BcryptTest extends AbstractEncryptionMethodTest { +public class BCryptTest extends AbstractEncryptionMethodTest { - public BcryptTest() { - super(new BCRYPT(mockSettings()), + public BCryptTest() { + super(new BCrypt(mockSettings()), "$2a$10$6iATmYgwJVc3YONhVcZFve3Cfb5GnwvKhJ20r.hMjmcNkIT9.Uh9K", // password "$2a$10$LOhUxhEcS0vgDPv/jkXvCurNb7LjP9xUlEolJGk.Uhgikqc6FtIOi", // PassWord1 "$2a$10$j9da7SGiaakWhzIms9BtwemLUeIhSEphGUQ3XSlvYgpYsGnGCKRBa", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java b/src/test/java/fr/xephi/authme/security/crypts/CrazyCrypt1Test.java similarity index 81% rename from src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java rename to src/test/java/fr/xephi/authme/security/crypts/CrazyCrypt1Test.java index fa27de3b..f98ece5a 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/CrazyCrypt1Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link CRAZYCRYPT1}. + * Test for {@link CrazyCrypt1}. */ -public class CRAZYCRYPT1Test extends AbstractEncryptionMethodTest { +public class CrazyCrypt1Test extends AbstractEncryptionMethodTest { - public CRAZYCRYPT1Test() { - super(new CRAZYCRYPT1(), + public CrazyCrypt1Test() { + super(new CrazyCrypt1(), "d5c76eb36417d4e97ec62609619e40a9e549a2598d0dab5a7194fd997a9305af78de2b93f958e150d19dd1e7f821043379ddf5f9c7f352bf27df91ae4913f3e8", // password "49c63f827c88196871e344e589bd46cc4fa6db3c27801bbad5374c0d216381977627c1d76f2114667d5dd117e046f7493eb06e4f461f4f848aa08f6f40a3e934", // PassWord1 "6fefb0233bab6e6efb9c16f82cb0d8f569488905e2dae0e7c9dde700e7363da67213d37c44bc15f4a05854c9c21e5688389d416413c7309398aa96cb1f341d08", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/DOUBLEMD5Test.java b/src/test/java/fr/xephi/authme/security/crypts/DoubleMd5Test.java similarity index 67% rename from src/test/java/fr/xephi/authme/security/crypts/DOUBLEMD5Test.java rename to src/test/java/fr/xephi/authme/security/crypts/DoubleMd5Test.java index 53039951..cc12df9e 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/DOUBLEMD5Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/DoubleMd5Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link DOUBLEMD5}. + * Test for {@link DoubleMd5}. */ -public class DOUBLEMD5Test extends AbstractEncryptionMethodTest { +public class DoubleMd5Test extends AbstractEncryptionMethodTest { - public DOUBLEMD5Test() { - super(new DOUBLEMD5(), + public DoubleMd5Test() { + super(new DoubleMd5(), "696d29e0940a4957748fe3fc9efd22a3", // password "c77aa2024d9fb7233a2872452d601aba", // PassWord1 "fbd5790af706ec19f8a7ef161878758b", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java b/src/test/java/fr/xephi/authme/security/crypts/Ipb3Test.java similarity index 75% rename from src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java rename to src/test/java/fr/xephi/authme/security/crypts/Ipb3Test.java index cf42567e..984f3d21 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Ipb3Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link IPB3}. + * Test for {@link Ipb3}. */ -public class IPB3Test extends AbstractEncryptionMethodTest { +public class Ipb3Test extends AbstractEncryptionMethodTest { - public IPB3Test() { - super(new IPB3(), + public Ipb3Test() { + super(new Ipb3(), new HashedPassword("f8ecea1ce42b5babef369ff7692dbe3f", "1715b"), //password new HashedPassword("40a93731a931352e0619cdf09b975040", "ba91c"), //PassWord1 new HashedPassword("a77ca982373946d5800430bd2947ba11", "a7725"), //&^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java b/src/test/java/fr/xephi/authme/security/crypts/Ipb4Test.java similarity index 81% rename from src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java rename to src/test/java/fr/xephi/authme/security/crypts/Ipb4Test.java index 28c76a09..5f71c23d 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Ipb4Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link IPB4}. + * Test for {@link Ipb4}. */ -public class IPB4Test extends AbstractEncryptionMethodTest { +public class Ipb4Test extends AbstractEncryptionMethodTest { - public IPB4Test() { - super(new IPB4(), + public Ipb4Test() { + super(new Ipb4(), new HashedPassword("$2a$13$leEvXu77OIwPwNvtZIJvaeAx8EItGHuR3nIlq8416g0gXeJaQdrr2", "leEvXu77OIwPwNvtZIJval"), //password new HashedPassword("$2a$13$xyTTP9zhQQtRRKIJPv5AuuOGJ6Ni9FLbDhcuIAcPjt3XzCxIWe3Uu", "xyTTP9zhQQtRRKIJPv5Au3"), //PassWord1 new HashedPassword("$2a$13$rGBrqErm9DZyzbxIGHlgf.xfA15/4d5Ay/TK.3y9lG3AljcoG9Lsi", "rGBrqErm9DZyzbxIGHlgfN"), //&^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/JoomlaTest.java b/src/test/java/fr/xephi/authme/security/crypts/JoomlaTest.java index f46d51d8..d2673b34 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/JoomlaTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/JoomlaTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link JOOMLA}. + * Test for {@link Joomla}. */ public class JoomlaTest extends AbstractEncryptionMethodTest { public JoomlaTest() { - super(new JOOMLA(), + super(new Joomla(), "b18c99813cd96df3a706652f47177490:377c4aaf92c5ed57711306909e6065ca", // password "c5af71da91a8841d95937ba24a5b7fdb:07068e5850930b794526a614438cafc7", // PassWord1 "f5fccd5166af7080833d7c7a6a531295:7cb6eeabcfac67ffe1341ec43375a9e6", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java b/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java index 0c9d67cf..c0791837 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link MD5}. + * Test for {@link Md5}. */ public class Md5Test extends AbstractEncryptionMethodTest { public Md5Test() { - super(new MD5(), + super(new Md5(), "5f4dcc3b5aa765d61d8327deb882cf99", // password "f2126d405f46ed603ff5b2950f062c96", // PassWord1 "0833dcd2bc741f90c46bbac5498fd08f", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/MD5VBTest.java b/src/test/java/fr/xephi/authme/security/crypts/Md5vBTest.java similarity index 74% rename from src/test/java/fr/xephi/authme/security/crypts/MD5VBTest.java rename to src/test/java/fr/xephi/authme/security/crypts/Md5vBTest.java index acb823e6..eac20fcd 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/MD5VBTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Md5vBTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link MD5VB}. + * Test for {@link Md5vB}. */ -public class MD5VBTest extends AbstractEncryptionMethodTest { +public class Md5vBTest extends AbstractEncryptionMethodTest { - public MD5VBTest() { - super(new MD5VB(), + public Md5vBTest() { + super(new Md5vB(), "$MD5vb$bd9832fffa287321$5006d371fcb813f2347987f902a024ad", // password "$MD5vb$5e492c1166b5a828$c954fa5ee561700a097826971653b57f", // PassWord1 "$MD5vb$3ec43cd46a61d70b$59687c0976f2e327b1245c8063f7008c", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java b/src/test/java/fr/xephi/authme/security/crypts/MyBBTest.java similarity index 76% rename from src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java rename to src/test/java/fr/xephi/authme/security/crypts/MyBBTest.java index 101f475f..fabd9f35 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/MyBBTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link MYBB}. + * Test for {@link MyBB}. */ -public class MYBBTest extends AbstractEncryptionMethodTest { +public class MyBBTest extends AbstractEncryptionMethodTest { - public MYBBTest() { - super(new MYBB(), + public MyBBTest() { + super(new MyBB(), new HashedPassword("57c7a16d860833db5030738f5a465d2b", "acdc14e6"), //password new HashedPassword("08fbdf721f2c42d9780b7d66df0ba830", "792fd7fb"), //PassWord1 new HashedPassword("d602f38fb59ad9e185d5604f5d4ddb36", "4b5534a4"), //&^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/PHPBBTest.java b/src/test/java/fr/xephi/authme/security/crypts/PhpBBTest.java similarity index 69% rename from src/test/java/fr/xephi/authme/security/crypts/PHPBBTest.java rename to src/test/java/fr/xephi/authme/security/crypts/PhpBBTest.java index 61a42360..f495659e 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/PHPBBTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/PhpBBTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link PHPBB}. + * Test for {@link PhpBB}. */ -public class PHPBBTest extends AbstractEncryptionMethodTest { +public class PhpBBTest extends AbstractEncryptionMethodTest { - public PHPBBTest() { - super(new PHPBB(), + public PhpBBTest() { + super(new PhpBB(), "$H$7MaSGQb0xe3Fp/a.Q.Ewpw.UKfCv.t0", // password "$H$7ESfAVjzqajC7fJFcZKZIhyds41MuW.", // PassWord1 "$H$7G65SXRPbR69jLg.qZTjtqsw36Ciw7.", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java b/src/test/java/fr/xephi/authme/security/crypts/PhpFusionTest.java similarity index 79% rename from src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java rename to src/test/java/fr/xephi/authme/security/crypts/PhpFusionTest.java index 0b10c1c7..a08a6ad8 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/PhpFusionTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link PHPFUSION}. + * Test for {@link PhpFusion}. */ -public class PHPFUSIONTest extends AbstractEncryptionMethodTest { +public class PhpFusionTest extends AbstractEncryptionMethodTest { - public PHPFUSIONTest() { - super(new PHPFUSION(), + public PhpFusionTest() { + super(new PhpFusion(), new HashedPassword("f7a606c4eb3fcfbc382906476e05b06f21234a77d1a4eacc0f93f503deb69e70", "6cd1c97c55cb"), // password new HashedPassword("8a9b7bb706a3347e5f684a7cb905bfb26b9a0d099358064139ab3ed1a66aeb2b", "d6012370b73f"), // PassWord1 new HashedPassword("43f2f23f44c8f89e2dbf06050bc8c77dbcdf71a7b5d28c87ec657d474e63d62d", "f75400a209a4"), // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/ROYALAUTHTest.java b/src/test/java/fr/xephi/authme/security/crypts/RoyalAuthTest.java similarity index 81% rename from src/test/java/fr/xephi/authme/security/crypts/ROYALAUTHTest.java rename to src/test/java/fr/xephi/authme/security/crypts/RoyalAuthTest.java index 0de9c2f3..7734dc03 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/ROYALAUTHTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/RoyalAuthTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link ROYALAUTH}. + * Test for {@link RoyalAuth}. */ -public class ROYALAUTHTest extends AbstractEncryptionMethodTest { +public class RoyalAuthTest extends AbstractEncryptionMethodTest { - public ROYALAUTHTest() { - super(new ROYALAUTH(), + public RoyalAuthTest() { + super(new RoyalAuth(), "5d21ef9236896bc4ac508e524e2da8a0def555dac1cdfc7259d62900d1d3f553826210c369870673ae2cf1c41abcf4f92670d76af1db044d33559324f5c2a339", // password "ecc685f4328bc54093c086ced66c5c11855e117ea22940632d5c0f55fff84d94bfdcc74e05f5d95bbdd052823a7057910748bc1c7a07af96b3e86731a4f11794", // PassWord1 "2c0b4674f7c2c266db13ae4382cbeee3083167a774f6e73793a6268a0b8b2c3c6b324a99596f4a7958e58c5311c77e25975a3b517ce17adfc4eaece821e3dd19", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java b/src/test/java/fr/xephi/authme/security/crypts/Salted2Md5Test.java similarity index 83% rename from src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java rename to src/test/java/fr/xephi/authme/security/crypts/Salted2Md5Test.java index 20ec99fe..fc34a13f 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Salted2Md5Test.java @@ -7,12 +7,12 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** - * Test for {@link SALTED2MD5}. + * Test for {@link Salted2Md5}. */ -public class SALTED2MD5Test extends AbstractEncryptionMethodTest { +public class Salted2Md5Test extends AbstractEncryptionMethodTest { - public SALTED2MD5Test() { - super(new SALTED2MD5(mockSettings()), + public Salted2Md5Test() { + super(new Salted2Md5(mockSettings()), new HashedPassword("9f3d13dc01a6fe61fd669954174399f3", "9b5f5749"), // password new HashedPassword("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"), // PassWord1 new HashedPassword("38dcb83cc68424afe3cda012700c2bb1", "eb2c3394"), // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java b/src/test/java/fr/xephi/authme/security/crypts/SaltedSha512Test.java similarity index 84% rename from src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java rename to src/test/java/fr/xephi/authme/security/crypts/SaltedSha512Test.java index 94cc78e8..98ab0615 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/SaltedSha512Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link SALTEDSHA512}. + * Test for {@link SaltedSha512}. */ -public class SALTEDSHA512Test extends AbstractEncryptionMethodTest { +public class SaltedSha512Test extends AbstractEncryptionMethodTest { - public SALTEDSHA512Test() { - super(new SALTEDSHA512(), + public SaltedSha512Test() { + super(new SaltedSha512(), new HashedPassword("dea7a37cecf5384ae8e347fd1411efb51364b6ba1b328695de3b354612c1d7010807e8b7051c40f740e498490e1f133e2c2408327d13fbdd68e1b1f6d548e624", "29f8a3c52147f987fee7ba3e0fb311bd"), // password new HashedPassword("7c06225aac574d2dc7c81a2ed306637adf025715f52083e05bdab014faaa234e24a97d0e69ea0108dfa77cc9228e58be319ee677e679b5d1ad168d40e50a42f6", "8ea37b85d020b98f60c0fe9b8ec9296c"), // PassWord1 new HashedPassword("55711adbe03c9616f3505f0d57077fdd528c32243eb6f9840c1a6ff9e553940d6b89790750ebd52ebda63ca793fbe9980d54057af40836820c648750fe22d49c", "9f58079631ef21d32b4710694f1f461b"), // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/Sha1Test.java b/src/test/java/fr/xephi/authme/security/crypts/Sha1Test.java index 620cef27..2bd5543f 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/Sha1Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Sha1Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link SHA1}. + * Test for {@link Sha1}. */ public class Sha1Test extends AbstractEncryptionMethodTest { public Sha1Test() { - super(new SHA1(), + super(new Sha1(), "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", // password "285d0c707f9644b75e1a87a62f25d0efb56800f0", // PassWord1 "a42ef8e61e890af80461ca5dcded25cbfcf407a4", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/Sha256Test.java b/src/test/java/fr/xephi/authme/security/crypts/Sha256Test.java index 3257fe1f..b9f75165 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/Sha256Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Sha256Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link SHA256}. + * Test for {@link Sha256}. */ public class Sha256Test extends AbstractEncryptionMethodTest { public Sha256Test() { - super(new SHA256(), + super(new Sha256(), "$SHA$11aa0706173d7272$dbba96681c2ae4e0bfdf226d70fbbc5e4ee3d8071faa613bc533fe8a64817d10", // password "$SHA$3c72a18a29b08d40$8e50a7a4f69a80f4893dc921eac84bd74b3f9ebfa22908302c9965eac3aa45e5", // PassWord1 "$SHA$584cea1cfab90030$adc006330e73d81e463fe02a4fe9b17bdbbcc05955bff72fb27cf2089f0b3859", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/Sha512Test.java b/src/test/java/fr/xephi/authme/security/crypts/Sha512Test.java index 17ba989c..2871cd3a 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/Sha512Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Sha512Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link SHA512}. + * Test for {@link Sha512}. */ public class Sha512Test extends AbstractEncryptionMethodTest { public Sha512Test() { - super(new SHA512(), + super(new Sha512(), "b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86", // password "ae9942149995a8171391625b36da134d5e288c721650d7c8d2d464fb49a49f3f551e4916ab1e097d9dd1201b01d69b1dccdefa3d2524a66092fb61b3df6e7e71", // PassWord1 "8c4f3df78db191142d819a72c16058b9e1ea41ae9b1649e1184eb89e30344c51c9c71039c483cf2f1b76b51480d8459d7eb3cfbaa24b07f2041d1551af4ead75", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/SMFTest.java b/src/test/java/fr/xephi/authme/security/crypts/SmfTest.java similarity index 72% rename from src/test/java/fr/xephi/authme/security/crypts/SMFTest.java rename to src/test/java/fr/xephi/authme/security/crypts/SmfTest.java index 8ba3b197..53803334 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/SMFTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/SmfTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link SMF}. + * Test for {@link Smf}. */ -public class SMFTest extends AbstractEncryptionMethodTest { +public class SmfTest extends AbstractEncryptionMethodTest { - public SMFTest() { - super(new SMF(), + public SmfTest() { + super(new Smf(), "9b361c66977bb059d460a20d3c21fb3394772df5", // password "31a560bdd095a837945d46add1605108ba87b268", // PassWord1 "8d4b84544e0891be8c183fe9b1003cfac18c51a1", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java b/src/test/java/fr/xephi/authme/security/crypts/Wbb3Test.java similarity index 81% rename from src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java rename to src/test/java/fr/xephi/authme/security/crypts/Wbb3Test.java index d838c14f..7443253f 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Wbb3Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link WBB3}. + * Test for {@link Wbb3}. */ -public class WBB3Test extends AbstractEncryptionMethodTest { +public class Wbb3Test extends AbstractEncryptionMethodTest { - public WBB3Test() { - super(new WBB3(), + public Wbb3Test() { + super(new Wbb3(), new HashedPassword("8df818ef7d56075ab2744f74b98ad68a375ccac4", "b7415b355492ea60314f259a35733a3092c03e3f"), // password new HashedPassword("106da5cf5df92cb845e12cf62cbdb5235b6dc693", "6110f19b2b52910dccf592a19c59126873f42e69"), // PassWord1 new HashedPassword("940a9fb7acec0178c6691e8b3c14bd7d789078b1", "f9dd501ff3d1bf74904f9e89649e378429af56e7"), // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/WBB4Test.java b/src/test/java/fr/xephi/authme/security/crypts/Wbb4Test.java similarity index 75% rename from src/test/java/fr/xephi/authme/security/crypts/WBB4Test.java rename to src/test/java/fr/xephi/authme/security/crypts/Wbb4Test.java index 5b714cab..6c5459f6 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WBB4Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Wbb4Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link WBB4}. + * Test for {@link Wbb4}. */ -public class WBB4Test extends AbstractEncryptionMethodTest { +public class Wbb4Test extends AbstractEncryptionMethodTest { - public WBB4Test() { - super(new WBB4(), + public Wbb4Test() { + super(new Wbb4(), "$2a$08$7DGr.wROqEPe0Z3XJS7n5.k.QWehovLHbpI.UkdfRb4ns268WsR6C", // password "$2a$08$yWWVUA4PB4mqW.0wyIvV3OdoH492HuLk5L3iaqUrpRK2.2zn08d/K", // PassWord1 "$2a$08$EHXUFt7bTT9Fnsu22KWvF.QDssiosV8YzH8CyWqulB/ckOA7qioJG", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/WHIRLPOOLTest.java b/src/test/java/fr/xephi/authme/security/crypts/WhirlpoolTest.java similarity index 81% rename from src/test/java/fr/xephi/authme/security/crypts/WHIRLPOOLTest.java rename to src/test/java/fr/xephi/authme/security/crypts/WhirlpoolTest.java index 1fbc94fd..76cef4b0 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WHIRLPOOLTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/WhirlpoolTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link WHIRLPOOL}. + * Test for {@link Whirlpool}. */ -public class WHIRLPOOLTest extends AbstractEncryptionMethodTest { +public class WhirlpoolTest extends AbstractEncryptionMethodTest { - public WHIRLPOOLTest() { - super(new WHIRLPOOL(), + public WhirlpoolTest() { + super(new Whirlpool(), "74DFC2B27ACFA364DA55F93A5CAEE29CCAD3557247EDA238831B3E9BD931B01D77FE994E4F12B9D4CFA92A124461D2065197D8CF7F33FC88566DA2DB2A4D6EAE", // password "819B4CBD26508E39EA76BFE102DCF2ACC87A446747CAB0BD88522B0822A724583E81B6A4BD2CE255DB694E530B659F47D434EEB50344A02F50B64414C9671583", // PassWord1 "71ECB0E5AEAB006F5336348076AA6A8E46075AEC9E010C7055BA1334B57746F2A9D8A8799BDD9B7EB4AB7544A59D25F469C8BCA2067508ACBA62A929260A1E17", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java b/src/test/java/fr/xephi/authme/security/crypts/WordpressTest.java similarity index 77% rename from src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java rename to src/test/java/fr/xephi/authme/security/crypts/WordpressTest.java index 49a16d65..0c444c2f 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/WordpressTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link WORDPRESS}. + * Test for {@link Wordpress}. */ -public class WORDPRESSTest extends AbstractEncryptionMethodTest { +public class WordpressTest extends AbstractEncryptionMethodTest { - public WORDPRESSTest() { - super(new WORDPRESS(), + public WordpressTest() { + super(new Wordpress(), "$P$B9wyjxuU4yrfjnnHNGSzH9ti9CC0Os1", // password "$P$BjzPjjzPjjkRzvGGRTyYu0sNqcz6Ci0", // PassWord1 "$P$BjzPjjzPjrAOyB1V0WFdpisgCTFx.N/", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/XAUTHTest.java b/src/test/java/fr/xephi/authme/security/crypts/XAuthTest.java similarity index 84% rename from src/test/java/fr/xephi/authme/security/crypts/XAUTHTest.java rename to src/test/java/fr/xephi/authme/security/crypts/XAuthTest.java index 4fcd8039..877a81ee 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/XAUTHTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/XAuthTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link XAUTH}. + * Test for {@link XAuth}. */ -public class XAUTHTest extends AbstractEncryptionMethodTest { +public class XAuthTest extends AbstractEncryptionMethodTest { - public XAUTHTest() { - super(new XAUTH(), + public XAuthTest() { + super(new XAuth(), "e54d4916577410d26d2f6e9362445463dab9ffdff9a67ed3b74d3f2312bc8fab84f653fcb88ad8338793ef8a6d0a1162105e46ec24f0dcb52355c634e3e6439f45444b09c715", // password "d54489a4fd4732ee03d56810ab92944096e3d49335266adeecfbc12567abb3ff744761b33a1fcc4d04739e377775c788e4baace3caf35c7b9176b82b1fe3472e4cbdc5a43214", // PassWord1 "ce6404c1092fb5abf0a72f9c4327bfe8f4cdc4b8dc90ee6ca35c42b8ae9481b89c2559bb60b99ff2b57a102cfced40b8e2f5ef481400c9e6f79445017fc763b1cc27f4c2df36", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java b/src/test/java/fr/xephi/authme/security/crypts/XfBCryptTest.java similarity index 73% rename from src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java rename to src/test/java/fr/xephi/authme/security/crypts/XfBCryptTest.java index 4edafbdd..af1f4589 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/XfBCryptTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link XFBCRYPT}. + * Test for {@link XfBCrypt}. */ -public class XFBCRYPTTest extends AbstractEncryptionMethodTest { +public class XfBCryptTest extends AbstractEncryptionMethodTest { - public XFBCRYPTTest() { - super(new XFBCRYPT(), + public XfBCryptTest() { + super(new XfBCrypt(), "$2a$10$UtuON/ZG.x8EWG/zQbryB.BHfQVrfxk3H7qykzP.UJQ8YiLjZyfqq", // password "$2a$10$Q.ocUo.YtHTdI4nu3pcpKun6BILcmWHm541ANULucmuU/ps1QKY4K", // PassWord1 "$2a$10$yHjm02.K4HP5iFU1F..yLeTeo7PWZVbKAr/QGex5jU4.J3mdq/uuO", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/service/CommonServiceTest.java b/src/test/java/fr/xephi/authme/service/CommonServiceTest.java index 7927f826..ad0b7e73 100644 --- a/src/test/java/fr/xephi/authme/service/CommonServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/CommonServiceTest.java @@ -84,21 +84,6 @@ public class CommonServiceTest { verify(messages).send(sender, key, replacements); } - @Test - public void shouldRetrieveMessage() { - // given - MessageKey key = MessageKey.ACCOUNT_NOT_ACTIVATED; - String[] lines = new String[]{"First message line", "second line"}; - given(messages.retrieve(key)).willReturn(lines); - - // when - String[] result = commonService.retrieveMessage(key); - - // then - assertThat(result, equalTo(lines)); - verify(messages).retrieve(key); - } - @Test public void shouldRetrieveSingleMessage() { // given diff --git a/src/test/java/fr/xephi/authme/service/MigrationServiceTest.java b/src/test/java/fr/xephi/authme/service/MigrationServiceTest.java index fa82b97a..11a0f253 100644 --- a/src/test/java/fr/xephi/authme/service/MigrationServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/MigrationServiceTest.java @@ -5,7 +5,7 @@ import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.security.crypts.SHA256; +import fr.xephi.authme.security.crypts.Sha256; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; import org.junit.BeforeClass; @@ -28,6 +28,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.hamcrest.MockitoHamcrest.argThat; +import static fr.xephi.authme.AuthMeMatchers.equalToHash; /** * Test for {@link MigrationService}. @@ -42,7 +43,7 @@ public class MigrationServiceTest { private DataSource dataSource; @Mock - private SHA256 sha256; + private Sha256 sha256; @BeforeClass public static void setUpLogger() { @@ -122,7 +123,7 @@ public class MigrationServiceTest { .build(); } - private static void setSha256MockToUppercase(SHA256 sha256) { + private static void setSha256MockToUppercase(Sha256 sha256) { given(sha256.computeHash(anyString(), anyString())).willAnswer(new Answer() { @Override public HashedPassword answer(InvocationOnMock invocation) { diff --git a/src/test/java/fr/xephi/authme/service/PluginHookServiceTest.java b/src/test/java/fr/xephi/authme/service/PluginHookServiceTest.java index d4e3f8cd..e07ea1c2 100644 --- a/src/test/java/fr/xephi/authme/service/PluginHookServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/PluginHookServiceTest.java @@ -58,7 +58,7 @@ public class PluginHookServiceTest { assertThat(pluginHookService.isEssentialsAvailable(), equalTo(true)); } - // Note ljacqu 20160312: Cannot test with Multiverse or CombatTagPlus because their classes are declared final + // Note ljacqu 20160312: Cannot test with CombatTagPlus because its class is declared final @Test public void shouldHookIntoEssentialsAtInitialization() { diff --git a/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java b/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java index 21ffa952..3953d4ef 100644 --- a/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java @@ -4,24 +4,30 @@ import ch.jalu.injector.testing.BeforeInjecting; import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import com.google.common.base.Strings; +import fr.xephi.authme.TestHelper; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; +import fr.xephi.authme.service.ValidationService.ValidationResult; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.ProtectionSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.service.ValidationService.ValidationResult; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.junit.Test; 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 java.util.Arrays.asList; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; @@ -55,6 +61,7 @@ public class ValidationServiceTest { .willReturn(asList("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.ENABLE_RESTRICTED_USERS)).willReturn(false); } @Test @@ -115,8 +122,8 @@ public class ValidationServiceTest { @Test public void shouldAcceptEmailWithEmptyLists() { // given - given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); - given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); // when boolean result = validationService.validateEmail("test@example.org"); @@ -130,7 +137,7 @@ public class ValidationServiceTest { // given given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)) .willReturn(asList("domain.tld", "example.com")); - given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); // when boolean result = validationService.validateEmail("TesT@Example.com"); @@ -144,7 +151,7 @@ public class ValidationServiceTest { // given given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)) .willReturn(asList("domain.tld", "example.com")); - given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); // when boolean result = validationService.validateEmail("email@other-domain.abc"); @@ -156,7 +163,7 @@ public class ValidationServiceTest { @Test public void shouldAcceptEmailNotInBlacklist() { // given - given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)) .willReturn(asList("Example.org", "a-test-name.tld")); @@ -170,7 +177,7 @@ public class ValidationServiceTest { @Test public void shouldRejectEmailInBlacklist() { // given - given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)) .willReturn(asList("Example.org", "a-test-name.tld")); @@ -263,8 +270,8 @@ public class ValidationServiceTest { @Test public void shouldNotInvokeGeoLiteApiIfCountryListsAreEmpty() { // given - given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); - given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); // when boolean result = validationService.isCountryAdmitted("addr"); @@ -278,7 +285,7 @@ public class ValidationServiceTest { public void shouldAcceptCountryInWhitelist() { // given given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(asList("ch", "it")); - given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); String ip = "127.0.0.1"; given(geoIpService.getCountryCode(ip)).willReturn("CH"); @@ -294,7 +301,7 @@ public class ValidationServiceTest { public void shouldRejectCountryMissingFromWhitelist() { // given given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(asList("ch", "it")); - given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); String ip = "123.45.67.89"; given(geoIpService.getCountryCode(ip)).willReturn("BR"); @@ -309,7 +316,7 @@ public class ValidationServiceTest { @Test public void shouldAcceptCountryAbsentFromBlacklist() { // given - given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(asList("ch", "it")); String ip = "127.0.0.1"; given(geoIpService.getCountryCode(ip)).willReturn("BR"); @@ -325,7 +332,7 @@ public class ValidationServiceTest { @Test public void shouldRejectCountryInBlacklist() { // given - given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(asList("ch", "it")); String ip = "123.45.67.89"; given(geoIpService.getCountryCode(ip)).willReturn("IT"); @@ -338,6 +345,54 @@ public class ValidationServiceTest { verify(geoIpService).getCountryCode(ip); } + @Test + public void shouldCheckNameRestrictions() { + // 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")); + validationService.reload(); + + Player bobby = mockPlayer("bobby", "127.0.0.4"); + Player tamara = mockPlayer("taMARA", "8.8.8.8"); + Player notRestricted = mockPlayer("notRestricted", "0.0.0.0"); + + // when + boolean isBobbyAdmitted = validationService.fulfillsNameRestrictions(bobby); + boolean isTamaraAdmitted = validationService.fulfillsNameRestrictions(tamara); + boolean isNotRestrictedAdmitted = validationService.fulfillsNameRestrictions(notRestricted); + + // then + assertThat(isBobbyAdmitted, equalTo(true)); + assertThat(isTamaraAdmitted, equalTo(false)); + assertThat(isNotRestrictedAdmitted, equalTo(true)); + } + + @Test + public void shouldLogWarningForInvalidRestrictionRule() { + // given + 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;")); + + // when + validationService.reload(); + + // then + ArgumentCaptor stringCaptor = ArgumentCaptor.forClass(String.class); + verify(logger).warning(stringCaptor.capture()); + assertThat(stringCaptor.getValue(), containsString("Tamara;")); + } + + private static Player mockPlayer(String name, String ip) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + TestHelper.mockPlayerIp(player, ip); + given(player.getAddress().getHostName()).willReturn("--"); + return player; + } + private static void assertErrorEquals(ValidationResult validationResult, MessageKey messageKey, String... args) { assertThat(validationResult.hasError(), equalTo(true)); assertThat(validationResult.getMessageKey(), equalTo(messageKey)); diff --git a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java index 88724a84..cddc0cab 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java @@ -1,15 +1,33 @@ 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.ImmutableSetMultimap; +import com.google.common.collect.Multimap; +import fr.xephi.authme.ClassCollector; +import fr.xephi.authme.ReflectionTestUtils; +import fr.xephi.authme.datasource.DataSourceType; import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever; +import fr.xephi.authme.settings.properties.DatabaseSettings; +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; /** @@ -22,6 +40,17 @@ public class SettingsConsistencyTest { */ private static final int MAX_COMMENT_LENGTH = 90; + /** + * Exclusions for the enum in comments check. Use {@link Exclude#ALL} + * to skip an entire property from being checked. + */ + private static final Multimap, Enum> EXCLUDED_ENUMS = + ImmutableSetMultimap., Enum>builder() + .put(DatabaseSettings.BACKEND, DataSourceType.FILE) + .put(SecuritySettings.PASSWORD_HASH, Exclude.ALL) + .put(SecuritySettings.LEGACY_HASHES, Exclude.ALL) + .build(); + private static ConfigurationData configurationData; @BeforeClass @@ -66,4 +95,119 @@ public class SettingsConsistencyTest { } } + @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("src/main/java", "fr/xephi/authme/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 + * so the user knows which values are available. + */ + @Test + public void shouldMentionAllEnumValues() { + // given + Map, Enum> invalidEnumProperties = new HashMap<>(); + + for (Property property : configurationData.getProperties()) { + // when + Class> enumClass = getEnumClass(property); + if (enumClass != null) { + String comments = String.join("\n", configurationData.getCommentsForSection(property.getPath())); + Arrays.stream(enumClass.getEnumConstants()) + .filter(e -> !comments.contains(e.name()) && !isExcluded(property, e)) + .findFirst() + .ifPresent(e -> invalidEnumProperties.put(property, e)); + } + } + + // then + if (!invalidEnumProperties.isEmpty()) { + String invalidEnums = invalidEnumProperties.entrySet().stream() + .map(e -> e.getKey() + " does not mention " + e.getValue() + " and possibly others") + .collect(Collectors.joining("\n- ")); + + fail("Found enum properties that do not list all entries in the comments:\n- " + invalidEnums); + } + } + + /** + * Returns the enum class the property holds values for, if applicable. + * + * @param property the property to get the enum class from + * @return the enum class, or null if not available + */ + private static Class> getEnumClass(Property property) { + if (property instanceof EnumProperty) { + return getFieldValue(EnumProperty.class, (EnumProperty) property, "clazz"); + } else if (property instanceof EnumSetProperty) { + return getFieldValue(EnumSetProperty.class, (EnumSetProperty) property, "enumClass"); + } + return null; + } + + private static boolean isExcluded(Property property, Enum enumValue) { + return EXCLUDED_ENUMS.get(property).contains(Exclude.ALL) + || EXCLUDED_ENUMS.get(property).contains(enumValue); + } + + /** + * Dummy enum to specify in the exclusion that all enum values + * should be skipped. See its usages. + */ + private enum Exclude { + ALL + } } diff --git a/src/test/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetrieverTest.java b/src/test/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetrieverTest.java index 2a1c588e..fcb1228c 100644 --- a/src/test/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetrieverTest.java +++ b/src/test/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetrieverTest.java @@ -22,7 +22,7 @@ public class AuthMeSettingsRetrieverTest { // an error margin of 10: this prevents us from having to adjust the test every time the config is changed. // If this test fails, replace the first argument in closeTo() with the new number of properties assertThat((double) configurationData.getProperties().size(), - closeTo(150, 10)); + closeTo(160, 10)); } @Test diff --git a/src/test/java/fr/xephi/authme/settings/properties/SettingsClassConsistencyTest.java b/src/test/java/fr/xephi/authme/settings/properties/SettingsClassConsistencyTest.java index a88dbfb9..d0d74135 100644 --- a/src/test/java/fr/xephi/authme/settings/properties/SettingsClassConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/settings/properties/SettingsClassConsistencyTest.java @@ -1,6 +1,7 @@ package fr.xephi.authme.settings.properties; import ch.jalu.configme.SettingsHolder; +import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.properties.Property; import fr.xephi.authme.ClassCollector; import fr.xephi.authme.ReflectionTestUtils; @@ -10,11 +11,13 @@ import org.junit.Test; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @@ -74,6 +77,27 @@ public class SettingsClassConsistencyTest { } } + /** + * Checks that {@link AuthMeSettingsRetriever} returns a ConfigurationData with all + * available SettingsHolder classes. + */ + @Test + public void shouldHaveAllClassesInConfigurationData() { + // given + long totalProperties = classes.stream() + .map(Class::getDeclaredFields) + .flatMap(Arrays::stream) + .filter(field -> Property.class.isAssignableFrom(field.getType())) + .count(); + + // when + ConfigurationData configData = AuthMeSettingsRetriever.buildConfigurationData(); + + // then + assertThat("ConfigurationData should have " + totalProperties + " properties (as found manually)", + configData.getProperties(), hasSize((int) totalProperties)); + } + @Test public void shouldHaveHiddenEmptyConstructorOnly() { for (Class clazz : classes) { diff --git a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java index 629adbd4..7111f81b 100644 --- a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java @@ -95,4 +95,14 @@ public class StringUtilsTest { public void shouldHaveHiddenConstructor() { TestHelper.validateHasOnlyPrivateEmptyConstructor(StringUtils.class); } + + @Test + public void shouldCheckIfHasNeedleInWord() { + // given/when/then + assertThat(StringUtils.isInsideString('@', "@hello"), equalTo(false)); + assertThat(StringUtils.isInsideString('?', "absent"), equalTo(false)); + assertThat(StringUtils.isInsideString('-', "abcd-"), equalTo(false)); + assertThat(StringUtils.isInsideString('@', "hello@example"), equalTo(true)); + assertThat(StringUtils.isInsideString('@', "D@Z"), equalTo(true)); + } } diff --git a/src/test/java/fr/xephi/authme/util/UtilsTest.java b/src/test/java/fr/xephi/authme/util/UtilsTest.java index 458023d6..cbb64f37 100644 --- a/src/test/java/fr/xephi/authme/util/UtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/UtilsTest.java @@ -1,13 +1,20 @@ package fr.xephi.authme.util; import fr.xephi.authme.TestHelper; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; import org.junit.BeforeClass; import org.junit.Test; +import java.util.logging.Logger; import java.util.regex.Pattern; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; /** * Test for {@link Utils}. @@ -49,6 +56,49 @@ public class UtilsTest { TestHelper.validateHasOnlyPrivateEmptyConstructor(Utils.class); } + @Test + public void shouldLogAndSendMessage() { + // given + Logger logger = TestHelper.setupLogger(); + Player player = mock(Player.class); + String message = "Finished adding foo to the bar"; + + // when + Utils.logAndSendMessage(player, message); + + // then + verify(logger).info(message); + verify(player).sendMessage(message); + } + + @Test + public void shouldHandleNullAsCommandSender() { + // given + Logger logger = TestHelper.setupLogger(); + String message = "Test test, test."; + + // when + Utils.logAndSendMessage(null, message); + + // then + verify(logger).info(message); + } + + @Test + public void shouldNotSendToCommandSenderTwice() { + // given + Logger logger = TestHelper.setupLogger(); + CommandSender sender = mock(ConsoleCommandSender.class); + String message = "Test test, test."; + + // when + Utils.logAndSendMessage(sender, message); + + // then + verify(logger).info(message); + verifyZeroInteractions(sender); + } + @Test public void shouldCheckIfClassIsLoaded() { // given / when / then diff --git a/src/test/resources/fr/xephi/authme/data/limbo/seg16-8-limbo.json b/src/test/resources/fr/xephi/authme/data/limbo/seg16-8-limbo.json new file mode 100644 index 00000000..84da8a26 --- /dev/null +++ b/src/test/resources/fr/xephi/authme/data/limbo/seg16-8-limbo.json @@ -0,0 +1,32 @@ +{ + "88897c88-7c8f-c12e-4931-6206d4ca067d": { + "location": { + "world": "world", + "x": -196.69999998807907, + "y": 67.0, + "z": 5.699999988079071, + "yaw": 222.14977, + "pitch": 10.649977 + }, + "group": "staff", + "operator": true, + "can-fly": false, + "walk-speed": 0.3, + "fly-speed": 0.1 + }, + "8c679491-1234-abcd-9102-1fa6e0cc3f81": { + "location": { + "world": "nether", + "x": 300.12345, + "y": 42.3, + "z": -72.482749988079071, + "yaw": 100.27788, + "pitch": 4.242111 + }, + "group": "primary", + "operator": false, + "can-fly": true, + "walk-speed": 0.1, + "fly-speed": 0.0 + } +} diff --git a/src/test/resources/fr/xephi/authme/data/limbo/seg16-f-limbo.json b/src/test/resources/fr/xephi/authme/data/limbo/seg16-f-limbo.json new file mode 100644 index 00000000..9f256262 --- /dev/null +++ b/src/test/resources/fr/xephi/authme/data/limbo/seg16-f-limbo.json @@ -0,0 +1,17 @@ +{ + "fab69c88-2cd0-1fed-f00d-dead14ca067d": { + "location": { + "world": "world", + "x": -196.69999998807907, + "y": 67.0, + "z": 5.699999988079071, + "yaw": 222.14977, + "pitch": 10.649977 + }, + "group": "", + "operator": false, + "can-fly": false, + "walk-speed": 0.2, + "fly-speed": 0.1 + } +} diff --git a/src/test/resources/fr/xephi/authme/data/limbo/seg32-01011-limbo.json b/src/test/resources/fr/xephi/authme/data/limbo/seg32-01011-limbo.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/test/resources/fr/xephi/authme/data/limbo/seg32-01011-limbo.json @@ -0,0 +1 @@ +{} diff --git a/src/test/resources/fr/xephi/authme/data/limbo/seg32-10110-limbo.json b/src/test/resources/fr/xephi/authme/data/limbo/seg32-10110-limbo.json new file mode 100644 index 00000000..b50fb8bd --- /dev/null +++ b/src/test/resources/fr/xephi/authme/data/limbo/seg32-10110-limbo.json @@ -0,0 +1,17 @@ +{ + "f6a97c88-7c8f-c12e-4931-6206d4ca067d": { + "location": { + "world": "lobby", + "x": -120.31415, + "y": 25.0, + "z": -80.71234, + "yaw": 22.14977, + "pitch": 40.649977 + }, + "group": "noob", + "operator": false, + "can-fly": true, + "walk-speed": 0.2, + "fly-speed": 0.1 + } +}